技術のお話

久しぶりにBlog書く。
TwitterFacebookでは長すぎる気がしたので、ここに吐き出しておく。

自分の居る会社は、仕事以外でも技術を磨いたり新しい知識を得ることに積極的なエンジニアが多く、趣味で何かを作ったり、触ってみたりした結果を同僚と面白おかしく共有したり、LTなどにまとめて発表するといった文化が根付いている。というか、LTについては全員参加が義務で、あらかじめ発表順とかも細かくスケジューリングされている。

自分も紆余曲折はあったけれども、今の会社に入社し、そういうところは真似するべきだとも思って、会社で見聞きしたOSSなどにプライベートで触れてみたり、勉強会に参加してみたりした。ところが、長続きしないし、何か新しいモノを得られたり満足できた気もしないし、単に貴重な時間を無駄にしただけのように感じられた。

面倒だったり、良いアイデアが浮かばなかったり、「勉強」と捉えて考えると気合が必要で…。3日坊主どころか、手を付けることさえ億劫になってしまったりした。入る会社を間違えたか、そもそもエンジニアに向いているのだろうか、この集団の中に居てもいいのだろうか、などと考えることもあった。

                            • -

そうなってしまう原因を自分なりに色々考え続けた。

ただなんとなく流行りのソフトウェアや言語を触ってみたりしようとすると、目的意識が無いから手をつけるまでに至らないことが多い。適当にネットで表面的なことを調べたりはするんだけど、それで終わってしまう。これを試して何を得るのか、何を作るのか。勉強嫌いな人間なので、目的が勉強だとモチベーションが上がらない。

たぶん成長意欲やクリエイティブ精神に溢れたエンジニアなら、そういう風にはならないんだと思う。同僚も先輩もみんな本当に楽しそうにやってたから、ますます置いてけぼり感が強くなった。仕事ではコードも書いて、給料も貰ってるし、評価もされたけど、なんだか物足りない日々が続く。

                            • -

そこで、あえて目的を(無理やり)作ってみると、これが不思議なことに、あまり苦にならないことが分かった。例えばLT。(実際に発表するかどうかは置いといて)会社のLTで発表するんだ、プレゼンを作らねば、という目的を作り、同時進行で進めると、あまり深い領域まで突っ込むまでには至らないけど、基本的なことは調べて試してプレゼン資料にすることができた。何かアプリケーションを作ったりまではしなくとも、プレゼンという成果物ができるので、そこそこ満足できて、記憶にも残ることが分かった。

試す事とプレゼンを作ることを別工程にするのではなく、同時に平行してやるのがポイントで、コードをCloneしてきてビルドできたら、そこまでの手順をプレゼンに書く。設定ファイルが作成できたら、それについて書く。次に実際に起動して…という風に同時にやるのがポイント。

まぁこのやり方でもそのうち飽きるかもだけど、しばらく様子見してみようと思う。

Windows PCにgollumを入れて動かす方法

最初に断っておくと、Windows用にソースを書き換えるとか、そういう対応ではなく、
VMを入れてそこにgollumを入れてローカルで使えるようにする、的なモノ。

まずgollumについて
gollumはGidHubのWiki機能を単体で動かせるようにしたもの。
単体でサーバー機能を有しており、Webブラウザからアクセスして、
好きなようにWikiを作ることができる。

ローカルで整形されたメモが欲しい時に便利かも。
というか、自分はそれが目的。

その他の特徴とか...

  • Gitで記事が管理されるので、DBのインストールや設定が不要
  • 全文検索機能がある
  • いろいろなMarkdown記法が使える
  • もちろんコードハイライトなんかも使える

ただし、注意点がいくつか。
いずれも現行Verでの注意点なので、今後改善される可能性はある。

  • Windowsでは動作しない
  • Ruby2.0.0では動作しない (1.9.3推奨)


長い長いセットアップ
まずVM環境を作ろう。
Windows8 Proを「偶然」使っていたので、Hyper-Vを使ってみた。

何気にこの記事の大半はHyper-Vの話になってしまっている。
もちろんVMWareVirtualBoxでもOKなはず。

