DynamoDB トランザクションによる悲観的ロック
DynamoDB トランザクションは、グループ化されたオペレーションにオールオアナッシングアプローチを提供します。TransactWriteItems を使用すると、DynamoDB はトランザクション内のすべての項目をモニタリングします。トランザクション中に別のオペレーションによって項目が変更された場合、トランザクション全体がキャンセルされ、DynamoDB は TransactionCanceledException を返します。この動作は、競合する同時変更が事後に検出されるのではなく防止されるため、悲観的な同時実行制御の一形式を提供します。
どのような場合にロックにトランザクションを使用するか
トランザクションは、次の場合に適しています。
同じテーブル内またはテーブル間で、複数の項目をアトミックに更新する必要があります。
ビジネスロジックでは、オールオアナッシングセマンティクス (すべての変更が成功するか、まったく適用されないかのいずれか) が必要です。
一般的な例としては、アカウント間での資金の送金、在庫テーブルと注文テーブルの両方を更新する注文の配置、ゲーム内のプレイヤー間でのアイテムの交換などがあります。
トレードオフ
- 書き込みコストの増加
最大 1 KB の項目の場合、トランザクションは項目ごとに 2 WCU (1 つは準備用、もう 1 つはコミット用) を消費しますが、標準書き込みの場合は 1 WCU です。
- 項目の制限
1 つのトランザクションには、1 つ以上のテーブルで最大 100 個のアクションを含めることができます。
- 競合の機密性
トランザクション内の項目が別のオペレーションによって変更されると、トランザクション全体が失敗します。競合率が高いシナリオでは、頻繁にキャンセルが発生する可能性があります。
実装
次の例では、TransactWriteItems を使用して 2 つの項目間で在庫をアトミックに転送します。トランザクション中に別のプロセスがいずれかの項目を変更すると、オペレーション全体がロールバックされます。
import boto3 client = boto3.client('dynamodb') def transfer_inventory(source_id, target_id, quantity): try: client.transact_write_items( TransactItems=[ { 'Update': { 'TableName': 'Inventory', 'Key': {'ItemID': {'S': source_id}}, 'UpdateExpression': 'SET QuantityLeft = QuantityLeft - :qty', 'ConditionExpression': 'QuantityLeft >= :qty', 'ExpressionAttributeValues': { ':qty': {'N': str(quantity)} } } }, { 'Update': { 'TableName': 'Inventory', 'Key': {'ItemID': {'S': target_id}}, 'UpdateExpression': 'SET QuantityLeft = QuantityLeft + :qty', 'ExpressionAttributeValues': { ':qty': {'N': str(quantity)} } } } ] ) return True except client.exceptions.TransactionCanceledException as e: print(f"Transaction canceled: {e}") return False
この例では、条件式は十分な在庫が存在することを確認しますが、バージョン属性は必要ありません。DynamoDB は、準備フェーズとコミットフェーズの間に別のオペレーションによってトランザクション内の項目が変更された場合、トランザクションを自動的にキャンセルします。これにより、悲観的な同時実行制御が実現します。競合する同時変更はトランザクション自体によって防止されます。
注記
バージョンチェックを追加の条件式として追加することで、トランザクションと楽観的ロックを組み合わせることができます。これにより、追加の保護レイヤーが提供されますが、トランザクションが競合を検出するためには必須ではありません。
詳細については、「DynamoDB トランザクションで複雑なワークフローを管理する」を参照してください。