← Week 2: DynamoDB Internals

Day 10: DynamoDB Consistency and Transactions

Phase 5 · Aug 21, 2026

← Week 2: DynamoDB Internals

Agenda (2–3 hours)

  • Read (45 min): DynamoDB read consistency documentation; DynamoDB Transactions documentation; DynamoDB conditional expressions
  • Study (45 min): Design conditional expressions for: increment a counter only if item exists; add to a set only if it doesn't already contain the value
  • Practice (45 min): Implement a distributed counter, a unique-email check, and a multi-item transactional update in Rust
  • Challenge (30 min): DynamoDB transactions are limited to 100 items across 1 table (or multiple tables). Design a pattern for a saga that updates 200 items in a single logical operation
← Week 2: DynamoDB Internals

Read Consistency

Eventually consistent reads (default):

  • Return data as of a recent point in time (lag < 1s typically)
  • 50% cheaper in RCU
  • Use for: dashboard queries, non-critical reads

Strongly consistent reads:

  • Return data reflecting all writes that received a successful response
  • Must specify: consistent_read: true
  • Not available on GSIs
dynamodb.get_item()
    .table_name("orders")
    .key("pk", AttributeValue::S("order-42".to_string()))
    .consistent_read(true)  // strongly consistent
    .send().await?;
← Week 2: DynamoDB Internals

Conditional Expressions

DynamoDB's optimistic locking primitive:

// Only update if version matches (optimistic lock)
dynamodb.update_item()
    .condition_expression("version = :expected_version")
    .expression_attribute_values(":expected_version", AttributeValue::N("5".to_string()))
    .update_expression("SET balance = :new_bal, version = :new_version")
    // ...
    .send().await
    .map_err(|e| match e.into_service_error() {
        UpdateItemError::ConditionalCheckFailedException(_) => AppError::StaleVersion,
        e => AppError::from(e),
    })?;
← Week 2: DynamoDB Internals

TransactWriteItems

Atomically update up to 100 items, across up to 10 tables:

dynamodb.transact_write_items()
    .transact_items(TransactWriteItem::builder()
        .put(Put::builder()
            .table_name("orders")
            .item("pk", AttributeValue::S("order-42".to_string()))
            .condition_expression("attribute_not_exists(pk)") // fail if exists
            .build()?)
        .build())
    .transact_items(TransactWriteItem::builder()
        .update(Update::builder()
            .table_name("inventory")
            .key("item_id", AttributeValue::S("sku-99".to_string()))
            .update_expression("SET qty = qty - :one")
            .condition_expression("qty > :zero")
            .build()?)
        .build())
    .send().await?;
← Week 2: DynamoDB Internals

TransactGetItems

Atomically read multiple items in a consistent snapshot:

// All items reflect the same point in time — no dirty reads across items
dynamodb.transact_get_items()
    .transact_items(TransactGetItem::builder()
        .get(Get::builder().table_name("accounts").key("id", av!("user-1")).build()?)
        .build())
    .transact_items(TransactGetItem::builder()
        .get(Get::builder().table_name("accounts").key("id", av!("user-2")).build()?)
        .build())
    .send().await?;
← Week 2: DynamoDB Internals

Key Takeaways

  • Eventually consistent reads: half-price, slight staleness acceptable
  • Strongly consistent reads: required for up-to-date data after a write
  • Conditional expressions implement optimistic locking and uniqueness constraints
  • Transactions: up to 100 items atomically; consume 2× the capacity of non-transactional writes

Tomorrow: DynamoDB Streams — change data capture for DynamoDB.