長いので簡潔にまとめる。

  1. Metro (Modern UI)を立ち上げて「Windowsの機能」と入力
  2. 「設定」タブ → Windowsの機能の有効化または無効化
  3. Hyper-VにチェックをつけてOKボタン → 2回ぐらい再起動されるので待つ
  4. Hyper-Vマネージャを起動

んで、ここからは手順が人によって変わってくると思うんだけど、
自分はASUSのUltrabookを使っているので無線LANかつNICが1つしか無い。
このままだと仮想マシンを作ってもネットに繋げないので、
仮想アダプタを作って、無線とブリッジ接続する方法を採る。

この辺、たぶん間違いもあるんだろうけど、お遊び用という前提で緩くやっちゃう。

というわけで、以下の手順で...

  1. Hyper-Vマネージャの「仮想スイッチマネージャ」をクリック
  2. 仮想スイッチの作成 → 「内部」を選択して作成
  3. 名前は適当に、「内部ネットワーク」になってることを確認してOK
  4. ネットワークと共有センター → アダプターの設定の変更
  5. さっき作った仮想アダプタがあるはずなので、そいつとWi-Fiを選択して右クリック → ブリッジ接続、再起動を求められたら再起動する

いよいよ仮想マシンを作る

  1. Hyper-Vマネージャの右側のメニューから、新規 → 仮想マシン
  2. メモリ割り当ては自分は1024MBにしたけど、それはスペックと相談して
  3. ネットワークの構成で、さっき作った仮想アダプタを選択
  4. インストールオプションで、インストールするLinuxとかが入ったisoを指定
  5. 仮想マシンが作成されたら、右クリック → 接続
  6. 上のメニューから、操作 → 起動 (だったかな?)
  7. あとはLinuxを入れる

入れたあとの作業、まずネットワーク設定から

  • /etc/sysconfig/network を、大体こんな風に設定
NETWORKING=yes
HOSTNAME=host名
NETWORKING_IPV6=no
GATEWAY=192.168.0.1
  • ifconfig -a して、eth0が見えることを確認し
  • /etc/sysconfig/network-scripts/ifcfg-eth0 を作成して編集
DEVICE=eth0
ONBOOT=yes
BOOTPROTO=none
DNS1=192.168.0.1
IPADDR=192.168.0.{被らない程度の値に}
  • sudo /etc/init.d/network restart して、ちゃんと再起動できればOK
  • 試しにブラウザでネットに繋がるかどうか確認しておこう
  • ホスト側からgollumを使いたいので、firewall設定でport=4567を開けておく

gollumを入れて動かす

  1. ruby 1.9.3 と git を入れておこう (間違えて ruby 2.0.0 を入れないように)
  2. gem install gollum で gollumを入れる
  3. mkdir gollum → cd gollum → git init
  4. gollumコマンドで起動、デーモンっぽく運用した方がいいけど、とりあえずこれで
  5. http://localhost:4567/ でアクセス
  6. ホストからだと http://{仮想マシンで設定したIP}:4567/ でOK


最後にもう1つだけ注意点。
本文に日本語を使うのは全然OKなんだけど、ページタイトルに日本語が
使えない。これ、直すこともできるんだろうけど、今回はとりあえずナシで。

to_url周辺が怪しい模様。

RSpecでFailure時にEncoding::UndefinedConversionErrorが出る時の対処法

Ruby 1.9.3以降とか、特定のVerの組み合わせで出る症状と思われる。
もっと良い解決策があるんだろうと思うけど、以下のようにすれば
とりあえずは直る。

# gems/rspec-expectations-2.14.0/lib/rspec/expectations/differ.rb

# 変更前
def matching_encoding(string, source)
        string.encode(source.encoding)
end

# 変更後
def matching_encoding(string, source)
        string.force_encoding('UTF-8')
end


調べてみたところ、source.encodingがおかしくなることがあるようで、
encodeに失敗してしまうらしい。
会社の先輩がio.readが云々、みたいに言ってたけど、
もっと深く追いかければその辺に行き着くのかもしれない。

ちなみに…

