Using DynamoDB Transactions for Atomic Multi‑Item Operations: A Practical Guide
When you need to update several items across tables in a single, all‑or‑nothing operation, DynamoDB transactions offer ACID guarantees. This post shows how to use TransactWriteItems, handle idempotency, watch costs, and avoid common pitfalls.
21 Jun 2026, 00:13 UTC

Problem: Ensuring All‑Or‑Nothing Updates Across Tables
In many applications, a user action touches multiple items—think of a payment that debits a wallet, credits a merchant, and logs a transaction. If one of those writes fails, the system ends up in a corrupt state. Traditional DynamoDB operations are atomic only per item, so you need a higher‑level mechanism to guarantee that either all writes succeed or none do.
Why DynamoDB Transactions Matter
Transactions in DynamoDB (TransactWriteItems / TransactGetItems) provide full ACID guarantees within a single AWS region. They support up to 100 items or 4 MB of data per request, and they serialize conflicting writes via optimistic concurrency control. The key benefits are:
- Atomicity – either the entire set of actions succeeds or the entire set is rolled back.
- Consistency – reads in a transaction see a snapshot as of the transaction start time.
- Isolation – concurrent transactions on overlapping items are serialized; the last commit wins.
- Durability – once a transaction commits, the changes survive failures.
How to Use TransactWriteItems
The AWS CLI makes it straightforward to craft a transaction. Below is a minimal example that updates two items in separate tables and includes a conditional check. Replace TABLE_A, TABLE_B, and the key values with your own.
aws dynamodb transact-write-items \
--transact-items file://transaction.json
transaction.json
{
"TransactItems": [
{
"Update": {
"TableName": "TABLE_A",
"Key": {"PK": {"S": "user#123"}},
"UpdateExpression": "SET balance = balance - :amount",
"ConditionExpression": "balance > :amount",
"ExpressionAttributeValues": {
":amount": {"N": "50"}
}
}
},
{
"Put": {
"TableName": "TABLE_B",
"Item": {
"PK": {"S": "tx#456"},
"Amount": {"N": "50"},
"Timestamp": {"S": "2026-10-14T23:15:00Z"}
}
}
}
]
}
When the balance is insufficient, the ConditionExpression fails and DynamoDB throws a TransactionCanceledException. The entire transaction is rolled back, leaving both tables unchanged.
Idempotency with a Token Attribute
Retries are common in distributed systems. To prevent duplicate writes, include a client‑generated idempotency token as an attribute and guard it with attribute_not_exists. Example: add IdempotencyToken to the Put action.
"Put": {
"TableName": "TABLE_B",
"Item": {
"PK": {"S": "tx#456"},
"Amount": {"N": "50"},
"Timestamp": {"S": "2026-10-14T23:15:00Z"},
"IdempotencyToken": {"S": "abc123"}
},
"ConditionExpression": "attribute_not_exists(IdempotencyToken)"
}
On a retry, the condition fails, DynamoDB rolls back the transaction, and the client can safely ignore the error or retry with a new token.
Cost & Throughput Considerations
Transactions consume twice the write capacity units (WCUs) of a standard write on the same items because DynamoDB performs an internal two‑phase commit. If you write 10 items that would normally cost 1 WCU each, the transaction consumes ~20 WCUs. The following table shows a quick estimate:
| Item Count | Standard Write WCUs | Transaction WCUs |
|---|---|---|
| 10 | 10 | 20 |
| 50 | 50 | 100 |
| 100 | 100 | 200 |
Additionally, the default per‑region quota is 1,000 WCUs per second for transactions. If your workload exceeds this, request a quota increase via the AWS Support Center.
Common Pitfalls & Trade‑offs
- Global Tables – Transactions are not supported on multi‑region global tables. Attempting to target a global replica returns a
ValidationException. - Item Size – Items larger than 400 KB cannot participate in a transaction. The total payload of a transaction (including attribute names and condition expressions) must stay under 4 MB.
- TTL Deletes – Time‑to‑live (TTL) deletes are asynchronous and cannot be mixed with transactional writes on the same item. Mixing may lead to lost updates.
- Non‑Transactional Reads – If you read an item outside a transaction and then write it transactionally, you risk a stale read. Prefer
TransactGetItemsfor read‑modify‑write patterns.
Verification & Monitoring
Use CloudWatch metrics ConsumedWriteCapacityUnits and TransactionConflict to gauge contention. CloudTrail data events (enable DynamoDB data events) will record each TransactWriteItems call, giving you auditability.
To verify that a transaction rolls back on a failed condition, run the example above with balance set to 30 and observe a TransactionCanceledException. After the call, query TABLE_A to confirm the balance remains 30 and TABLE_B has no new item.
Actionable Next Steps
- Create a test table and populate it with sample data.
- Implement the idempotency pattern in your application code.
- Enable CloudWatch metrics for
ConsumedWriteCapacityUnitsandTransactionConflict. - Request a transaction quota increase if you anticipate high throughput.
- Document transaction boundaries and ensure all related writes go through a single
TransactWriteItemscall.
By following these guidelines, you can safely use DynamoDB transactions to enforce consistency across your application’s critical data paths while keeping an eye on cost and capacity limits.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.