버전 번호를 이용한 낙관적 잠금 - Amazon DynamoDB

버전 번호를 이용한 낙관적 잠금

낙관적 잠금은 업데이트 또는 삭제하려는 클라이언트 측 항목이 Amazon DynamoDB의 항목과 동일하도록 하려는 전략입니다. 이 전략을 사용하면 다른 사용자의 쓰기 작업이 자신의 데이터베이스 쓰기 작업을 덮어쓰지 못하도록 보호하며, 그 반대도 마찬가지입니다.

낙관적 잠금 전략에서는 각 항목마다 버전 번호 역할을 하는 속성이 있습니다. 항목을 테이블에서 가져오면 애플리케이션이 해당 항목의 버전 번호를 기록합니다. 이 항목을 업데이트할 수는 있지만 서버 쪽 버전 번호가 바뀌지 않는 경우에 한합니다. 버전 불일치가 있는 경우 사용자가 항목을 수정하기 전에 다른 사람이 해당 항목을 수정했음을 의미합니다. 더 이상 유효하지 않은 항목 버전이 있으므로 업데이트 시도가 실패합니다. 이러한 경우 항목을 검색한 후 해당 항목 업데이트를 시도하여 재시도합니다. 낙관적 잠금을 통해 다른 사람이 수행한 변경 사항을 잘못 덮어쓰지 않게 됩니다. 또한 다른 사람이 사용자의 변경 사항을 잘못 덮어쓰지 않도록 합니다.

자체적인 낙관적 잠금 전략을 구현할 수 있지만 AWS SDK for Java에서는 @DynamoDBVersionAttribute 주석을 제공합니다. 테이블 매핑 클래스에서 버전 번호를 저장할 속성을 하나 지정하여 이 주석을 사용해 표시하면 됩니다. 그러면 객체를 저장할 때 DynamoDB 테이블의 해당 항목이 버전 번호를 저장하는 속성을 갖게 됩니다. 처음 객체를 저장할 때 DynamoDBMapper가 버전 번호를 할당하고, 이후 항목을 업데이트할 때마다 버전 번호가 일정하게 자동으로 오릅니다. 업데이트 또는 삭제 요청은 클라이언트 측 객체 버전이 DynamoDB 테이블의 해당 항목 버전 번호와 일치해야만 가능합니다.

다음의 경우 ConditionalCheckFailedException이 발생합니다.

  • @DynamoDBVersionAttribute를 이용한 낙관적 잠금을 사용하며, 서버의 버전 값이 클라이언트 측의 값과 다를 경우

  • DynamoDBSaveExpression과 함께 DynamoDBMapper를 사용하여 데이터를 저장하는 동안 고유의 조건부 제약 조건을 지정하며 이러한 제약 조건이 실패한 경우

참고
  • DynamoDB 전역 테이블은 동시 업데이트 간에 "last writer wins" 조정을 사용합니다. 전역 테이블을 사용할 경우 last writer 정책을 우선 적용합니다. 따라서 잠금 전략이 작동하지 않습니다.

  • DynamoDBMapper 트랜잭션 쓰기 작업은 동일한 객체에 대해 @DynamoDBVersionAttribute 주석 및 조건 표현식을 지원하지 않습니다. 트랜잭션 쓰기 내 객체에 @DynamoDBVersionAttribute 주석이 추가되고 조건식도 있는 경우에는 SdkClientException이 발생합니다.

예를 들어, 다음 Java 코드에서는 몇 가지 속성이 있는 CatalogItem 클래스를 정의합니다. Version 속성이 @DynamoDBVersionAttribute 주석으로 표시되어 있습니다.

@DynamoDBTable(tableName="ProductCatalog") public class CatalogItem { private Integer id; private String title; private String ISBN; private Set<String> bookAuthors; private String someProp; private Long version; @DynamoDBHashKey(attributeName="Id") public Integer getId() { return id; } public void setId(Integer Id) { this.id = Id; } @DynamoDBAttribute(attributeName="Title") public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } @DynamoDBAttribute(attributeName="ISBN") public String getISBN() { return ISBN; } public void setISBN(String ISBN) { this.ISBN = ISBN;} @DynamoDBAttribute(attributeName = "Authors") public Set<String> getBookAuthors() { return bookAuthors; } public void setBookAuthors(Set<String> bookAuthors) { this.bookAuthors = bookAuthors; } @DynamoDBIgnore public String getSomeProp() { return someProp;} public void setSomeProp(String someProp) {this.someProp = someProp;} @DynamoDBVersionAttribute public Long getVersion() { return version; } public void setVersion(Long version) { this.version = version;} }

