Skip to content

Transactions

Run multiple operations that succeed or fail together. If any operation fails, DynamoDB rolls back all changes automatically.

Key features

  • All-or-nothing operations
  • Put, delete, update, and read in one transaction
  • Max 100 items per transaction
  • Metrics on every operation (see observability)

Getting started

Transactions are useful when you need to update related data atomically. For example, when creating an order, you might want to:

  1. Create the order record
  2. Update the user's order count
  3. Decrease inventory

If any of these fails, you don't want partial data. Transactions guarantee all operations succeed or none do.

import asyncio

from pydynox import DynamoDBClient, Transaction

client = DynamoDBClient()


async def create_order():
    async with Transaction(client) as tx:
        tx.put("users", {"pk": "USER#1", "sk": "PROFILE", "name": "John"})
        tx.put("orders", {"pk": "ORDER#1", "sk": "DETAILS", "user": "USER#1"})


asyncio.run(create_order())

When you use Transaction as a context manager, it automatically commits when the block ends. If an exception occurs inside the block, the transaction is not committed.

Reading multiple items

Use transact_get to read multiple items atomically. This gives you a consistent snapshot - all items are read at the same point in time.

import asyncio

from pydynox import DynamoDBClient

client = DynamoDBClient()


async def get_order_details():
    items = await client.transact_get(
        [
            {"table": "users", "key": {"pk": "USER#1", "sk": "PROFILE"}},
            {"table": "orders", "key": {"pk": "ORDER#1", "sk": "DETAILS"}},
        ]
    )

    user, order = items
    print(f"User: {user}, Order: {order}")


asyncio.run(get_order_details())

This is useful when you need to read related data that must be consistent. For example, reading a user and their orders together.

Writing with client methods

You can also use transact_write directly for more complex operations:

import asyncio

from pydynox import DynamoDBClient

client = DynamoDBClient()


async def create_user_and_order():
    operations = [
        {
            "type": "put",
            "table": "users",
            "item": {"pk": "USER#1", "sk": "PROFILE", "name": "John"},
        },
        {
            "type": "put",
            "table": "orders",
            "item": {"pk": "ORDER#1", "sk": "DETAILS", "user": "USER#1", "total": 100},
        },
    ]
    await client.transact_write(operations)


asyncio.run(create_user_and_order())

API reference

Transaction class

Method Description
tx.put(table, item) Add or replace an item
tx.delete(table, key) Remove an item
tx.update(table, key, updates) Update specific attributes
tx.condition_check(table, key, condition) Check a condition without modifying

Client methods

Async (default) Sync Description
await client.transact_write(ops) client.sync_transact_write(ops) Write multiple items atomically
await client.transact_get(gets) client.sync_transact_get(gets) Read multiple items atomically

Classes

Async (default) Sync Description
Transaction SyncTransaction Context manager for transactions

Limits

DynamoDB transactions have limits you should know:

Limit Value
Max items 100
Max size 4 MB total
Region All items must be in the same region

If you exceed these limits, the transaction fails before any operation runs.

When to use transactions

Use transactions when:

  • You need all-or-nothing behavior
  • You're updating related data that must stay consistent
  • You need to check conditions before writing (like "only update if version matches")
  • You need a consistent snapshot of multiple items

Don't use transactions for:

  • Simple single-item operations (just use save())
  • High-throughput batch writes (use BatchWriter instead - it's faster)
  • Operations that can tolerate partial success

Tip

Transactions cost twice as much as regular operations because DynamoDB does extra work to guarantee atomicity. Use them only when you need the guarantee.

Error handling

If a transaction fails, DynamoDB returns an error and no changes are made:

import asyncio

from pydynox import DynamoDBClient, Transaction
from pydynox.exceptions import TransactionCanceledException

client = DynamoDBClient()


async def safe_transfer():
    try:
        async with Transaction(client) as tx:
            tx.put("users", {"pk": "USER#1", "name": "John"})
            tx.put("orders", {"pk": "ORDER#1", "user": "USER#1"})
    except TransactionCanceledException as e:
        print(f"Transaction canceled: {e}")
    except Exception as e:
        print(f"Transaction failed: {e}")


asyncio.run(safe_transfer())

Common reasons for transaction failures:

  • Item size exceeds 400 KB
  • Total transaction size exceeds 4 MB
  • More than 100 items
  • Condition check failed
  • Throughput exceeded

Sync API

For sync code, use SyncTransaction and the sync_ prefixed methods:

from pydynox import DynamoDBClient, SyncTransaction

client = DynamoDBClient()

with SyncTransaction(client) as tx:
    tx.put("users", {"pk": "USER#1", "sk": "PROFILE", "name": "John"})
    tx.put("orders", {"pk": "ORDER#1", "sk": "DETAILS", "user": "USER#1"})

# Direct client method
items = client.sync_transact_get(
    [
        {"table": "users", "key": {"pk": "USER#1", "sk": "PROFILE"}},
    ]
)

Next steps