5 Things You Didn't Know Amazon DynamoDB Could Do
When most people think of DynamoDB, they think “fast NoSQL database” and stop there. But after working with DynamoDB for the past six years, I’ve discovered it’s capable of so much more than simple key-value storage. Today, I’m sharing five powerful DynamoDB features that even experienced AWS engineers often overlook.
1. Time Travel: Query Your Data From Any Point in the Past 35 Days
Yes, you read that right. DynamoDB has a built-in time machine.
Point-in-Time Recovery (PITR)
Point-in-Time Recovery lets you restore your table to any second within the last 35 days. But here’s the part most people miss: you don’t have to wait for a disaster to use it.
Real-world use case: A customer reported that their order status was incorrectly changed “sometime last week.” Instead of digging through application logs, I:
Enabled PITR (if not already enabled)
Restored the table to a new table at the suspected time
Queried the restored table to see the historical state
Found exactly when and how the data changed
How to Enable PITR
Via Console:
Open DynamoDB → Tables
Select your table
Backups tab → Point-in-time recovery
Click Edit → Enable
Via AWS CLI:
aws dynamodb update-continuous-backups \
--table-name Orders \
--point-in-time-recovery-specification PointInTimeRecoveryEnabled=trueHow to Restore to a Specific Time
aws dynamodb restore-table-to-point-in-time \
--source-table-name Orders \
--target-table-name Orders-2024-01-15-Investigation \
--restore-date-time 2024-01-15T14:30:00ZPro tip: You can restore to a new table without affecting your production data, making PITR perfect for investigations and debugging.
2. Build Event-Driven Architectures
DynamoDB Streams is like having a built-in change data capture (CDC) system that many developers don’t realize exists.
What Are DynamoDB Streams?
Every time you insert, update, or delete an item in DynamoDB, Streams can capture that change and trigger downstream actions. Think of it as a built-in event bus specifically for your database changes.
Real-World Use Case: Audit Logging
Here’s a pattern I use constantly:
DynamoDB Table → DynamoDB Stream → Lambda → S3 (audit logs)
→ EventBridge → Multiple consumersEvery change to sensitive data automatically creates an immutable audit trail.
Setting Up Streams
Enable Streams on your table:
aws dynamodb update-table \
--table-name Users \
--stream-specification \
StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGESStreamViewType options:
KEYS_ONLY- Only the key attributesNEW_IMAGE- The entire item after the changeOLD_IMAGE- The entire item before the changeNEW_AND_OLD_IMAGES- Both before and after (recommended)
Lambda Function to Process Streams
import json
import boto3
def lambda_handler(event, context):
for record in event[’Records’]:
if record[’eventName’] == ‘INSERT’:
new_item = record[’dynamodb’][’NewImage’]
print(f”New user created: {new_item}”)
elif record[’eventName’] == ‘MODIFY’:
old_item = record[’dynamodb’][’OldImage’]
new_item = record[’dynamodb’][’NewImage’]
# Detect specific field changes
if old_item[’email’] != new_item[’email’]:
send_email_change_notification(new_item)
elif record[’eventName’] == ‘REMOVE’:
old_item = record[’dynamodb’][’OldImage’]
print(f”User deleted: {old_item}”)
return {’statusCode’: 200}Connect Lambda to the Stream
aws lambda create-event-source-mapping \
--function-name ProcessUserChanges \
--event-source-arn arn:aws:dynamodb:us-east-1:123456789012:table/Users/stream/2024-01-15T00:00:00.000 \
--starting-position LATEST \
--batch-size 100Powerful Patterns I’ve Built With Streams
Materialized Views: Automatically update a denormalized table when source data changes
Cache Invalidation: Clear CloudFront or ElastiCache when data updates
Cross-Region Replication: Build custom replication logic
Real-time Analytics: Stream changes to Kinesis Data Analytics or OpenSearch
Data Validation: Detect and alert on suspicious changes
Important: Streams retain data for 24 hours, and you get them included with your DynamoDB table—no extra charge for the stream itself, only for the Lambda processing.
3. Query DynamoDB with SQL
Remember when you had to learn DynamoDB’s query syntax and couldn’t use SQL? Those days are over.
PartiQL: SQL for DynamoDB
PartiQL lets you use familiar SQL syntax to query DynamoDB. It’s built right into the AWS SDK and console.
Traditional DynamoDB Query
import boto3
dynamodb = boto3.resource(’dynamodb’)
table = dynamodb.Table(’Orders’)
response = table.query(
KeyConditionExpression=’customerId = :cid AND orderDate > :date’,
ExpressionAttributeValues={
‘:cid’: ‘CUST-123’,
‘:date’: ‘2024-01-01’
}
)Same Query with PartiQL
import boto3
dynamodb = boto3.client(’dynamodb’)
response = dynamodb.execute_statement(
Statement=”“”
SELECT * FROM Orders
WHERE customerId = ? AND orderDate > ?
“”“,
Parameters=[’CUST-123’, ‘2024-01-01’]
)When to Use PartiQL
Good for:
Ad-hoc queries in the console
Developers transitioning from SQL databases
Quick prototyping
Batch operations
Stick with native API for:
High-performance production code (slightly faster)
Complex conditional expressions
When you need fine-grained control
Pro tip: You can use PartiQL directly in the DynamoDB console. Go to your table → Explore table items → Query or Scan → Switch to PartiQL editor.
4. Export Terabytes to S3 Without Impacting Performance
Need to run analytics on your DynamoDB data? Most people think they have to scan the entire table (expensive and slow) or set up complex ETL pipelines. There’s a better way.
DynamoDB Export to S3
This feature lets you export your entire table (or a point-in-time snapshot) to S3 without consuming any read capacity. Zero impact on your production workload.
How to Export
aws dynamodb export-table-to-point-in-time \
--table-arn arn:aws:dynamodb:us-east-1:123456789012:table/Orders \
--s3-bucket my-analytics-bucket \
--s3-prefix dynamodb-exports/orders/ \
--export-format DYNAMODB_JSONOr use the newer ION format (more compact):
--export-format IONWhat You Get
The export creates files in S3 that you can query with:
Amazon Athena (my favorite)
AWS Glue
Amazon EMR
Amazon Redshift Spectrum
Any tool that reads S3
Real-World Example: Analytics Pipeline
Here’s how I built a cost-effective analytics pipeline:
Export DynamoDB to S3 daily (automated with EventBridge + Lambda)
Catalog with AWS Glue Crawler
Query with Athena
Pro tip: Use the new PARQUET export format for much better Athena performance and lower costs.
Use Cases
Monthly reporting without impacting production
Training ML models on historical data
Compliance exports for audit
Migrating data to a data warehouse
Long-term archival (cheaper than DynamoDB storage)
5. Build a Distributed Task Queue (With Automatic Cleanup)
This one surprised me when I first discovered it: you can use DynamoDB as a lightweight task queue or job scheduler with built-in automatic cleanup.
Time To Live (TTL) + Streams = Task Queue
The combination of TTL (Time To Live) and Streams creates a powerful pattern for scheduled tasks.
How It Works
Write items to DynamoDB with a TTL attribute
DynamoDB automatically deletes expired items (usually within 48 hours, but often faster)
Stream captures the DELETE event
Lambda processes the “expired” item as a task
Real-World Use Case: Reminder System
I built a reminder system for a SaaS app that sends notifications at scheduled times:
Step 1: Enable TTL
aws dynamodb update-time-to-live \
--table-name Reminders \
--time-to-live-specification \
“Enabled=true, AttributeName=expiresAt”Step 2: Write Reminders
import boto3
from datetime import datetime, timedelta
dynamodb = boto3.resource(’dynamodb’)
table = dynamodb.Table(’Reminders’)
# Send reminder in 1 hour
expire_time = int((datetime.now() + timedelta(hours=1)).timestamp())
table.put_item(Item={
‘reminderId’: ‘REM-123’,
‘userId’: ‘USER-456’,
‘message’: ‘Meeting in 1 hour’,
‘expiresAt’: expire_time # Unix timestamp
})Step 3: Process Expiring Items
import boto3
import json
sns = boto3.client(’sns’)
def lambda_handler(event, context):
for record in event[’Records’]:
# Only process DELETE events from TTL expiration
if (record[’eventName’] == ‘REMOVE’ and
‘userIdentity’ in record and
record[’userIdentity’].get(’type’) == ‘Service’ and
record[’userIdentity’].get(’principalId’) == ‘dynamodb.amazonaws.com’):
# This was a TTL deletion
reminder = record[’dynamodb’][’OldImage’]
user_id = reminder[’userId’][’S’]
message = reminder[’message’][’S’]
# Send the reminder
send_reminder(user_id, message)
print(f”Reminder sent to {user_id}”)
return {’statusCode’: 200}
def send_reminder(user_id, message):
# Send via SNS, SES, or your notification service
sns.publish(
TopicArn=’arn:aws:sns:us-east-1:123456789012:UserNotifications’,
Message=message,
MessageAttributes={
‘userId’: {’DataType’: ‘String’, ‘StringValue’: user_id}
}
)Important TTL Considerations
⚠️ TTL is not precise: Items are typically deleted within 48 hours of expiration, but often much faster (minutes to hours). Don’t use this for time-critical operations.
✅ TTL is free: No additional cost for the deletions
✅ Check expiration in your app: Always check if an item is expired when reading:
response = table.get_item(Key={’id’: ‘123’})
item = response.get(’Item’)
if item:
if item[’expiresAt’] < int(datetime.now().timestamp()):
# Item has expired but not yet deleted by TTL
return None
return itemWhen NOT to Use This Pattern
Time-critical tasks (use EventBridge Scheduler instead)
High-precision scheduling (use Step Functions)
Very high throughput (use SQS)
But for many use cases, DynamoDB TTL + Streams is a simple, cost-effective solution that requires no additional infrastructure.
Conclusion
DynamoDB is far more than a simple key-value store. These five features transform it into:
A time-machine database (PITR)
An event streaming platform (Streams)
A SQL-queryable datastore (PartiQL)
An analytics data source (Export to S3)
A task scheduler (TTL + Streams)
The best part? Most of these features cost little to nothing extra and are just waiting to be enabled in your existing tables.


The PITR feature for investigatons is brilliant. I've wasted hours digging through logs when I could have just spun up a point in time snapshot. The TTL plus Streams pattern for task queues is clever to, though I agree the 48 hour window makes it unsuitable for anything time sensitive. Have you run into any gotchas with the export to S3 feature at really large scale?