DynamoDB StreamsCDCLambda TriggersGlobal TablesLast Writer WinsEvent-Driven

Streams & Global Tables

Change data capture for event-driven architectures and multi-region active-active replication with automatic conflict resolution.

40 min read9 sections
01

What DynamoDB Streams Are

DynamoDB Streams is an ordered log of all item-level changes in a table. Every INSERT, MODIFY, or REMOVE generates a stream record that downstream consumers can process.

πŸ“Ή

The Security Camera Analogy

DynamoDB Streams is like a security camera recording every change to your table. It captures what happened (INSERT/MODIFY/REMOVE), when it happened, and what the item looked like before and after. The footage is kept for 24 hours. Multiple viewers (Lambda functions) can watch the same footage independently without affecting each other.

DynamoDB Streams Characteristics

  • βœ…Ordered log of all item-level changes (INSERT, MODIFY, REMOVE)
  • βœ…Records available for 24 hours (then automatically deleted)
  • βœ…Each stream record contains: event type, item keys, before/after images
  • βœ…Exactly-once delivery per shard β€” within a shard, records are ordered
  • βœ…Records within a partition key are ordered (cross-partition ordering not guaranteed)
  • βœ…Near real-time β€” records available within milliseconds of the write
02

Stream View Types

View TypeWhat's CapturedUse Case
KEYS_ONLYOnly key attributes of modified itemTrigger processing that fetches fresh data
NEW_IMAGEEntire item after modificationSync to search index, cache warming
OLD_IMAGEEntire item before modificationAudit logging, undo operations
NEW_AND_OLD_IMAGESBoth before and afterDiff detection, complex event processing

Choose Wisely β€” Cannot Change Later

The stream view type is set when enabling streams on a table. Changing it requires disabling and re-enabling streams, which creates a new stream ARN. NEW_AND_OLD_IMAGES is the most flexible but generates the most data. Choose based on your consumer needs.

stream-record-structure.tstypescript
// Stream record structure (NEW_AND_OLD_IMAGES)
{
  eventID: "abc123",
  eventName: "MODIFY",  // INSERT | MODIFY | REMOVE
  dynamodb: {
    Keys: { PK: { S: "USER#123" }, SK: { S: "PROFILE" } },
    OldImage: { PK: { S: "USER#123" }, name: { S: "Old Name" }, version: { N: "5" } },
    NewImage: { PK: { S: "USER#123" }, name: { S: "New Name" }, version: { N: "6" } },
    SequenceNumber: "111222333",
    SizeBytes: 256
  },
  eventSourceARN: "arn:aws:dynamodb:us-east-1:123456:table/Users/stream/..."
}
03

Processing Streams with Lambda

The most common pattern: Lambda polls the stream automatically when configured as an event source trigger. No infrastructure to manage β€” fully serverless CDC.

1

Item Written

Application writes to DynamoDB table

2

Stream Record

DynamoDB creates stream record (within milliseconds)

3

Lambda Polls

Lambda service polls stream shards for new records

4

Batch Invocation

Lambda invoked with batch of records (configurable batch size)

5

Processing

Lambda processes records β€” sync to ES, invalidate cache, send notification

Lambda Trigger Configuration

SettingPurposeRecommendation
BatchSizeRecords per Lambda invocationStart with 100, tune based on processing time
MaximumBatchingWindowInSecondsWait time to fill batch0 for low latency, 5-60 for throughput
BisectBatchOnFunctionErrorSplit failing batch to find poison pillAlways enable
MaximumRetryAttemptsRetries before sending to DLQ3-5 attempts
DestinationConfig.OnFailureDead letter queue for failed batchesAlways configure (SQS or SNS)
FilterCriteriaOnly invoke for matching eventsFilter by eventName or item attributes

Poison Pill Records

If one record in a batch causes Lambda to fail, the entire batch retries indefinitely (blocking the shard). Enable BisectBatchOnFunctionError to split the batch and isolate the failing record. Always configure a dead letter queue for unprocessable records.

04

Event-Driven Patterns

Common Stream Processing Patterns