@DynamoDBVersionAttribute 주석을 Long이나 Integer 같이 null이 허용된 형식을 제공하는 기본 래퍼 클래스에서 제공하는 null 형식에 적용할 수 있습니다.

낙관적 잠금 전략은 이러한 DynamoDBMapper 메서드에 아래와 같은 영향을 끼칩니다.

  • save - 새 항목의 경우 DynamoDBMapper는 초기 버전 번호로 1을 할당합니다. 항목을 검색하고, 해당 속성 중 하나 이상을 업데이트하고, 변경 사항 저장을 시도하면 클라이언트 측 버전 번호와 서버 측 버전 번호가 일치하는 경우에만 저장 작업이 성공합니다. 그런 다음 DynamoDBMapper가 버전 번호를 자동으로 일정하게 올립니다.

  • delete - delete 메서드가 객체를 파라미터로 사용하고 DynamoDBMapper가 항목을 삭제하기 전에 버전을 검사합니다. 요청 시 DynamoDBMapperConfig.SaveBehavior.CLOBBER를 지정하면 버전 검사는 비활성화됩니다.

    DynamoDBMapper에서 낙관적 잠금을 내부 구현할 경우에는 DynamoDB가 제공하는 조건부 업데이트와 조건부 삭제 지원을 사용합니다.

  • transactionWrite

    • Put - 새 항목의 경우 DynamoDBMapper는 초기 버전 번호로 1을 할당합니다. 이후 항목을 가져와 속성을 하나 이상 업데이트한 후 변경 사항을 저장하려고 해도 클라이언트 측과 서버 쪽의 버전 번호가 일치해야만 넣기 작업이 가능합니다. 그런 다음 DynamoDBMapper가 버전 번호를 자동으로 일정하게 올립니다.

    • Update - 새 항목의 경우 DynamoDBMapper는 초기 버전 번호로 1을 할당합니다. 이후 항목을 가져와 속성을 하나 이상 업데이트한 후 변경 사항을 저장하려고 해도 클라이언트 측과 서버 쪽의 버전 번호가 일치해야만 업데이트 작업이 가능합니다. 그런 다음 DynamoDBMapper가 버전 번호를 자동으로 일정하게 올립니다.

    • Delete - DynamoDBMapper는 항목을 삭제하기 전에 버전을 확인합니다. 클라이언트 측과 서버 측 버전 번호가 일치할 경우에만 삭제 작업이 가능합니다.

    • ConditionCheck - @DynamoDBVersionAttribute 주석은 ConditionCheck 작업에 지원되지 않습니다. ConditionCheck 항목에 @DynamoDBVersionAttribute 주석이 추가될 때 SdkClientException이 발생합니다.

낙관적 잠금 비활성화

낙관적 잠금은 DynamoDBMapperConfig.SaveBehavior 열거 값을 UPDATE에서 CLOBBER로 변경하면 비활성화할 수 있습니다. 그런 다음 버전 검사를 제외한 DynamoDBMapperConfig 인스턴스를 생성하여 모든 요청에 사용하면 됩니다. DynamoDBMapperConfig.SaveBehavior와 그 밖에 옵션으로 제공되는 DynamoDBMapper 파라미터에 대한 자세한 내용은 DynamoDBMapper의 구성 설정(선택 사항) 단원을 참조하세요.

또한 잠금 기능을 특정 작업에만 설정할 수도 있습니다. 예를 들어 다음은 DynamoDBMapper를 사용하여 카탈로그 항목을 저장하는 Java 코드 조각입니다. 이 조각을 보면 옵션인 DynamoDBMapperConfig 파라미터를 save 메서드에 추가하여 DynamoDBMapperConfig.SaveBehavior를 지정하고 있습니다.

참고

transactionWrite 메서드는 DynamoDBMapperConfig.SaveBehavior 구성을 지원하지 않습니다. transactionWrite의 낙관적 잠금 비활성화는 지원되지 않습니다.

DynamoDBMapper mapper = new DynamoDBMapper(client); // Load a catalog item. CatalogItem item = mapper.load(CatalogItem.class, 101); item.setTitle("This is a new title for the item"); ... // Save the item. mapper.save(item, new DynamoDBMapperConfig( DynamoDBMapperConfig.SaveBehavior.CLOBBER));