

# 버전 번호를 이용한 낙관적 잠금
<a name="BestPractices_OptimisticLocking"></a>

낙관적 잠금은 쓰기 시 충돌을 방지하는 대신 감지하는 전략입니다. 각 항목에는 업데이트마다 증가하는 버전 속성이 포함되어 있습니다. 항목을 업데이트할 때 버전 번호가 애플리케이션이 마지막으로 읽은 값과 일치하는지 확인하는 [조건 표현식](Expressions.ConditionExpressions.md)을 포함합니다. 다른 프로세스가 그 동안 항목을 수정한 경우 조건이 실패하고 DynamoDB가 `ConditionalCheckFailedException`을 반환합니다.

## 낙관적 잠금을 사용해야 하는 경우
<a name="BestPractices_OptimisticLocking_WhenToUse"></a>

다음과 같은 경우 낙관적 잠금이 적합합니다.
+ 여러 사용자 또는 프로세스가 동일한 항목을 동시에 업데이트하려고 시도할 수 있지만 충돌이 드물게 발생하는 경우.
+ 실패한 쓰기를 재시도해도 애플리케이션에 비용 부담이 적은 경우.
+ 분산 잠금 관리의 오버헤드와 복잡성을 피하고 싶은 경우.

일반적인 예로는 전자 상거래 인벤토리 업데이트, 협업 편집 플랫폼, 금융 거래 레코드 등이 있습니다.

## 단점
<a name="BestPractices_OptimisticLocking_Tradeoffs"></a>

**높은 경합으로 오버헤드 재시도**  
동시성이 높은 환경에서는 충돌 가능성이 증가하여 재시도 횟수와 쓰기 비용이 증가할 수 있습니다.

**구현 복잡성**  
항목에 버전 관리를 추가하고 조건부 확인을 처리하면 애플리케이션 로직이 더 복잡해집니다. AWS SDK for Java v2 Enhanced Client는 [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 | 
| --- | --- | --- | 
| 바나나 | 1 | 10 | 
| 사과 | 1 | 5 | 
| 오렌지 | 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 Enhanced Client는 [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) 섹션을 참조하세요.