DynamoDB: An Introduction to Optimistic Locking

Concurrency in distributed systems is a common challenge. To address this, we use a technique called Optimistic Locking.

a year ago   •   3 min read

By Maik Wiesmüller
Photo by Campaign Creators / Unsplash
Table of contents

Concurrency in distributed systems is a common challenge. Imagine you're editing a document, and at the same time, another colleague of yours is making changes to the same document. Without a proper mechanism in place, there's a risk that one of your updates could be lost. To address this issue in systems like DynamoDB, we use a technique called Optimistic Locking.

What is Optimistic Locking?

Optimistic Locking is a strategy that ensures data integrity in concurrent systems. Instead of locking the data item during the entire update process, Optimistic Locking allows multiple users to fetch the same item, but it ensures that only the first user's changes are stored, while others are flagged with a mismatch.

How does it achieve this? By associating each data item with a "version number". If two users fetch the same item, they get the same version number. But, if one user updates the data item before the other, the version number in the database changes. The next user, when trying to save changes, will encounter a mismatch in version numbers, indicating that the item has been modified by someone else in the interim.

Implementing Optimistic Locking in DynamoDB

DynamoDB, being a managed NoSQL database provided by AWS, supports Optimistic Locking natively. Here's how to do it:

  1. Version Attribute: First, design your table to have an attribute that acts as a version number. This could be a simple integer or a timestamp. Whenever an item is updated, this version number is incremented.
  2. Conditional Writes: DynamoDB provides the ability to use Conditional Write for put, update, and delete operations. The idea is to include a ConditionExpression in your write request that checks if the version number on the client side matches that of the item in DynamoDB.
  3. Handling Mismatches: If another user has updated the record before your request, the version numbers will not match. DynamoDB will then throw a ConditionalCheckFailedException. This is your signal that the data has changed since you last fetched it.
  4. Retrying Operations: When faced with a ConditionalCheckFailedException, your application should re-fetch the updated item, apply its changes again, and attempt the update. This ensures that you're always working with the most recent data and not overwriting someone else's changes.
import boto3
from botocore.exceptions import ClientError

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

def get_item(item_id):
    response = table.get_item(Key={'id': item_id})
    return response.get('Item')

def update_item(item_id, new_data, expected_version):
    try:
        response = table.update_item(
            Key={'id': item_id},
            UpdateExpression="SET data = :d, _version = _version + :i",
            ExpressionAttributeValues={
                ':d': new_data,
                ':i': 1 # version increment 
            },
            ConditionExpression="_version = :v", # here is the lock condition
            ExpressionAttributeValues={
                ':v': expected_version
            },
            ReturnValues="UPDATED_NEW"
        )
        return response
    except ClientError as e:
        if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
            ...
            # handle lock exception here.
            ...
        else:
            raise

This can also be used in transact_write_items to use optimistic locking over multiple items or even tables.

Benefits and Best Practices

Optimistic Locking in DynamoDB ensures that:

  • Your application is free from accidental overwrites.
  • Users can be notified when working on stale data.
  • You maintain high performance since you're not locking database resources upfront.

However, to get the best out of Optimistic Locking in DynamoDB:

  • Retries: Implement a robust retry mechanism. This means, after a failed attempt, the application should have logic to re-fetch, reprocess, and attempt saving again.
  • Limit Infinite Loops: In extremely high-traffic systems, you may encounter constant mismatches. Implement a mechanism to limit retries to avoid potential infinite loops.

Conclusion

Concurrency can be a headache in distributed systems, but with mechanisms like Optimistic Locking, these challenges become more manageable. DynamoDB's native support for this pattern, combined with best practices in handling mismatches, ensures that your application maintains data integrity even in the face of concurrent operations. As with all systems, test thoroughly and design with the end-user in mind!

Spread the word

Keep reading