

# バージョン番号を使用した楽観的ロック
<a name="BestPractices_OptimisticLocking"></a>

楽観的ロックは、競合を回避するのではなく、書き込み時に競合を検出する戦略です。各項目には、更新ごとに増分するバージョン属性が含まれています。項目を更新するときは、バージョン番号がアプリケーションが最後に読み取った値と一致するかどうかをチェックする[条件式](Expressions.ConditionExpressions.md)を含めます。その間に別のプロセスが項目を変更した場合、条件は失敗し、DynamoDB は `ConditionalCheckFailedException` を返します。

## どのような場合に楽観的ロックを使用するか
<a name="BestPractices_OptimisticLocking_WhenToUse"></a>

楽観的ロックは、次の場合に適しています。
+ 複数のユーザーまたはプロセスが同じ項目を更新する可能性がありますが、競合はまれです。
+ 失敗した書き込みを再試行することは、アプリケーションにとって安価です。
+ 分散ロックの管理に伴うオーバーヘッドや複雑さを回避したい。

一般的な例としては、e コマース在庫の更新、共同編集プラットフォーム、金融取引レコードなどがあります。

## トレードオフ
<a name="BestPractices_OptimisticLocking_Tradeoffs"></a>

**競合率が高い場合の再試行のオーバーヘッド**  
高同時実行性環境では、競合の可能性が高くなり、再試行や書き込みコストの増大につながる可能性があります。

**実装の複雑さ**  
項目にバージョン管理を追加し、条件付きチェックを処理すると、アプリケーションロジックが複雑になります。AWS SDK for Java v2 拡張クライアントは、自動的にバージョン番号を管理する [https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE) 注釈を通じて組み込みサポートを提供します。

## パターンの設計
<a name="BestPractices_OptimisticLocking_PatternDesign"></a>

各項目にバージョン属性を含めます。シンプルなスキーマ設計は次のとおりです。
+ パーティションキー – 各項目の一意の識別子 (`ItemId` など)。
+ 属性:
  + `ItemId` – ジョブの一意の識別子
  + `Version` – 項目のバージョン番号を表す整数
  + `QuantityLeft` – 項目の残りのインベントリ

項目が最初に作成されると、`Version` 属性は 1 に設定されます。更新される都度、バージョン番号は 1 ずつ増分します。


| ItemID (パーティションキー) | バージョン | QuantityLeft | 
| --- | --- | --- | 
| Bananas | 1 | 10 | 
| Apples | 1 | 5 | 
| Oranges | 1 | 7 | 

## 実装
<a name="BestPractices_OptimisticLocking_Implementation"></a>

楽観的ロックを実装するには、次の手順に従います。

1. 項目の最新バージョンを読み取ります。

   ```
   def get_item(item_id):
       response = table.get_item(Key={'ItemID': item_id})
       return response['Item']
   
   item = get_item('Bananas')
   current_version = item['Version']
   ```

1. バージョン番号をチェックする条件式を使用して項目を更新します。

   ```
   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
   ```

1. 新しい読み取りを再試行して競合を処理します。

   各再試行には追加の読み取りが必要なため、再試行の合計数を制限します。

   ```
   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 拡張クライアントは、[https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE) 注釈を通じて組み込みの楽観的ロックサポートを提供し、自動的にバージョン番号を管理します。

条件式の詳細については、「[DynamoDB 条件式 CLI の例](Expressions.ConditionExpressions.md)」を参照してください。