Dynamodb ConditionalCheckFailedException on read / update operation - java sdk

24,093

Solution 1

Dynamodb has this concept of Optimistic Locking, is strategy to ensure that the client-side item that you are updating (or deleting) is the same as the item in DynamoDB.

  1. First save:

If it's a first update, you have to follow this format

DynamodbSaveExpression saveExpression = 
       new DynamodbSaveExpression()
       .withExpectedEntry("ItemId", new ExpectedAttributeValue().withExists(false)
       .withConditionalOperator(AND)
       .withExpectedEntry("ItemName", new ExpectedAttributeValue().withExists(false));
// saving the item to dynamodb 
dynamodbmapper.save(itemToSave, saveExpression);

Explanation: We are telling that itemToSave should be saved only when both "ItemId", "ItemValue" combination is not there in dynamodb.

  1. Update to an existing item:

For an update to an existing item in dynamodb, you have to follow this format

// Step1: Do a GET
ExampleItem itemInDynamo = dynamoDBMapper.load(ExampleItem.class, "item_id_value", "item_name_value");
// Step2: Get the version in Dynamo
Long versionInDynamo = itemInDynamo.getVersion();
DynamodbSaveExpression saveExpression = 
       new DynamodbSaveExpression()
       .withExpectedEntry("version", 
       new ExpectedAttributeValue(new AttributeValue().withN(versionInDynamodb.toString())));
// Step3: Update the item
dynamodbmapper.save(itemToSave, saveExpression);

Save will only succeed if the version with which you are saving is the same in the Dynamodb, if not save will fail with ConditionalCheckFailedException then you have retry from step1. This is Read first then Write.

Solution 2

Looks like you need to set the version when creating the item class. It's expecting 1 but found null. The versionedAttribute is expecting a value, and it seems when the item is created for the first time it will expect 1.

If you update this item in the future, it should take care of incrementing the version internally.

Solution 3

Usualy this occurs when your keys are same for more then one element in dynamodb. For example if you have Folder with id as hashKey and folderName as rangeKey, You wont save the Folder again where your hash(hashkey+rangeKey) would be same.

Share:
24,093
Shamik
Author by

Shamik

Updated on July 30, 2022

Comments

  • Shamik
    Shamik over 1 year

    I'm new to DynamoDB and trying out a sample scenario using Transaction support. I'm using the same entity provided in dynamodb-transaction library. Only difference is I've added a range key with the hash key. Here's the table definition:

    • ItemId --> hash key , string
    • ItemName --> range key, string

    @DynamoDBTable(tableName = "Item")
    public static class ExampleItem {
        private String itemId;
        private String value;
        private String itemName;
        private Long version;
        @DynamoDBHashKey(attributeName = "ItemId")
        public String getItemId() {
            return itemId;
        }
        public void setItemId(String itemId) {
            this.itemId = itemId;
        }
        @DynamoDBRangeKey(attributeName="ItemName")
        public String getItemName() {
            return itemName;
        }
        public void setItemName(String itemName) {
            this.itemName = itemName;
        }
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
            this.value = value;
        }
        @DynamoDBVersionAttribute
        public Long getVersion() {
            return version;
        }
        public void setVersion(Long version) {
            this.version = version;
        }
    }
    

    As you can see, I'm using the version attribute as well. Now, I'm trying to execute a simple create or update scenario using transaction/dbmapper. Here's the code snippet.

    Transaction t1 = txManager.newTransaction();
    ExampleItem keyItem = new ExampleItem();
    keyItem.setItemId("Item1");
    keyItem.setItemName("USA");
    ExampleItem item = t1.load(keyItem);
    if (item != null) {
        item.setValue("Magenta");
        item.setItemName("UK");
        t1.save(item);
    } else {
        item = new ExampleItem();
        item.setItemId(keyItem.getItemId());
        item.setItemName("USA");
        item.setValue("Violet");
        t1.save(item);
    }
    t1.commit();
    t1.delete();
    

    I'm able to add the record without any issue, but I'm having a problem when I'm trying to read the record and update any attribute. I'm getting the following exception:

    Uncaught exception:com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException: Status Code: 0, AWS Service: null, AWS Request ID: null, AWS Error Code: null, AWS Error Message: Item {ItemId={S: Item1,}, ItemName={S: UK,}} had unexpected attributes: Status Code: 0, AWS Service: null, AWS Request ID: null, AWS Error Code: null, AWS Error Message: expected attribute(s) {version={Value: {N: 1,},Exists: true}} but found null
    com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException: Status Code: 0, AWS Service: null, AWS Request ID: null, AWS Error Code: null, AWS Error Message: Item {ItemId={S: Item1,}, ItemName={S: UK,}} had unexpected attributes: Status Code: 0, AWS Service: null, AWS Request ID: null, AWS Error Code: null, AWS Error Message: expected attribute(s) {version={Value: {N: 1,},Exists: true}} but found null
    

    Looks like it has something to do with version, but not sure where am I going wrong. Any pointers will be appreciated.

    -Thanks

    Shamik