AWS News Blog

Transaction Library for DynamoDB

Today we are introducing a new client library that will reduce the development effort needed for you to perform atomic transactions that can encompass multiple DynamoDB items in one or more tables. This allows you to develop those applications more easily on DynamoDB that until now either required relational databases (with their attendant scalability issues) or required you to do a lot of work in your application tier to implement atomicity.

Let’s review the concepts of atomicity and transactions before digging in…

In many cases, several related database storage operations must be treated as a transaction. Within the scope of a transaction, all of the storage operations must succeed or fail as a unit. This is referred to at Atomicity. When all goes well, a transaction proceeds as follows:

  1. Begin transaction.
  2. Put item #1.
  3. Put item #N.
  4. Commit transaction.

If an error is detected before step 5 completes, or if the program terminates unexpectedly, then the transaction fails and the Put operations (Steps 2 through 4) are undone, leaving the database items in the state that they existed in prior to step 1. If you are writing code that transfers money from one bank account to the next, you’d definitely encapsulate the operations in a transaction, otherwise you could lose money (and prevent the accounts from balancing) if an interruption or fault occurred at an inopportune time.

The DynamoDB Transaction Library
The new library has been implemented as an extension to the existing DynamoDB in the AWS SDK for Java. This library tracks the status of ongoing transactions using a pair of DynamoDB tables. The first table stores the transactions and the second one stores pre-transaction snapshots of items modified within a transaction. You must call the verifyOrCreateTransactionTable  and verifyOrCreateTransactionImagesTable functions from the TableManager class to create the tables before you initiate any transactions.. Be sure to provision sufficient read and write capacity to avoid undue delays.

Here’s a code sample that creates the necessary tables:

AmazonDynamoDB client = new AmazonDynamoDBClient ( ) ; // pass in your AWS Credentials, and configure which DynamoDB endpoint / region you’re talking to

TransactionManager.verifyOrCreateTransactionTable(client, “Transactions” /*tableName*/,
10 /*readCapacityUnits*/, 10 /*writeCapacityUnits*/, 10 * 60 /*waitTimeSeconds*/);

TransactionManager.verifyOrCreateTransactionImagesTable(client, “TransactionImages” /*tableName*/,
10 /*readCapacityUnits*/, 10 /*writeCapacityUnits*/, 10 * 60 /*waitTimeSeconds*/);

Here’s some code that performs a transaction on the Thread and Reply tables, as described in the Amazon DynamoDB Developer Guide.  In the example Forums application, there is a Thread table, which stores one item for each question that has been asked on the forum, and a Reply table, which stores one item for each response to each question.  The Thread table also contains a count of the number of total replies for each question and whether or not the question is answered.  The following transaction both adds a new Reply, and increments the Replies counter in the associated Thread item:

// Create a new transaction manager (do this once, potentially at the same time as making a new client object)
TransactionManager txManager = new TransactionManager (client, “Transactions”, “TransactionImages” ) ;

// Create a new transaction from the transaction manager
Transaction t1 = txManager.newTransaction();

// Make a PutItem request to add a new Reply to a Thread asking about DynamoDB Transactions
Map<String, AttributeValue> reply = new HashMap<String, AttributeValue>();
reply.put(“Id”, new AttributeValue(“Amazon DynamoDB#Transactions?”));
reply.put(“ReplyDateTime”, new AttributeValue(“(the current date and time)”));
reply.put(“PostedBy”, new AttributeValue(“DavidY@AWS”));
reply.put(“Message”, new AttributeValue(“Transactions are now available!”));

t1.putItem(new PutItemRequest()
.withTableName(“Reply”)
.withItem(reply));
// At this point the new Reply item is in the table, but is not yet committed

// Add second request for a different item to the transaction object
Map<String, AttributeValue> thread = new HashMap<String, AttributeValue>();
thread.put(“ForumName”, new AttributeValue(“Amazon DynamoDB”));
thread.put(“Subject”, new AttributeValue(“Transactions?”));

Map<String, AttributeValueUpdate> threadUpdates = new HashMap<String, AttributeValueUpdate>();
threadUpdates.put(“Replies”, new AttributeValueUpdate(new AttributeValue().withN(“1”), “ADD”));

t1.updateItem(new UpdateItemRequest()
.withTableName(“Thread”)
.withKey(thread)
.withAttributeUpdates(threadUpdates));
// At this point the new Thread item is in the table, but is not yet committed

// Commit the transaction
t1.commit();
// Committed transaction. The Thread and Reply writes are now both committed

// Delete the transaction item
t1.delete();

In addition to atomic writes, the transaction library offers 3 levels of read isolation: fully isolated, committed, and uncommitted.  Fully isolated reads are performed through obtaining locks during a transaction, just like writes.  Committed reads provide a consistency guarantee similar to eventually consistent reads, and are performed by reading the old copy of the item if a lock is detected.  Uncommitted reads (also known as dirty reads) are the cheapest, but are the most dangerous, since they may return data that will later be rolled back.  Heres some code that performs a read operation at the committed isolation level:

// reusing the txManager variable from earlier

Map<String, AttributeValue> key = new HashMap<String, AttributeValue>();
key.put(“ForumName”, new AttributeValue(“Amazon DynamoDB”));
key.put(“Subject”, new AttributeValue(“Transactions?”));

// Uncommitted reads happen on the transaction manager, not on a transaction.
Map<String, AttributeValue> item = txManager.getItem(new GetItemRequest()
.withKey(key)
.withTableName(“Thread”),
IsolationLevel.COMMITTED).getItem();

More code examples are available in the README file distributed with the transactions library.

A single transaction can span one or more DynamoDB tables. Transactional puts are more expensive (in terms of read and write capacity units). A put that does not contend with any other simultaneous puts can be expected to perform 7N + 4 writes as the original operation, where N is the number of requests in the transaction.

Getting Started
To get started, download the AWS SDK for Java, pull the DynamoDB Transactions library off of the AWS Labs GitHub repository, read the documentation, and start coding!

— Jeff;

Jeff Barr

Jeff Barr

Jeff Barr is Chief Evangelist for AWS. He started this blog in 2004 and has been writing posts just about non-stop ever since.