分散ロック

分散システムで複数のプロセスが同じリソースに同時アクセスすることを防ぐ排他制御

分散システム排他制御

分散ロックとは

分散ロック (Distributed Lock) は、分散システムで複数のプロセスが同じリソースに同時アクセスすることを防ぐ排他制御の仕組みである。単一プロセスの mutex と異なり、ネットワーク越しのロックが必要。

なぜ必要か

分散システムでは複数の LambdaECS タスクが同時に同じリソースにアクセスする。単一プロセス内の mutex はプロセスをまたげないため、ネットワーク越しに排他制御を行う分散ロックが必要になる。ロックがなければ、在庫の二重引き当てや決済の重複処理といったデータ不整合が発生する。

❌ ロックなし:
  Lambda A: 在庫を読む (残り 1)
  Lambda B: 在庫を読む (残り 1)
  Lambda A: 在庫を 0 に更新
  Lambda B: 在庫を 0 に更新
  → 2 つの注文が成立 (在庫は 1 つしかない)

✅ ロックあり:
  Lambda A: ロック取得 → 在庫を読む → 更新 → ロック解放
  Lambda B: ロック取得を待つ → 在庫 0 → 注文拒否

DynamoDB での分散ロック

DynamoDB での分散ロックのコード例を示す。

// ロックの取得
async function acquireLock(lockId: string, owner: string, ttlMs: number) {
  const now = Date.now();
  await db.put({
    TableName: 'locks',
    Item: { lockId, owner, expiresAt: now + ttlMs },
    ConditionExpression: 'attribute_not_exists(lockId) OR expiresAt < :now',
    ExpressionAttributeValues: { ':now': now },
  });
}

// ロックの解放
async function releaseLock(lockId: string, owner: string) {
  await db.delete({
    TableName: 'locks',
    Key: { lockId },
    ConditionExpression: '#owner = :owner',
    ExpressionAttributeNames: { '#owner': 'owner' },
    ExpressionAttributeValues: { ':owner': owner },
  });
}

分散ロックの実装方法

分散ロックの実装方法を以下にまとめる。

方法 ツール 特徴
条件付き書き込み DynamoDB サーバーレスに最適
SET NX Redis (ElastiCache) 高速、TTL 付き
Redlock Redis (複数ノード) 高可用性
ZooKeeper Apache ZooKeeper 強い整合性

ロックの注意点

ロックの注意点を以下にまとめる。

問題 対策
デッドロック TTL を設定し、期限切れで自動解放
ロック保持者の障害 TTL で自動解放
クロック問題 フェンシングトークンを使用
パフォーマンス ロックの粒度を細かくする

フェンシングトークン

フェンシングトークンを図で示す。

1. ロック取得時にトークン (単調増加する番号) を発行
2. リソースへの書き込み時にトークンを添付
3. リソース側で古いトークンの書き込みを拒否
→ 期限切れのロックで古い書き込みが成功するのを防ぐ

サーバーレスでの代替手段

Lambda ではロックの代わりに冪等性で対処するのが推奨。DynamoDB の条件付き書き込みによる楽観ロック (バージョン番号)、SQS FIFO によるメッセージの順序保証 + 重複排除、Step Functions によるワークフローでの排他制御が代替手段だ。

分散ロックの理解を深めるには関連書籍が参考になる。

この記事は役に立ちましたか?

関連用語

関連する記事