def matching_encoding(string, source)
        string.encode(source.encoding, :undef => :replace)
end

こう書いても通るが、日本語などマルチバイト部分が読めなくなるので非推奨。

アレのチェッカーアプリ

仕組みは秘密だけど、うちの会社は(男子)トイレの個室が
使用中かどうかが分かるようになっている。

結果はjsonで取得できる(社外でも)。

んで、それをチェックするアプリやらが社内でたくさん作られたことがあったんだけど、
自分もなんか作ってみたのでメモ(今も誰かが作ってるかも)。

要件はこんな感じ。

  • 一定時間ごとにチェックして欲しい
  • ウインドウは出さないで、タスクトレイに入っていて欲しい
  • 結果はバルーンメッセージで表示して欲しい

環境はPySideを使った。
Python用のQtライブラリで、PyQtというのもあるんだけどそっちは有料なので、
こちらを使うことにした。


タスクトレイからバルーンメッセージ、というと面倒そうだけど
割と簡単に実現できる。こんな感じ。

import threading
from PySide import QtCore
from PySide import QtGui

class MainWindow(QtGui.QDialog):
    def __init__(self, parent = None):
        super(MainWindow, self).__init__(parent)
        self.resize(0, 0)  # ウインドウは表示しない

        # タスクトレイに「終了」メニューを追加
        self.quitAction = QtGui.QAction(u"&終了", self)
        self.connect(self.quitAction, QtCore.SIGNAL('triggered()'), QtGui.qApp, QtCore.SLOT('quit()'))
        self.quitAction.triggered.connect(self.trayCloseEvent)

        # 終了メニューと終了アクションを結びつける
        self.trayIconMenu = QtGui.QMenu(self)
        self.trayIconMenu.addAction(self.quitAction)

        # タスクトレイメニューとアイコンを設定
        self.trayIcon = QtGui.QSystemTrayIcon(self)
        self.trayIcon.setContextMenu(self.trayIconMenu)
        self.trayIcon.setIcon(QtGui.QIcon(ICON_FILE_NAME))
        
        # トレイを表示
        self.trayIcon.show()

        # チェッカーを持っておく
        self.__checker = Checker()

    # タスクトレイアイコンのメニューから「終了」を選んだ時
    def trayCloseEvent(self):
        self.endApplication()

    # ウインドウを閉じた時
    def closeEvent(self, event):
        self.endApplication()

    # 終了時の処理
    def endApplication(self):
        # スレッドを止めておく
        if self.__th.isAlive():
            self.__th._stop()
        QtGui.qApp.quit()

    # 実行
    def run(self):
        self.set_next_thread()
        return super(MainWindow, self).exec_()

    # 空き状況のチェック
    def check(self):
        r = self.__checker.check()
        self.trayIcon.showMessage("アレの状況", r)
        self.set_next_thread()

    # CHECK_INTERVAL_SECOND秒後にself.checkを実行するように設定
    # QThreadの方が良い...???
    def config_next_thread(self):
        self.__th = threading.Timer(CHECK_INTERVAL_SECOND, self.check)
        self.__th.start()

class Checker:
    def check():
        ### 取得する方法はいくらでもあると思うので、略

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    if not QtGui.QSystemTrayIcon.isSystemTrayAvailable():
        raise OSError("Can not use system tray on this system !")

    app.setQuitOnLastWindowClosed(False)
    app.setApplicationName('Checker')
    mainWindow = MainWindow()
    return mainWindow.run()

コードの行数は全部で140行ぐらい。
実行すると、こういう風に表示される。

f:id:woodboy644:20130729235846p:plain

PythonとQtだし、そのままMacでも動くのでは…と思ったら、
そんな都合の良い話は無かった。

具体的には...

  • Macでバルーンを出すには、Growlが必要 (Growl最新版は有料)
  • この辺に書かれているような、「おまじない」が必要らしい

2つ目が原因かどうかは分かんないけど、素直にGrowlを入れて実行したら
Segmentation Fault になった。

まずはQThreadを使うようにして、おまじないを試してみるのが良いかな。