PatternHow It WorksExample
Search syncStream β†’ Lambda β†’ Elasticsearch indexNear-real-time search of DynamoDB data
Cache invalidationStream β†’ Lambda β†’ delete Redis keyInvalidate cache when source data changes
Audit loggingStream β†’ Lambda β†’ write to audit tableImmutable record of every change
AggregationStream β†’ Lambda β†’ update counters/sumsMaintain materialized views
NotificationStream β†’ Lambda β†’ SNS/SESSend email when order status changes
Cross-region syncStream β†’ Lambda β†’ write to other regionBefore Global Tables existed

Outbox Pattern with DynamoDB

outbox-pattern.txttext
Problem: Dual-write β€” write to DynamoDB AND publish to SNS/EventBridge
  If DynamoDB write succeeds but SNS publish fails β†’ inconsistency

Solution: Transactional Outbox
  1. Write business item + event record in same DynamoDB transaction
  2. Stream processor reads event record from stream
  3. Stream processor publishes to SNS/EventBridge
  4. Guarantee: if item write succeeds, event WILL eventually be published

TransactWriteItems([
  { Put: { item: orderItem } },           // business data
  { Put: { item: orderCreatedEvent } }     // outbox event
])
β†’ Stream captures both β†’ Lambda publishes event to EventBridge

DynamoDB as Event Store

DynamoDB as Event Store

  • βœ…Append-only writes β€” never update past events
  • βœ…PK = AGGREGATE#123, SK = EVENT#0000001 (sequence number)
  • βœ…Rebuild state by reading all events for an aggregate (Query)
  • βœ…Stream as the publish mechanism β€” new events stream to consumers
  • βœ…Snapshot optimization: periodically write current state to avoid full replay
05

Global Tables Overview

Global Tables replicate a DynamoDB table across multiple AWS regions. Active-active: reads and writes accepted in any region. Replication is asynchronous with sub-second lag typically.

Global Tables Characteristics

  • βœ…Active-active: reads and writes accepted in any region
  • βœ…Replication lag typically < 1 second (asynchronous)
  • βœ…DynamoDB Streams powers replication internally
  • βœ…Each region is fully independent β€” regional failure doesn't affect others
  • βœ…Conflict resolution: last writer wins (based on timestamp)
  • βœ…All replicas must use same capacity mode (provisioned or on-demand)
🌐

The Branch Office Analogy

Global Tables are like branch offices that each maintain their own copy of the company ledger. Any branch can accept new entries. Changes propagate to all other branches within seconds. If two branches modify the same entry simultaneously, the most recent change wins. If a branch goes offline, the others continue operating β€” when it comes back, it catches up automatically.

1

Write in us-east-1

Application writes item to US East region

2

Local Commit

Item committed locally, acknowledged to client

3

Stream Replication

DynamoDB Streams propagates change to other regions

4

Remote Apply

eu-west-1 and ap-southeast-1 receive and apply the change

5

Globally Consistent

All regions have the item (typically < 1 second total)

06

Conflict Resolution

When the same item is modified in multiple regions simultaneously, DynamoDB uses last-writer-wins based on the item's timestamp. There is no merge β€” the entire item is replaced by the winner.

No Merge, No Custom Resolution

DynamoDB does not support custom conflict resolution strategies. Last-writer-wins is the only option. If two regions update different attributes of the same item simultaneously, one update is lost entirely. Design your application to avoid concurrent writes to the same item from multiple regions.

Strategies to Avoid Conflicts

Strategies to Avoid Conflicts

  • βœ…Route writes to user's home region (user affinity)
  • βœ…Partition ownership: each region 'owns' specific partition keys
  • βœ…Append-only patterns: never update, only insert new items
  • βœ…Use conditional writes with version numbers (will fail in non-owner region)
  • βœ…Accept eventual consistency β€” design for idempotent operations
07

Global Tables Use Cases & Limitations

βœ… Use Cases

  • Low-latency reads globally (nearest region)
  • Disaster recovery (automatic failover)
  • Data locality compliance (write to specific region)
  • Active-active multi-region applications
  • Gaming leaderboards (regional + global)

