Search code examples
pythonamazon-web-servicesamazon-dynamodbboto

Incrementing a counter in DynamoDB when value to be updated is in a map field


I have a lambda function that needs to retrieve an item from DynamoDB and update the counter of that item. But..

The DynamoDB table is structured as:

id: int
options: map
    some_option: 0
    some_other_option: 0

I need to first retrieve the item of the table that has a certain id and a certain option listed as a key in the options.

Then I want to increment that counter by some value.

Here is what I have so far:

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('options')

response = None
try:
    response = table.get_item(Key={'id': id})
except ClientError as e:
    print(e.response['Error']['Message'])

option = response.get('Item', None)
if option:
    option['options'][some_option] = int(option['options'][some_option]) + some_value
    # how to update item in DynamoDB now?

My issues is how to update the record now and more importantly will such solution cause data races? Could 2 simultaneous lambda calls that try to update the same item at the same option cause data races? If so what's the way to solve this?

Any pointers/help is appreciated.


Solution

  • Ok, I found the answer:

    All I need is:

    response = table.update_item(
        Key={
            'id': my_id,
        }, 
        UpdateExpression='SET options.#s = options.#s + :val',  
        ExpressionAttributeNames={
            "#s": my_option
        },  
        ExpressionAttributeValues={
            ':val': Decimal(some_value)
        },
        ReturnValues="UPDATED_NEW"
    )
    

    This is inspired from Step 3.4: Increment an Atomic Counter which provides an atomic approach to increment values. According to the documentation:

    DynamoDB supports atomic counters, which use the update_item method to increment or decrement the value of an existing attribute without interfering with other write requests. (All write requests are applied in the order in which they are received.)