Advanced6 min read

DynamoDB Streams

DynamoDB Streams is a change-data-capture log: every insert, update, and delete on a table is captured, in order, as a stream of records you can react to. It's how you turn a table into an event source without polling it.

In the audit-log scenario you want to react the instant a sensitive event lands — fire an alert when someone exports an invoice or grants an admin role — without scanning the table on a timer. Streams is the push side of that.

How do DynamoDB Streams work?

DynamoDB Streams capture every insert, update, and delete on a table as a time-ordered, deduplicated log of records, retained for up to 24 hours. You choose what each record carries with StreamViewType (keys, new image, old image, or both), then consume the stream with a Lambda trigger to react to item changes without polling.

  • Streams captures item-level changes as a time-ordered, deduplicated log, retained for up to 24 hours.
  • You choose what each record carries via StreamViewType: keys only, the new image, the old image, or both old and new.
  • Records are ordered within a , and a stream is sharded the same way the table is.
  • The native consumer is Lambda — a trigger that runs per batch of new records, with Kinesis Data Streams as the alternative for richer fan-out.

The problem: reacting without polling

You need "alert me when a role.granted event is written." The naïve approach is a scheduled job that scans for new events every minute — which reads the whole recent partition every time, costs capacity, and is always at least a minute late.

What you actually want is a push: DynamoDB tells you the moment an item changes. That's exactly what Streams provides, with the change record delivered to your code instead of you hunting for it.

How Streams works

Per the AWS docs, DynamoDB Streams stores "a deduplicated, time-ordered log of changes for up to 24 hours" with native Lambda integration (change data capture for DynamoDB). Each record describes one item-level modification.

When you enable a stream you pick a StreamViewType, which controls how much of the changed item each record carries:

StreamViewTypeeach record contains
KEYS_ONLYonly the key attributes of the changed item
NEW_IMAGEthe entire item as it looks after the change
OLD_IMAGEthe entire item as it looked before the change
NEW_AND_OLD_IMAGESboth the before and after images

Records are ordered within each partition key and the stream is sharded along the same partition structure as the table. Retention is 24 hours — Streams is a reaction buffer, not a permanent history. For durable history you store the events themselves (which is exactly what our audit-log table already is).

The native consumer is a Lambda trigger: DynamoDB invokes your function with a batch of new stream records as they arrive.

LambdaStream"DynamoDB"AppLambdaStream"DynamoDB"App"Put EVENT role.granted""change record (NEW_IMAGE)""batch of records""if action is sensitive →alert"

A worked example: alert on sensitive audit events

The audit-log table gets a stream with NEW_IMAGE, so each record carries the full new event. A Lambda consumes the batch and forwards only the records that matter:

stream record (NEW_IMAGE)consumer action
TENANT#acmeEVENT#…#a2action=invoice.exportsend to SIEM
TENANT#globex EVENT#…#b9 action=role.grantedpage on-call
TENANT#acmeEVENT#…#a1action=login.successignore

The function never touches the table — it reacts purely to what the stream hands it. No polling, no scan, and the alert fires within seconds of the write. Because records are ordered per partition key, all of one tenant's events arrive in the order they were written.

This is also the standard way to maintain a downstream copy: a stream consumer can project each event into OpenSearch for full-text audit search, or aggregate counts — all derived from the same change log.

Do it in DynoTable

Before you wire up a stream consumer, you need to know the exact shape of the item your Lambda will receive — which attributes exist, how nested maps and lists look, what a NEW_IMAGE record will actually contain.

To convert a sample item between plain JSON and the attribute-value shape a stream record uses, the DynamoDB JSON Converter does it in your browser. And in DynoTable, you can inspect the full item — including its DynamoDB-JSON form — so you model the NEW_IMAGE record against real data instead of guessing the field shape.

Inspecting an audit event item in DynoTable to model the NEW_IMAGE stream record its Lambda consumer will receive.
Inspecting an audit event item in DynoTable to model the NEW_IMAGE stream record its Lambda consumer will receive.

If you're testing a consumer locally, run the table against DynamoDB Local and inspect it the same way — see connect to DynamoDB Local.

Pitfalls and next steps

  • 24 hours is not a backlog. If your consumer is down for a day, records age out and are gone. Streams is for near-real-time reaction, not durable replay — keep the events themselves for history.
  • Pick the smallest StreamViewType you need. NEW_AND_OLD_IMAGES doubles the payload; if you only need the key to go re-read the item, KEYS_ONLY is cheaper.
  • Ordering is per partition key, not global. Events for two different tenants have no ordering guarantee between them — only within one tenant's partition.
  • TTL deletes show up as stream records with the system-attribute marker, which is how you archive expiring items — see DynamoDB TTL.

Streams turns the audit log into an event source. The next operational concern is the opposite end of an item's life — expiring old events automatically with DynamoDB TTL.

Download DynoTable to inspect the exact item shape your stream consumer will receive before you write a line of Lambda code.

Updated