Links#
- DynamoDB Streams
- DynamoDB Streams and AWS Lambda triggers
- Best practices for DynamoDB and Lambda
- DynamoDB stream view types
1. Important Points#
DynamoDB Streams 是 DynamoDB table 的 change log。它适合把 table mutation 变成异步事件,但不适合做长期日志系统。
common use cases:
update search index
invalidate cache
maintain aggregate / materialized view
sync to another table / region / system
trigger async workflow after order status changed
audit important item changes
publish domain events to EventBridge / SNS / SQS
not good for:
long-term event retention
replay from months ago
high-fanout consumer group design
replacing online readsStream record order:
same item:
change order is preserved
different partition keys:
do not rely on global order2. Stream View Type#
| View Type | Contains | Use Case |
|---|---|---|
KEYS_ONLY |
changed item keys only | lightweight notification, consumer reads latest item |
NEW_IMAGE |
item after change | index update, cache refresh |
OLD_IMAGE |
item before change | audit delete/update old value |
NEW_AND_OLD_IMAGES |
before and after | diff, audit, sync with delete/update context |
Default recommendation:
NEW_AND_OLD_IMAGES:
best for correctness
more payload size
easier to handle update/delete/audit
NEW_IMAGE:
good when only current state mattersEnable stream:
aws dynamodb update-table \
--table-name customer_order_status \
--stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES \
--region ap-northeast-13. Lambda Consumer#
Minimal Lambda handler:
import json
from boto3.dynamodb.types import TypeDeserializer
deserializer = TypeDeserializer()
def decode_image(image):
if not image:
return None
return {k: deserializer.deserialize(v) for k, v in image.items()}
def lambda_handler(event, context):
for record in event["Records"]:
event_name = record["eventName"] # INSERT / MODIFY / REMOVE
keys = decode_image(record["dynamodb"].get("Keys"))
old_item = decode_image(record["dynamodb"].get("OldImage"))
new_item = decode_image(record["dynamodb"].get("NewImage"))
print(json.dumps({
"event_name": event_name,
"keys": keys,
"customer_id": (new_item or old_item or {}).get("customer_id"),
"order_id": (new_item or old_item or {}).get("order_id"),
"status": (new_item or old_item or {}).get("status"),
"old_item": old_item,
"new_item": new_item,
}, default=str))
# example:
# INSERT -> publish order-created event
# MODIFY -> publish order-status-changed event
# REMOVE -> remove order from read modelLambda execution role policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadCustomerOrderStatusStream",
"Effect": "Allow",
"Action": [
"dynamodb:DescribeStream",
"dynamodb:GetRecords",
"dynamodb:GetShardIterator",
"dynamodb:ListStreams"
],
"Resource": "arn:aws:dynamodb:ap-northeast-1:123456789012:table/customer_order_status/stream/*"
},
{
"Sid": "WriteLambdaLogs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:ap-northeast-1:123456789012:*"
}
]
}If event source mapping fails with Cannot access stream, attach this policy to the Lambda execution role used by customer-order-status-stream-worker.
Create event source mapping:
STREAM_ARN="$(aws dynamodb describe-table \
--table-name customer_order_status \
--region ap-northeast-1 \
--query 'Table.LatestStreamArn' \
--output text)"
aws lambda create-event-source-mapping \
--function-name customer-order-status-stream-worker \
--event-source-arn "${STREAM_ARN}" \
--starting-position LATEST \
--batch-size 100 \
--maximum-batching-window-in-seconds 5 \
--region ap-northeast-14. Best Practices#
idempotency:
consumer must handle duplicate delivery
use eventID / sequenceNumber / business version as dedupe key
batch failure:
enable partial batch response when possible
do not fail entire batch for one poison record
send failed records to DLQ / destination when supported
latency:
monitor IteratorAge
scale Lambda concurrency only after checking downstream capacity
payload:
choose stream view type intentionally
large items increase Lambda payload and processing cost
side effects:
use conditional writes when stream updates another DynamoDB table
avoid endless loop if consumer writes back to same table5. Monitoring#
| Signal | Why |
|---|---|
Lambda IteratorAge |
consumer lag |
Lambda Errors |
handler failure |
Lambda Throttles |
reserved concurrency or account limit |
Lambda Duration |
processing cost and timeout risk |
| DLQ messages | poison events or downstream failure |
Alert defaults:
IteratorAge:
P1 when >= 300s for 10m
Errors:
P1 when > 0 for 5m on critical stream
DLQ:
P1 when visible messages >= 16. Decision#
use DynamoDB Streams when:
consumer is close to the table
Lambda trigger is enough
24h-style short retention is acceptable
no independent fanout stream platform is required
use Kinesis Data Streams when:
multiple applications consume the same changes
consumers need independent read positions
retention / throughput / fanout control is important