バージョン番号を使用した楽観的ロック
楽観的ロックは、競合を回避するのではなく、書き込み時に競合を検出する戦略です。各項目には、更新ごとに増分するバージョン属性が含まれています。項目を更新するときは、バージョン番号がアプリケーションが最後に読み取った値と一致するかどうかをチェックする条件式を含めます。その間に別のプロセスが項目を変更した場合、条件は失敗し、DynamoDB は ConditionalCheckFailedException を返します。
どのような場合に楽観的ロックを使用するか
楽観的ロックは、次の場合に適しています。
複数のユーザーまたはプロセスが同じ項目を更新する可能性がありますが、競合はまれです。
失敗した書き込みを再試行することは、アプリケーションにとって安価です。
分散ロックの管理に伴うオーバーヘッドや複雑さを回避したい。
一般的な例としては、e コマース在庫の更新、共同編集プラットフォーム、金融取引レコードなどがあります。
トレードオフ
- 競合率が高い場合の再試行のオーバーヘッド
高同時実行性環境では、競合の可能性が高くなり、再試行や書き込みコストの増大につながる可能性があります。
- 実装の複雑さ
項目にバージョン管理を追加し、条件付きチェックを処理すると、アプリケーションロジックが複雑になります。AWS SDK for Java v2 拡張クライアントは、自動的にバージョン番号を管理する
@DynamoDbVersionAttribute注釈を通じて組み込みサポートを提供します。
パターンの設計
各項目にバージョン属性を含めます。シンプルなスキーマ設計は次のとおりです。
パーティションキー – 各項目の一意の識別子 (
ItemIdなど)。属性:
ItemId– ジョブの一意の識別子Version– 項目のバージョン番号を表す整数QuantityLeft– 項目の残りのインベントリ
項目が最初に作成されると、Version 属性は 1 に設定されます。更新される都度、バージョン番号は 1 ずつ増分します。
| ItemID (パーティションキー) | バージョン | QuantityLeft |
|---|---|---|
| Bananas | 1 | 10 |
| Apples | 1 | 5 |
| Oranges | 1 | 7 |
実装
楽観的ロックを実装するには、次の手順に従います。
-
項目の最新バージョンを読み取ります。
def get_item(item_id): response = table.get_item(Key={'ItemID': item_id}) return response['Item'] item = get_item('Bananas') current_version = item['Version'] -
バージョン番号をチェックする条件式を使用して項目を更新します。
def update_item(item_id, qty_bought, current_version): try: response = table.update_item( Key={'ItemID': item_id}, UpdateExpression="SET QuantityLeft = QuantityLeft - :qty, Version = :new_v", ConditionExpression="Version = :expected_v", ExpressionAttributeValues={ ':qty': qty_bought, ':new_v': current_version + 1, ':expected_v': current_version }, ReturnValues="UPDATED_NEW" ) return response except ClientError as e: if e.response['Error']['Code'] == 'ConditionalCheckFailedException': print("Version conflict: another process updated this item.") raise -
新しい読み取りを再試行して競合を処理します。
各再試行には追加の読み取りが必要なため、再試行の合計数を制限します。
def update_with_retry(item_id, qty_bought, max_retries=3): for attempt in range(max_retries): item = get_item(item_id) try: return update_item(item_id, qty_bought, item['Version']) except ClientError as e: if e.response['Error']['Code'] != 'ConditionalCheckFailedException': raise print(f"Retry {attempt + 1}/{max_retries}") raise Exception("Update failed after maximum retries.")
Java アプリケーションの場合、AWS SDK for Java v2 拡張クライアントは、@DynamoDbVersionAttribute 注釈を通じて組み込みの楽観的ロックサポートを提供し、自動的にバージョン番号を管理します。
条件式の詳細については、「DynamoDB 条件式 CLI の例」を参照してください。