Skip to content
AWS aws databases 6 min read

DynamoDB Streams

DynamoDB Streams is a feature that records every change to the items in a DynamoDB table and keeps that record as an ordered log you can read for the next 24 hours. Every time an item is created, updated, or deleted, DynamoDB writes a small “stream record” describing what happened. You can then have another service, usually AWS Lambda (a service that runs your code without managing servers), read those records and react to them. This is the foundation for event-driven architectures, where one action automatically triggers another, without you having to poll the table over and over.

What a stream actually contains

A stream is a time-ordered sequence of records. Each record describes a single change to one item and includes a timestamp, the table name, and the type of event (insert, modify, or remove). You choose how much detail each record carries when you enable the stream. This setting is called the stream view type.

Stream view typeWhat each record containsWhen to use this
KEYS_ONLYOnly the key attributes of the changed itemYou just need to know which item changed and will re-read it yourself
NEW_IMAGEThe whole item after the changeYou want the latest state, e.g. to copy it elsewhere
OLD_IMAGEThe whole item before the changeYou need to know what was deleted or overwritten
NEW_AND_OLD_IMAGESBoth before and afterChange data capture (CDC), auditing, computing diffs

Tip: NEW_AND_OLD_IMAGES is the most useful for CDC (Change Data Capture, the practice of streaming database changes to other systems) because you can see exactly what changed. It costs nothing extra to store more detail in the stream, so pick the view type that matches what your consumer needs.

When to use DynamoDB Streams (and when not to)

Use Streams when you want to do something in reaction to data changes: send a notification when an order is placed, replicate data to another table or region, update a search index, maintain an aggregate count, or feed an analytics pipeline.

Do not use Streams when you simply need to query current data (just read the table), or when you need a durable message queue that retains data for days. Stream records expire after 24 hours, so they are not long-term storage. For long retention or fan-out to many independent consumers, send the data into a Kinesis data stream instead (see below).

Enabling a stream

You enable a stream on an existing table or when you create it.

Console steps

  1. Open the DynamoDB console and choose Tables in the left menu.
  2. Select your table (for example, Orders).
  3. Open the Exports and streams tab.
  4. Under DynamoDB stream details, choose Turn on.
  5. Pick a view type (for CDC, choose New and old images).
  6. Choose Turn on stream.

AWS CLI

aws dynamodb update-table \
  --table-name Orders \
  --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES

Output:

{
    "TableDescription": {
        "TableName": "Orders",
        "TableStatus": "UPDATING",
        "StreamSpecification": {
            "StreamEnabled": true,
            "StreamViewType": "NEW_AND_OLD_IMAGES"
        },
        "LatestStreamArn": "arn:aws:dynamodb:us-east-1:111122223333:table/Orders/stream/2026-06-15T10:21:33.482"
    }
}

The LatestStreamArn (Amazon Resource Name, a unique ID for an AWS resource) is what you point your consumer at.

Connecting a Lambda consumer

The most common way to read a stream is to attach a Lambda function. AWS manages the polling for you: as records appear, Lambda invokes your function with a batch of them.

Console steps

  1. Open the Lambda console and choose Create function, then write your handler.
  2. In the function page, choose Add trigger.
  3. Select DynamoDB as the source.
  4. Pick your table, set Batch size (e.g. 100) and a Starting position (LATEST to read only new changes, or TRIM_HORIZON to read from the oldest available record).
  5. Choose Add.

AWS CLI

aws lambda create-event-source-mapping \
  --function-name ProcessOrders \
  --event-source-arn arn:aws:dynamodb:us-east-1:111122223333:table/Orders/stream/2026-06-15T10:21:33.482 \
  --batch-size 100 \
  --starting-position LATEST \
  --maximum-retry-attempts 3 \
  --bisect-batch-on-function-error \
  --maximum-record-age-in-seconds 3600

Output:

{
    "UUID": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
    "FunctionArn": "arn:aws:lambda:us-east-1:111122223333:function:ProcessOrders",
    "State": "Creating",
    "BatchSize": 100,
    "StartingPosition": "LATEST",
    "MaximumRetryAttempts": 3,
    "BisectBatchOnFunctionError": true,
    "MaximumRecordAgeInSeconds": 3600
}

A minimal Node.js handler looks like this:

exports.handler = async (event) => {
  for (const record of event.Records) {
    if (record.eventName === "INSERT") {
      const newItem = record.dynamodb.NewImage;
      console.log("New order:", newItem.orderId.S);
      // send confirmation, update an index, etc.
    }
  }
  return { processed: event.Records.length };
};

The big gotcha: ordering, expiry, and poison records

Three facts about streams trip up almost everyone:

  1. Records expire after 24 hours. If your consumer is broken or paused for longer than that, those changes are gone forever. There is no replay beyond the 24-hour window.
  2. Ordering is only guaranteed per partition key. A stream is split into “shards,” and all changes to items that share the same partition key go to the same shard in order. But two different partition keys can be processed in any order relative to each other. Do not assume global ordering.
  3. A failing Lambda blocks its shard. When your function throws an error, Lambda retries the same batch before moving on. If one “poison” record always fails, it can stall every other record behind it on that shard until the records expire. This is the classic way one bad event freezes an entire pipeline.

Warning: Always design your consumer to be idempotent (processing the same record twice produces the same result) because retries and at-least-once delivery mean you will see duplicates. Set MaximumRetryAttempts and MaximumRecordAgeInSeconds, enable BisectBatchOnFunctionError to isolate the bad record, and configure an OnFailure destination (an SQS queue or SNS topic) so poison records are sent aside instead of blocking the shard.

Streams vs Kinesis Data Streams for DynamoDB

DynamoDB can also push changes to a Kinesis data stream instead of (or alongside) the native stream.

FeatureDynamoDB StreamsKinesis Data Streams
Retention24 hours (fixed)Up to 365 days (configurable)
ConsumersBest with Lambda; one logical consumerMany independent consumers (fan-out)
OrderingPer partition keyPer partition key
Best forSimple event-driven Lambda triggersLong retention, multiple analytics consumers

There is no per-record charge for the native DynamoDB Stream itself; you pay only for the GetRecords read requests your consumers make (and Lambda polling is included free). Kinesis charges for shard hours and data, so for a simple Lambda trigger the native stream is usually cheaper.

Best Practices

  • Choose NEW_AND_OLD_IMAGES when you need CDC or auditing; use KEYS_ONLY when records will re-read the table themselves.
  • Make every consumer idempotent so duplicate deliveries are harmless.
  • Set MaximumRetryAttempts, MaximumRecordAgeInSeconds, and BisectBatchOnFunctionError, and add an OnFailure destination so poison records never stall a shard.
  • Keep your Lambda fast and within its timeout; a slow consumer lets records pile up and risks hitting the 24-hour expiry.
  • Monitor the IteratorAge CloudWatch metric, which tells you how far behind your consumer is. A rising value means you are falling behind.
  • For long retention or multiple independent consumers, route changes to a Kinesis data stream instead of the native stream.
Last updated June 15, 2026
Was this helpful?