⚠️ Limitations

  • Eventually consistent only across regions
  • Transactions cannot span regions
  • TTL deletions replicate (may cause surprises)
  • All replicas must use same capacity mode
  • Last-writer-wins only (no custom conflict resolution)
08

Interview Questions

Q:What are DynamoDB Streams and how do they differ from Kinesis Data Streams?

A: DynamoDB Streams is a change data capture log specific to DynamoDB β€” 24-hour retention, ordered per partition key, integrated with Lambda triggers. Kinesis Data Streams is a general-purpose streaming service with 7-day retention (extendable to 365), multiple consumers via KCL, and higher throughput. You can enable Kinesis adapter on DynamoDB for longer retention and fan-out.

Q:How would you implement near-real-time search on DynamoDB data?

A: Enable DynamoDB Streams with NEW_IMAGE view type. Configure Lambda trigger that reads stream records and indexes them into Elasticsearch/OpenSearch. On INSERT/MODIFY: upsert document in ES. On REMOVE: delete document from ES. Result: search index stays within seconds of DynamoDB, no polling needed.

Q:How does conflict resolution work in Global Tables?

A: Last-writer-wins based on timestamp. If the same item is modified in us-east-1 and eu-west-1 simultaneously, the write with the later timestamp survives. The entire item is replaced β€” no attribute-level merge. To avoid conflicts: route writes to user's home region, use append-only patterns, or implement application-level conflict detection.

Q:What is the outbox pattern and why use it with DynamoDB?

A: The outbox pattern solves the dual-write problem: writing to DynamoDB AND publishing an event must both succeed or both fail. Solution: write the business item and an 'event' item in the same DynamoDB transaction. A stream processor reads the event item from the stream and publishes it to SNS/EventBridge. Guarantees: if the write succeeds, the event will eventually be published.

Q:What happens to a Lambda stream processor when it encounters a bad record?

A: Without BisectBatchOnFunctionError, the entire batch retries indefinitely, blocking the shard. With bisect enabled, DynamoDB splits the batch in half recursively until the failing record is isolated. After MaximumRetryAttempts, the record is sent to the configured dead letter queue (OnFailure destination). Always enable both settings.

09

Common Mistakes

πŸ“­

Not configuring a dead letter queue for stream processors

Without a DLQ, a poison pill record blocks the entire shard indefinitely. All subsequent records in that shard are stuck. Always configure OnFailure destination and BisectBatchOnFunctionError.

βœ…Configure an SQS dead letter queue as the OnFailure destination and enable BisectBatchOnFunctionError on the Lambda event source mapping.

🌍

Assuming Global Tables provide strong consistency across regions

Global Tables are eventually consistent across regions (typically < 1 second). If you read in eu-west-1 immediately after writing in us-east-1, you may get stale data. Design for eventual consistency or route reads to the write region.

βœ…Route reads to the same region where the write occurred, or design your application to tolerate eventual consistency across regions.

✍️

Writing to the same item from multiple regions simultaneously

Last-writer-wins means one update is silently lost. If two regions update different attributes of the same item, the entire item from the 'loser' is discarded. Route writes for the same entity to one region, or use append-only patterns.

βœ…Implement region affinity for writes (route each entity's writes to a single region) or use append-only patterns to avoid conflicts.

πŸ”‘

Using KEYS_ONLY stream view when you need item data

With KEYS_ONLY, your Lambda must do a GetItem to fetch the actual data β€” adding latency, cost, and the risk of reading a newer version than what triggered the stream. Use NEW_IMAGE or NEW_AND_OLD_IMAGES if your processor needs item attributes.

βœ…Enable NEW_IMAGE or NEW_AND_OLD_IMAGES stream view type when your processor needs to access item attributes.

πŸ“‹

Not handling stream record ordering correctly

Records are ordered within a partition key, but NOT across partition keys. If your processor assumes global ordering, it will produce incorrect results. Process each partition key's records independently and handle out-of-order cross-partition events.

βœ…Process records per partition key independently and use sequence numbers or timestamps to handle cross-partition ordering.