あと、一定時間ごとに出るとちょっとウザい感じがするので、
ショートカットキーで出せたら良いかもしれない。

PHPの配列

仕事で気付いたんだけど、

// こんなことや..
$hoge = false;
$value1 = $hoge['something'];

// こんなことをしても...
$hoge = null;
$value2 = $hoge['something'];

// どちらもnullになるんだね
echo gettype($value1) . "\n";    // NULL
echo gettype($value2) . "\n";    // NULL

ちなみに、php.iniで

error_reporting = E_ALL | E_STRICT

としても警告などは出なかった。

不思議な仕様だなぁ。

ハッカソン

とある企画でハッカソンをやるらしく、普段使わない言語を使ってみましょう、
ということで例が挙がってたんだけど、Pythonが含まれていなくて悲しくなった。

でも普段使わないモノだから、過度にテクニカルなイベントにはならなさそうで、
その辺でバランス取られてるような気がした。

今のうちにWSGIとか触っておこうかしら。

Redisのアブナイ話

そういえば最近引っかかったRedisのトラップ(?)について
復習も兼ねて、色々思い出しつつ、まとめておこうと思った。

細かいところは間違ってるかもしれないけど…

Redisのコードはこのあたりで読める。

Redisってなんなの?

オンメモリKVSの1つで、key - value形式でデータを保持するNoSQL DBの1つ。
メモリ上にDBを保持 & C言語のみで他のライブラリに依存しない実装なので、速い。
一定条件(設定可能)でディスクにDBを書き出すことが可能なので、
仮にRedis本体が落っこちても、ちょっと前の状態までなら復元することができる。

同じようなKVSにmemcachedというモノがあるが、こっちはディスク書き出しが無い。
ので、Redis的な使い方をするなら、従来のDBと組み合わせて実装するなどの
工夫が求められる。その分、ややこしくはなる。

仮想メモリ機能もVer2.0から導入されたんだけど、ポリシー的な問題か、
あるいは実装上の問題かは分からないけど、実装したこと自体が失敗だったという
結論に至ったらしく、Ver2.4で使うと警告が出るようになり、
Ver2.6では機能自体が廃止された。


日本語のドキュメントとか、ノウハウ的な文書は少なめ。
実はあまり使われていないんだろうか・・・?

ほかには?

期限付きのkeyを設定することができる。保持期間をExpireと呼ぶ。

キャッシュに使ってるけど、実データが時々更新される可能性があるので、
短時間だけ保持しておいて、切れたら取得しなおす、みたいなときに有用。

いくらRedisが速いとは言っても、常に全keyを監視してるわけじゃないので、
期限が切れてもすぐにメモリ上から消えてくれるわけじゃない。

「いつでも消える条件が整った」というだけで、実際には参照されるまで消えない。
逆に言うと、keysコマンドなどで故意に参照することで広範囲の期限切れkeyを
消すことができるが、そういった処理自体が重いので、乱用するのはやめた方が良い。


何がアブナイの? (バージョン編)

まず最新のStableなVerに追従しよう。

Redisは(他のDBと比べると)歴史が浅いので、速度改善やバグ修正の余地は
たくさんあって、毎度のリリースでそれなりに修正されている。
古いVerは爆弾を抱えていることもあるので、理由もなく使い続けるのはやめよう。

ログとかも、新しいVerの方が色々吐いてくれたりするので、
問題発生時の原因特定がしやすい。

古いVerの問題は、この辺に書かれてる。

かなりヘビーに酷使したりしないと起こらない問題とかもあるので、
ちゃんと動いてると思って使い続けていたら
ある日いきなり爆発、なんてこともあるかもしれない。

何がアブナイの?(設定編)

Redisは割とテキトーな設定でもそれなりに動いてくれたりするが、
ちゃんと意味を把握して使わなければ、あとで痛い目に遭うかもしれない。

maxmemoryについて

最大メモリ・・・というと、ここに設定した以上のメモリは、
Redisは消費しないってことかな。


…というのは間違いで、Evict(押し出し)を開始する閾値がこの値となっている。
Evictは既存の消せそうなkeyを消して、新しくキャッシュできる領域を確保する
Redisの処理のこと。どんなkeyをEvictするかは、おおよその条件を設定可能。

