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:
- Begin transaction.
- Put item #1.
- Put item #N.
- 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:
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:
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. Here’s some code that performs a read operation at the “committed” isolation level:
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.
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!