버전 번호를 이용한 낙관적 잠금
낙관적 잠금은 쓰기 시 충돌을 방지하는 대신 감지하는 전략입니다. 각 항목에는 업데이트마다 증가하는 버전 속성이 포함되어 있습니다. 항목을 업데이트할 때 버전 번호가 애플리케이션이 마지막으로 읽은 값과 일치하는지 확인하는 조건 표현식을 포함합니다. 다른 프로세스가 그 동안 항목을 수정한 경우 조건이 실패하고 DynamoDB가 ConditionalCheckFailedException을 반환합니다.
낙관적 잠금을 사용해야 하는 경우
다음과 같은 경우 낙관적 잠금이 적합합니다.
여러 사용자 또는 프로세스가 동일한 항목을 동시에 업데이트하려고 시도할 수 있지만 충돌이 드물게 발생하는 경우.
실패한 쓰기를 재시도해도 애플리케이션에 비용 부담이 적은 경우.
분산 잠금 관리의 오버헤드와 복잡성을 피하고 싶은 경우.
일반적인 예로는 전자 상거래 인벤토리 업데이트, 협업 편집 플랫폼, 금융 거래 레코드 등이 있습니다.
단점
- 높은 경합으로 오버헤드 재시도
동시성이 높은 환경에서는 충돌 가능성이 증가하여 재시도 횟수와 쓰기 비용이 증가할 수 있습니다.
- 구현 복잡성
항목에 버전 관리를 추가하고 조건부 확인을 처리하면 애플리케이션 로직이 더 복잡해집니다. AWS SDK for Java v2 Enhanced Client는
@DynamoDbVersionAttribute주석을 통해 기본 지원을 제공하며, 주석은 자동으로 버전 번호를 관리합니다.
패턴 설계
각 항목에 버전 속성을 포함합니다. 다음은 간단한 스키마 설계입니다.
파티션 키 - 각 항목의 고유 식별자입니다(예:
ItemId).속성:
ItemId– 항목의 고유 식별자입니다.Version- 항목의 버전 번호를 나타내는 정수입니다.QuantityLeft- 항목의 남은 재고입니다.
항목이 처음 생성되면 Version 속성이 1로 설정됩니다. 매번 업데이트 시 버전 번호가 1씩 증가합니다.
| ItemID(파티션 키) | 버전 | QuantityLeft |
|---|---|---|
| 바나나 | 1 | 10 |
| 사과 | 1 | 5 |
| 오렌지 | 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 Enhanced Client는 @DynamoDbVersionAttribute 주석을 통해 내장된 낙관적 잠금 지원을 제공하여 버전 번호를 자동으로 관리합니다.
조건 표현식에 대한 자세한 내용은 DynamoDB 조건 표현식 CLI 예제 섹션을 참조하세요.