How to implement Atomic Counters in DynamoDB for high throughput

Upasana | August 12, 2019 | 3 min read | 6,834 views | AWS Tutorials


Atomic Updates in Amazon Dynamo DB

Dynamo DB provides functionality to atomcially updates numeric attributes, unconditionaly, without interfering with other write operations, thus giving a much better throughput than conventional load-modify-save kind of operations.

We can use UpdateItem operation to implement an atomic counter on a numeric attribute.

But we must understand that atomic update operations are not idempotent i.e. numeric value will increment each time we call UpdateItem, thus these operations may not be suitable for banking operations, instead it may be safer to use conditional writes if you can afford a slight change in the value due to retry of operation.

Step 1. Creating the DynamoDB config

We must have a valid AWS account with DynamoDB credentials to use this tutorial. We will need three properties: AccessKey, SecretKey and Region for dynamoDB.

/src/main/resources/application.yml
aws:
  accessKey : <your-aws-access-key>
  secretKey : <your-aws-secret>
  region : us-east-1
  dynamodb :
    endpoint:

First of all we need to create a AmazonDynamoDBClient and DynamoDB bean to get started with.

/src/main/java/foo/DynamoConfig.java
@Configuration
public class DynamoConfig {
    private static final Logger logger = LoggerFactory.getLogger(DynamoConfig.class);

    @Bean
    AmazonDynamoDBClient dbClient(@Value("${aws.dynamodb.endpoint}") String amazonDynamoDBEndpoint, AWSSettings awsSettings) {
        AmazonDynamoDBClient dbClient = new AmazonDynamoDBClient(
                new BasicAWSCredentials(awsSettings.getAccessKey(), awsSettings.getSecretKey()));
        dbClient.setRegion(Region.getRegion(Regions.fromName(awsSettings.getRegion())));
        logger.info("DB Endpoint is " + amazonDynamoDBEndpoint);
        if (!StringUtils.isEmpty(amazonDynamoDBEndpoint)) {
            dbClient.setEndpoint(amazonDynamoDBEndpoint);
        }
        if (awsSettings.getRegion().equalsIgnoreCase("local")) {
            dbClient.setEndpoint("http://localhost:8000");
        }
        return dbClient;
    }

    @Bean
    DynamoDB dynamoDB(AmazonDynamoDBClient client) {
        return new DynamoDB(client);
    }
}

Step 2. Create Model for table

Lets create a Content table that stores news title and description. We will create a siumilar table in DynamoDB making id as the hashkey.

/src/main/java/foo/Content.java
@Data
@DynamoDBTable(tableName = "local-content")
public class Content {
    @DynamoDBHashKey(attributeName = "id")
    private String id;

    @DynamoDBAttribute
    private String title;

    @DynamoDBAttribute
    private String description;

    @DynamoDBAttribute
    private long views;
}

Step 3. Creating DAO layer that updates views atomically

Lets create DAO layer for Content that will save and update number of views of a given content.

/src/main/java/foo/ContentDao.java
@Repository
public class ContentDao {

    @Autowired
    private AmazonDynamoDBClient dynamoDBClient;

    @Autowired
    private DynamoDB dynamoDB;

    public Content create(Content dto)  {
        DynamoDBMapper mapper = new DynamoDBMapper(dynamoDBClient);
        mapper.save(dto);
        return dto;
    }

    /**
     * This method atomically updates number of views without interfering with other update operations, use it with caution because method is not idempotent in nature.
     * @param contentId ID of the content
     * @param delta the number of views to increment
     */
    public void incrementViews(String contentId, long delta) {
        Map<String, AttributeValue> key = new HashMap<>();
        key.put("id", new AttributeValue().withS(contentId));

        UpdateItemRequest updateRequest =
                new UpdateItemRequest().withTableName("local-content")
                        .withKey(key)
                        .addAttributeUpdatesEntry("views", new AttributeValueUpdate()
                            .withValue(new AttributeValue().withN("" + delta))
                            .withAction(AttributeAction.ADD));
        dynamoDBClient.updateItem(updateRequest);
    }

Important Caution

  1. Do not use atomic counters for attribute that can not tolerate slight overcounting or undercounting for e.g. in banking applications.

  2. Atomic counter operations are not idempotent in nature, a simple retry will increment the value again.

Use Cases for Atomic Counters

Website visit counters are ideal candidates for atomic counters.

Download Git Repository

Code in this tutorial is available at https://github.com/cancerian0684/dynamodb-atomic-updates


AWS Tutorials:
  1. AWS Lambda Interview Questions for Developers
  2. AWS SDK 1.x - S3 file download & upload
  3. Python: Send event from AWS Lambda to AWS SQS
  4. AWS SDK 2: SQS Object Operations using Spring Boot
  5. S3 File upload & download with AWS Java SDK v2
  6. AWS Lambda in Kotlin using Spring Cloud Function
  7. Creating AWS Lambda using python 3.6
See all articles in AWS Tutorials
Top articles in this category:
  1. How to automatically Retry DynamoDB Write on ProvisionedThroughputExceededException
  2. What are Best Practices for Using Amazon DynamoDB?
  3. How will you ensure that no two threads update the same db record in parallel in amazon DynamoDB
  4. AWS DynamoDB Java interview questions
  5. What are Conditional Writes in AWS DynamoDB
  6. Scan all records of a Amazon DynamoDB table using a Java Code
  7. What is Eventual Consistency in DynamoDB?

Recommended books for interview preparation:

Find more on this topic: