分散ロック
分散システムで複数のプロセスが同じリソースに同時アクセスすることを防ぐ排他制御
分散システム排他制御
分散ロックとは
分散ロック (Distributed Lock) は、分散システムで複数のプロセスが同じリソースに同時アクセスすることを防ぐ排他制御の仕組みである。単一プロセスの mutex と異なり、ネットワーク越しのロックが必要。
なぜ必要か
❌ ロックなし:
Lambda A: 在庫を読む (残り 1)
Lambda B: 在庫を読む (残り 1)
Lambda A: 在庫を 0 に更新
Lambda B: 在庫を 0 に更新
→ 2 つの注文が成立 (在庫は 1 つしかない)
✅ ロックあり:
Lambda A: ロック取得 → 在庫を読む → 更新 → ロック解放
Lambda B: ロック取得を待つ → 在庫 0 → 注文拒否
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 によるワークフローでの排他制御が代替手段だ。
分散ロックの理解を深めるには関連書籍が参考になる。