Does dynamodb supports atomic counter on SET operation if value is not exists?

1.5k Views Asked by At

I have a use case where I need to store how many people view the blog post in DynamoDB.

Lets' say I have a table like below

blogId Total Views.
1 100

Now when a user views a new blog with ID 2 I have to store that in the table. I can do the following to achieve that:

isBlogExists = dynamodbMapper.get(BlogViews,{blogId: 2})
if (isBlogExists){
   dynamodbMapper.set("totalViews", new MathematicalExpression("totalViews", "+", 1))
} else{
   dynamodbMapper.set("totalViews", 1)
}

Unfortunately it ends up in race condition so I planned to use the SET operation like this

dynamodbMapper.set("totalViews", new MathematicalExpression("totalViews", "+", 1))

Since this item doesn't have the totalViews attribute, I am getting a error like

No item with the key found in Table.

So does DynamoDB doesn't support incrementing numeric values on key which doesn't exist?

I could use the ADD operation but DynamoDB recommends to use SET over ADD (source).

1

There are 1 best solutions below

0
On

DynamoDB offers the straightforward way of using ADD in the UpdateExpression, but AWS generally recommends going with SET instead:

Note

In general, we recommend using SET rather than ADD.

I don't see why and they don't elaborate on that. If you want to follow their guidance, here is what you can do.

The straightforward SET attr = attr + 1 falls short in some cases because it requires the attribute to exist already. The ADD expression assumes a default of 0 if that's not the case, SET doesn't.

Fortunately, we can create a default for non-existent attributes using the if_not_exists function. Here's an example of how to use it. It assumes a table called pk-only with only a partition key called PK of type string.

import boto3

TABLE_NAME = "pk-only"
TABLE_RESOURCE = boto3.resource("dynamodb").Table(TABLE_NAME)

def main():
    
    TABLE_RESOURCE.update_item(
        Key={"PK": "some_item"},
        UpdateExpression="SET #att = if_not_exists(#att, :start_value) + :inc",
        ExpressionAttributeNames={
            "#att": "counter"
        },
        ExpressionAttributeValues={
            ":inc": 1,
            ":start_value": 0
        }
    )

if __name__ == "__main__":
    main()

(This is an example in Python, but you should be able to adapt it to your Javascript code quite easily)