だから先に結論。Redisはmaxmemoryを超える量のメモリを「消費する」

Redisのメモリ消費量を厳密に制限することはできない
…という前提をまずは認識すべき。

まぁこういう設定名だと、できそうに見えちゃうんだけどね…。

後述するoutput bufferの問題とかも絡んでくるんだけど、
気付いたらmaxmemoryの2~3倍以上も実メモリを消費していた、ということも。

じゃあメモリを節約するには値を小さくすればいいのか?
どれだけEvictが発生し得るシステムなのか次第ではあるけれど、
状況によってはそれは有効だったり、あるいは逆効果であることも。

何事もバランスが重要なのです、きっと。

saveについて

「save 60 10000」などと書かれている(設定ファイルに)。
これは10000回のkeyの書き換えがあったら、60秒後にディスクに保存するよ、の意味。
クライアントから明示的に実行するには、BGSAVEというコマンドを送り付ければOK。

このsaveなんだけど、keyの数が非常に多くなった場合、意外と時間が掛かる。
そして、CPUリソースをけっこう消費する。

saveは別プロセスを立ち上げて、バックグラウンドで実行される。
同時に複数のsaveプロセスが立ち上がることはないけど、後述のoutput buffer問題などで
メモリが枯渇すると、プロセス作成が失敗することもある。

BGSAVEはRedisの特徴的な機能の1つであり、非常に重要な機能ではあるけど、
システムによっては、あまり重要ではないかもしれない。
思い切って、save間隔を長くしてみたりすると負荷の低減に有効かも。

client-output-buffer-limitについて

設定の中でもとりわけ注意しなければいけない項目
ただし、この項目はVer2.6以上で設定可能。


まずoutput-bufferってなんやねん、という話から。

output-bufferはRedisがメモリ上に確保するバッファの一種。
レプリケーション環境で運用してる際、slaveはmasterのコピーであるので、
keyの削除時には「このkey消しといてー」とslaveに通知する必要がある。
その情報はoutput bufferにため込んで、まとめて送信される。

あまりに削除するkeyが多かったり、masterが抱えるslaveがたくさんあったりすると、
output bufferの分だけ、メモリを大量に消費してしまう。

そうすると、通信がいつまで経っても終わらなくなって、しかし削除要求は増え続け、
slaveの更新ができないまま、メモリ消費量だけ肥大化する。

こうなると非常に危険で、色々な問題を誘発することがある。
空きメモリが無くなって、HTTPが死んだり、前述のBGSAVEプロセス作成に失敗して
スナップショットが長期間保存されない状況になったりする。


client-output-buffer-limitは所定の容量をbufferが超えたら、
クライアントとの通信を強制的に切断するという設定。
これはこれで問題があるけど、メモリが枯渇するよりはマシかもしれない。

masterから見たクライアントというのは、slaveのことなので、
仮にbufferのピーク容量が500MBで、slaveが4台繋がってたら、
最大で2GBぐらい消費することになる。
実際は詰まり始めたらどんどん消費してしまうので、「最大で」とか「最悪でも」なんて
想定をしても、あまり意味はないかもしれない。


ただ、output-bufferの危険性を認識しておくのは非常に大事。

なんかRedisのメモリ消費量がヤバいんですけどー、という状況に陥ったら
このあたりを見直してみるのが良さそう。

いろいろ書いたけど、要するに

Redisのバージョン上げよう

古いバージョンは爆弾を抱えているので危険!

Redisの邪魔はしないようにしよう

Redisの入ったサーバーで別の重い処理をさせたりするのは極力やめよう。
Redisの性能が落ちる原因になるよ。

1つのRedisに丸投げするのはやめよう

キャッシュの種類や役割に応じて、適切にインスタンスを分けよう。
1つのRedisに何もかも丸投げするのはやめよう。
ある日突然、死んでしまうかもしれません。

時には「おカネ」の力も大事だ

状況に応じてサーバー本体の増設も必要(かもしれない)。