分散ロックの仕組み ― 複数サーバーで「早い者勝ち」を安全に実現する方法


分散ロック: 複数ノードからのリソース排他制御 App Node 1 ロック取得! App Node 2 待機中... App Node 3 待機中... SETNX OK FAIL FAIL Redis Lock key: resource_1 TTL: 30s 安全にアクセス 共有リソース (DB / ファイル) フェンシングトークンで安全性を強化 ロック取得時にトークン#34を発行 > リソース側で番号を検証 古い番号(#33以前)の書き込みは拒否 → ゾンビロック対策 Redis: SETNX + TTL ZooKeeper: 一時ノード etcd: リースベース
分散ロックのイメージ: 1つのノードだけがリソースにアクセスできる
ひよこ ひよこ

分散ロックって何なの?普通のロックと違うの?

ペンギン先生 ペンギン先生

普通のロックは1台のサーバーの中だけで使うものだよ。たとえばプログラムの中でmutexを使えば、同じプロセス内のスレッド同士が同時にデータを書き換えるのを防げる。でも複数のサーバーネットワーク越しに同じデータベースやファイルを触る場合、1台の中のロックじゃ意味がないんだ

ひよこ ひよこ

なるほど…サーバーが3台あったら、3台とも「自分がロックを取った!」って思っちゃうかもしれないってこと?

ペンギン先生 ペンギン先生

そのとおり。だから全員が見える場所にロックを置く必要があるんだ。Redisのような外部サーバーに「このリソースは今○○が使用中」という情報を書き込んで、他のサーバーはそれを見て待つ仕組みだよ

ひよこ ひよこ

Redisでロックを取るって、具体的にはどうやるの?

ペンギン先生 ペンギン先生

基本はSETNXコマンドだよ。「このキーが存在しなければセットする」という操作で、成功した人だけがロックを取得できる。さらにTTL(有効期限)を付けて、万が一ロックを取ったサーバーがクラッシュしても一定時間後に自動解放されるようにするんだ

ひよこ ひよこ

でもRedis自体が落ちたらどうなるの?

ペンギン先生 ペンギン先生

いい質問だね。それを解決するのがRedlockアルゴリズムだよ。5台のRedisノードに同時にロックを取りに行って、過半数(3台以上)で成功したら「ロック取得成功」とみなす。1〜2台が落ちてもロックの仕組みは維持されるんだ

ひよこ ひよこ

Redis以外でも分散ロックってできるの?

ペンギン先生 ペンギン先生

ZooKeeperやetcdも定番だよ。ZooKeeperはエフェメラルノードという仕組みを使う。ロックを取ったクライアントの接続が切れると、ノードが自動的に消えてロックが解放されるんだ。etcdはリースという有効期限付きのキーを使って、同じようなことを実現しているよ

ひよこ ひよこ

有効期限が切れちゃったのに、まだ処理中だったらどうなるの?

ペンギン先生 ペンギン先生

それが分散ロック最大の難しさだね。ロックの期限が切れて別のサーバーが新しいロックを取得したのに、古いサーバーがまだ書き込みを続けてしまう「ゾンビロック」問題だよ。対策としてフェンシングトークンを使う方法がある。ロック取得時に単調増加する番号をもらって、リソース側でその番号が古くないかチェックするんだ

ひよこ ひよこ

なんだか完璧な分散ロックって難しそうなんだね…

ペンギン先生 ペンギン先生

実はRedlockアルゴリズムに対して、分散システムの専門家マーティン・クレップマンが「タイミングの仮定に依存しすぎていて安全ではない」と批判論文を出したことがあるんだ。Redisの作者アンティレスが反論して、業界で大きな議論になったよ。完璧な分散ロックの実現は理論的にも難しい問題なんだ

ひよこ ひよこ

じゃあ大手の会社はどうしてるの?

ペンギン先生 ペンギン先生

Googleは社内でChubbyというロックサービスを作って使っているよ。BigTableやGFSなどの基盤システムがChubbyに依存しているんだ。また、PostgreSQLにはアドバイザリーロックという機能があって、アプリケーションが自由に使える軽量なロック機構を提供しているよ

ひよこ ひよこ

用途に合わせて使い分けるのが大事なんだね!

ペンギン先生 ペンギン先生

そうだね。「絶対にデータが壊れてはいけない」場面ではZooKeeperやetcdのような強い整合性を持つシステムを使い、「多少の重複実行は許容できる」場面ではRedisの軽量ロックで十分だったりする。分散ロックは万能薬じゃないから、要件に合わせて選ぶのがエンジニアの腕の見せどころだよ