Building DynamoDB Transactions: Coordinating Atomic, Multi-Item Writes
Learn how to perform atomic, multi-item operations in DynamoDB using transactions.
The Build
In this tutorial, you’ll build an Orders and Payments workflow where both records succeed or fail together. You’ll use the AWS CLI to create two tables, perform a transactional write, verify atomicity, and then clean up your environment.
Why It Matters
Most DynamoDB writes affect a single item at a time. But real-world systems — like checkout or billing — often need to update multiple items that depend on each other. That’s where transactions come in. They let you group several operations into a single all-or-nothing block, ensuring your data stays consistent even under failure or concurrency.
What Is Atomicity?
In database terms, atomicity means that either all parts of a transaction succeed or none do. If one write fails, DynamoDB automatically rolls back the others, guaranteeing that your data never ends up half-complete. It’s the “safety lock” for multi-item operations.
Step 1 – Create the Orders and Payments Tables
You’ll create two tables:
- Orders (keyed by OrderID)
- Payments (keyed by PaymentID)
Both will use PAY_PER_REQUEST billing for simplicity.
aws dynamodb create-table \
  --table-name Orders \
  --attribute-definitions AttributeName=OrderID,AttributeType=S \
  --key-schema AttributeName=OrderID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST
aws dynamodb create-table \
  --table-name Payments \
  --attribute-definitions AttributeName=PaymentID,AttributeType=S \
  --key-schema AttributeName=PaymentID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST
Output (example):
{
  "TableDescription": {
    "TableName": "Orders",
    "TableStatus": "CREATING"
  }
}
Check table status until both read ACTIVE:
aws dynamodb describe-table --table-name Orders --query "Table.TableStatus"
aws dynamodb describe-table --table-name Payments --query "Table.TableStatus"
Output:
"ACTIVE"
Step 2 – Perform a Transactional Write
Now you’ll insert an order and its corresponding payment in a single atomic operation using transact-write-items.
aws dynamodb transact-write-items \
  --transact-items '[
    {
      "Put": {
        "TableName": "Orders",
        "Item": {
          "OrderID": {"S": "ORD#1001"},
          "Customer": {"S": "alice"},
          "Amount": {"N": "59.99"},
          "Status": {"S": "PENDING"}
        }
      }
    },
    {
      "Put": {
        "TableName": "Payments",
        "Item": {
          "PaymentID": {"S": "PAY#9001"},
          "OrderID": {"S": "ORD#1001"},
          "Method": {"S": "CreditCard"},
          "Amount": {"N": "59.99"}
        }
      }
    }
  ]'
Output:
{}
An empty response means both records were committed successfully — all or nothing.
Step 3 – Verify the Data Across Tables
Confirm that both items exist:
aws dynamodb get-item --table-name Orders --key '{"OrderID":{"S":"ORD#1001"}}'
aws dynamodb get-item --table-name Payments --key '{"PaymentID":{"S":"PAY#9001"}}'
Output (Orders):
{
  "Item": {
    "OrderID": {"S": "ORD#1001"},
    "Customer": {"S": "alice"},
    "Amount": {"N": "59.99"},
    "Status": {"S": "PENDING"}
  }
}
Output (Payments):
{
  "Item": {
    "PaymentID": {"S": "PAY#9001"},
    "OrderID": {"S": "ORD#1001"},
    "Amount": {"N": "59.99"},
    "Method": {"S": "CreditCard"}
  }
}
Step 4 – Test Transaction Failure
Now try a transaction that violates a condition — DynamoDB should roll back both writes.
Here, we’ll prevent the same OrderID from being reused:
aws dynamodb transact-write-items \
  --transact-items '[
    {
      "Put": {
        "TableName": "Orders",
        "Item": {
          "OrderID": {"S": "ORD#1001"},
          "Customer": {"S": "bob"},
          "Amount": {"N": "75.00"},
          "Status": {"S": "PENDING"}
        },
        "ConditionExpression": "attribute_not_exists(OrderID)"
      }
    },
    {
      "Put": {
        "TableName": "Payments",
        "Item": {
          "PaymentID": {"S": "PAY#9002"},
          "OrderID": {"S": "ORD#1001"},
          "Method": {"S": "CreditCard"},
          "Amount": {"N": "75.00"}
        }
      }
    }
  ]'
Expected Output (Error):
{
  "__type": "TransactionCanceledException",
  "CancellationReasons": [
    {"Code": "ConditionalCheckFailed", "Message": "OrderID already exists"}
  ]
}
In this case, the Orders table triggered the failure because the OrderID already existed — proving that DynamoDB aborted the entire transaction to maintain consistency.
No data was written — both tables rolled back atomically.
Step 5 – Update Order and Payment Atomically
Now you’ll update both tables in one transaction — marking the order as PAID and the payment as PROCESSED.
aws dynamodb transact-write-items \
  --transact-items '[
    {
      "Update": {
        "TableName": "Orders",
        "Key": {"OrderID": {"S": "ORD#1001"}},
        "UpdateExpression": "SET #S = :s",
        "ExpressionAttributeNames": {"#S": "Status"},
        "ExpressionAttributeValues": {":s": {"S": "PAID"}}
      }
    },
    {
      "Update": {
        "TableName": "Payments",
        "Key": {"PaymentID": {"S": "PAY#9001"}},
        "UpdateExpression": "SET #S = :s",
        "ExpressionAttributeNames": {"#S": "Status"},
        "ExpressionAttributeValues": {":s": {"S": "PROCESSED"}}
      }
    }
  ]'
Output:
{}
Check your updates:
aws dynamodb get-item --table-name Orders --key '{"OrderID":{"S":"ORD#1001"}}' --projection-expression "OrderID, Status"
aws dynamodb get-item --table-name Payments --key '{"PaymentID":{"S":"PAY#9001"}}' --projection-expression "PaymentID, Status"
Output (Orders):
{"Item": {"OrderID": {"S": "ORD#1001"}, "Status": {"S": "PAID"}}}
Output (Payments):
{"Item": {"PaymentID": {"S": "PAY#9001"}, "Status": {"S": "PROCESSED"}}}
Step 6 – Clean Up
To wrap up this exercise, you’ll remove all the resources you created — deleting both tables to keep your AWS environment tidy and cost-free.
Run the following commands to delete the Orders and Payments tables:
aws dynamodb delete-table --table-name Orders
aws dynamodb delete-table --table-name Payments
After a short delay, confirm that the tables were successfully deleted:
aws dynamodb list-tables
Output:
{"TableNames": []}
An empty list means both tables have been deleted successfully.
Wrap-Up
You’ve now built a fully atomic, multi-item workflow in DynamoDB.
Transactions ensure data consistency across multiple items or tables — a powerful feature for any distributed system design.
Pro Tip #1 — Keep Transactions Small
Transactions can include up to 25 items or 4 MB total data.
Keep payloads minimal for reliability and cost efficiency.
Transactions introduce a small amount of additional latency and cost compared to single-item writes, so reserve them for cases where data integrity outweighs performance sensitivity.
Pro Tip #2 — Combine with Condition Expressions
You can mix conditions inside a transaction to enforce complex business logic (like ensuring the payment only applies to pending orders).
Pro Tip #3 — Consistent Multi-Item Reads
DynamoDB also supports TransactGetItems, which lets you read up to 25 items across tables in a single atomic read operation. It guarantees that all items are returned from the same consistent point in time — ideal for validation steps before committing a transaction.
Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.
.jpeg)

 
 
 
Comments
Post a Comment