おはようございます。 若林(@nqounet)です。
memcached を使って排他制御したい事ってありますよね? ないですか?
私は、つい最近そういうことがあったので KeyedMutex::Memcached
を使ってみました。
あらすじ
掲示板時代からそうですが、更新処理が複数同時に実行された場合、排他制御(ロック)をしていないとデータが壊れます。
ファイルのロックについていろいろ考えていた時期もありました。
データベースを使っている場合、排他制御はあまり意識しなくても壊れなかった(むしろロックされすぎてエラーになったり)のですが、ある時を境によく壊れるようになりました。
セッション管理に memcached を使っていたので、それを使って排他制御ができないかな〜と CPAN を見ていたところ、使えそうなモジュールがありました。
- IPC::Lock::Memcached - memcached based locking - metacpan.org
- KeyedMutex::Memcached - An interprocess keyed mutex using memcached - metacpan.org
その中で IPC::Lock::Memcached
は少し古いのと、インストールの失敗数がそこそこあるので回避し、KeyedMutex::Memcached
を使うことにしました。
KeyedMutex::Memcached を使ってみる
使い方を見て、ほぼそのまま使えました。
|
|
new するときに Cache::Memcached::Fast
のインスタンスを渡せるので、既に利用しているインスタンスを再利用することができるのが良いですね。
lock
の第二引数は use_raii
のフラグですが、これを使うと $lock
がなくなった時にロックをはずしてくれます。
よくわからない場合は、黙って上のコードのように書くと良いと思います。
(use_raii
をしなかった場合は、if文の中で$mutex->release
としてロックをはずす必要があるので、上のコードのように書いておくのが間違いないでしょう。この機構は Scope::Guard
を利用しているのですが、こういうのは便利だなと思いました)
本来の使い方としては、上記のようにキャッシュをセットする時のロックとして使用するのが良いのでしょうが、重い処理を何度も動作しないように使ったりもできるようです。
システムへの組み込み方
今回は、更新するためのオブジェクトの中に、ロックを取得する機能をつけて、そこで使用しました。
イメージは以下の様な感じです。
|
|
ロックをかけたいところで if (my $mutex = $entry->create_mutex) { ... }
という感じで使えるかなと。
IDがない場合はロック不要(insertするのでDBが適切に処理してくれる)とみなして何もせずに真を返しています。
RAIIを使うと、明示的にロックをはずす必要がなくなるので、ロックの実体がなくてもロックを取得したものとして動作させることができます。
仕組み
ソースを見ると、 memcached
の add
が成功したかどうかでロックが取得できたかどうかを判定しています。
排他制御を確実に行うには、「ロック操作ができるかどうかの判定」と「ロック操作」が同時にできる必要があります。add
はそれが可能なのです。
ファイルシステムを使ったロックの場合、mkdir
やrename
を使うと思いますが、それにはちゃんとした理由があるのでした。
実は車輪の再発明しかけた
実は、CPANで探す前に簡単に書いてみたのです。
とりあえず排他制御として使えそうな物(ただしキーの指定ができない)ができたのですが、そこまで書いた時点で「さすがに誰か書いてるんじゃないか」と思って調べたら、わりとすぐに見つかったと。
まとめ
CPAN は偉大です。 最初から探しておけばよかった。