What is DynamoDB?
Fully managed NoSQL database (key-value and document). Serverless - no instances to manage.
Key Terms
| Term | What it is |
|---|---|
| Table | Collection of items (like a table in SQL, but schema-less) |
| Item | Single record (like a row in SQL) |
| Attribute | Field within an item (each item can have different attributes) |
| Partition Key (PK) | Primary identifier - DynamoDB uses this to distribute data |
| Sort Key (SK) | Optional secondary identifier - enables range queries within a partition |
| Primary Key | Either PK alone, or PK + SK combined |
┌─────────────────────────────────────────────────────────────────┐
│ DynamoDB Table: Orders │
│ │
│ Primary Key: customer_id (PK) + order_date (SK) │
│ │
│ ┌─────────────┬─────────────┬──────────┬───────────┐ │
│ │ customer_id │ order_date │ total │ items │ │
│ │ (PK) │ (SK) │ │ │ │
│ ├─────────────┼─────────────┼──────────┼───────────┤ │
│ │ user123 │ 2026-01-01 │ 150.00 │ [...] │ │
│ │ user123 │ 2026-01-02 │ 75.50 │ [...] │ │
│ │ user456 │ 2026-01-01 │ 200.00 │ [...] │ │
│ └─────────────┴─────────────┴──────────┴───────────┘ │
│ │
│ Query: Get all orders for user123 → returns 2 items │
│ Query: Get user123's orders after 2026-01-01 → returns 1 item │
└─────────────────────────────────────────────────────────────────┘
RDS/Aurora vs DynamoDB
| Aspect | RDS/Aurora | DynamoDB |
|---|---|---|
| Type | Relational (SQL) | NoSQL (key-value) |
| Schema | Fixed (define columns upfront) | Flexible (each item can differ) |
| Scaling | Vertical (bigger instance) | Horizontal (automatic partitioning) |
| Queries | Any SQL query (JOINs, etc.) | Limited (by key only, no JOINs) |
| Transactions | Full ACID | Limited ACID (up to 100 items) |
| Management | You manage instance size | Fully serverless |
| Pricing | Per instance hour | Per request + storage |
What “Manage Instance Size” Means (RDS/Aurora)
DB instance (EC2) size - the compute. You choose db.r5.large, db.r5.2xlarge, etc.
Storage is separate:
- RDS: You provision EBS size (e.g., 100GB, can grow)
- Aurora: Storage auto-grows (no provisioning needed)
RDS: You manage both
┌─────────────────┐ ┌─────────────────┐
│ DB Instance │ │ EBS Volume │
│ (you choose │────►│ (you provision │
│ db.r5.large) │ │ 100GB) │
└─────────────────┘ └─────────────────┘
Aurora: You manage compute only
┌─────────────────┐ ┌─────────────────┐
│ DB Instance │ │ Shared Storage │
│ (you choose │────►│ (auto-grows, │
│ db.r5.large) │ │ AWS manages) │
└─────────────────┘ └─────────────────┘
RDS EBS resize: Online, no downtime. But 6-hour cooldown between resizes, can only increase.
DynamoDB Architecture
Completely different from RDS/Aurora - no instances, no storage you see.
┌─────────────────────────────────────────────────────────────────┐
│ DynamoDB (what you see) │
│ │
│ Just a table with items. No instances, no storage config. │
│ You only configure: capacity mode (on-demand or provisioned) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ DynamoDB (what AWS runs - hidden from you) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Request Router │ │
│ │ (receives your API calls) │ │
│ └────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Partition 1 │ │ Partition 2 │ │ Partition 3 │ │
│ │ (PK hash │ │ (PK hash │ │ (PK hash │ │
│ │ 0-33%) │ │ 34-66%) │ │ 67-100%) │ │
│ │ │ │ │ │ │ │
│ │ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │ │
│ │ │ Storage │ │ │ │ Storage │ │ │ │ Storage │ │ │
│ │ │ Node │ │ │ │ Node │ │ │ │ Node │ │ │
│ │ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │ │
│ │ (3 replicas │ │ (3 replicas │ │ (3 replicas │ │
│ │ across AZs) │ │ across AZs) │ │ across AZs) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ AWS adds/removes partitions automatically based on: │
│ - Data size (10GB per partition) │
│ - Throughput needs │
└─────────────────────────────────────────────────────────────────┘
Architecture Comparison
| RDS | Aurora | DynamoDB | |
|---|---|---|---|
| Compute | You choose instance | You choose instance | Hidden (serverless) |
| Storage | You provision EBS | Auto-grows (shared) | Hidden (auto-partitioned) |
| Scaling compute | Manual | Manual | Automatic |
| Scaling storage | Manual | Automatic | Automatic |
| You see | Instances + storage | Instances + storage | Just a table |
| Pricing | Instance hours + storage | Instance hours + storage + I/O | Requests + storage |
Replication & High Availability
| Feature | What it does |
|---|---|
| Built-in replication | Every item stored in 3 AZs (always on, no config) |
| Global Tables | Multi-region active-active replication |
Single Region (automatic):
┌─────────────────────────────────────────────────────────────────┐
│ us-east-1 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ AZ-a │ │ AZ-b │ │ AZ-c │ │
│ │ Replica │◄───►│ Replica │◄───►│ Replica │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ Write acknowledged after 2 of 3 replicas confirm │
└─────────────────────────────────────────────────────────────────┘
Global Tables (you enable):
┌─────────────────────┐ ┌─────────────────────┐
│ us-east-1 │ │ eu-west-1 │
│ ┌─────────────────┐ │ │ ┌─────────────────┐ │
│ │ Table (R/W) │◄┼─────┼►│ Table (R/W) │ │
│ └─────────────────┘ │ │ └─────────────────┘ │
└─────────────────────┘ └─────────────────────┘
│ │
└─── Both regions can ──────┘
read AND write
(active-active)
Active-Passive vs Active-Active
| Mode | Meaning |
|---|---|
| Active-Passive | One region handles writes, other is read-only standby |
| Active-Active | Both regions can handle writes simultaneously |
Active-Passive (Aurora Global Database):
┌─────────────────────┐ ┌─────────────────────┐
│ us-east-1 (Primary) │ │ eu-west-1 (Secondary)│
│ │ │ │
│ Read ✓ Write ✓ │────►│ Read ✓ Write ✗ │
│ │ │ (read-only) │
└─────────────────────┘ └─────────────────────┘
Active-Active (DynamoDB Global Tables):
┌─────────────────────┐ ┌─────────────────────┐
│ us-east-1 │ │ eu-west-1 │
│ │ │ │
│ Read ✓ Write ✓ │◄───►│ Read ✓ Write ✓ │
│ │ │ │
└─────────────────────┘ └─────────────────────┘
Why Aurora Can’t Be Active-Active
Relational DBs need strong consistency. Conflict resolution is complex with transactions, foreign keys, constraints.
Problem:
User in US: UPDATE accounts SET balance = 100 WHERE id = 1
User in EU: UPDATE accounts SET balance = 200 WHERE id = 1
(same row, same time)
Which one wins? Aurora's solution: Only one region accepts writes.
Why DynamoDB Can Be Active-Active
DynamoDB handles conflicts with “last writer wins” (timestamp-based):
User in US: item.status = "shipped" (timestamp: 100)
User in EU: item.status = "delivered" (timestamp: 101)
Both regions eventually have: status = "delivered" (higher timestamp wins)
Works because: No JOINs, no foreign keys, each item is independent.
Capacity Modes
| Mode | How it works | Best for |
|---|---|---|
| On-Demand | Pay per request, auto-scales | Unpredictable traffic, new apps |
| Provisioned | You set RCU/WCU, pay for capacity | Predictable traffic, cost optimization |
On-Demand:
Traffic spikes → DynamoDB handles it → Pay for what you used
Provisioned:
You set: 100 RCU, 50 WCU
Traffic exceeds → Throttled (unless Auto Scaling enabled)
Pay for provisioned capacity even if unused
RCU/WCU:
- RCU (Read Capacity Unit) = 1 strongly consistent read/sec (up to 4KB)
- WCU (Write Capacity Unit) = 1 write/sec (up to 1KB)
Secondary Indexes
Query data by attributes other than primary key.
| Index Type | What it does | Limit |
|---|---|---|
| GSI (Global Secondary Index) | New partition key + optional sort key | 20 per table |
| LSI (Local Secondary Index) | Same partition key, different sort key | 5 per table, must create at table creation |
Table: Orders
PK: customer_id
SK: order_date
Problem: "Find all orders with status=shipped"
→ Can't query by status (not in primary key)
Solution: GSI
GSI PK: status
GSI SK: order_date
→ Now can query by status
DynamoDB Streams
Capture item-level changes (insert, update, delete) in real-time.
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ DynamoDB Table │────►│ DynamoDB Stream │────►│ Lambda │
│ │ │ (24hr retention)│ │ (process changes│
│ INSERT/UPDATE/ │ │ │ │ in real-time) │
│ DELETE │ │ │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Use cases:
- Trigger Lambda on data changes
- Replicate to other systems (Elasticsearch, analytics)
- Audit log
- Cross-region replication (Global Tables uses this internally)
TTL (Time to Live)
Automatically delete expired items. Free (no WCU consumed).
{
"user_id": "123",
"session_token": "abc...",
"expires_at": 1704153600 // Unix timestamp - deleted after this
}
Use cases: Sessions, temporary data, logs with retention.
Backup & Recovery
| Type | What it does |
|---|---|
| On-demand backup | Manual snapshot, kept until you delete |
| PITR (Point-in-Time Recovery) | Continuous backup, restore to any second in 35 days |
| AWS Backup | Centralized backup management across services |
PITR Timeline:
─────────────────────────────────────────────────────────►
│ │ │
35 days ago Bad write Now
happened
│
▼
Restore to 1 second before bad write
DAX (DynamoDB Accelerator)
In-memory cache in front of DynamoDB. Microsecond latency.
Without DAX:
App → DynamoDB (1-10ms)
With DAX:
App → DAX Cache (microseconds) → DynamoDB (cache miss only)
When to use: Read-heavy workloads needing sub-millisecond latency.
DAX vs ElastiCache
| DAX | ElastiCache | |
|---|---|---|
| For | DynamoDB only | Any database (RDS, Aurora, etc.) |
| Integration | Drop-in (same DynamoDB API) | You write caching logic |
| Engines | Purpose-built for DynamoDB | Redis or Memcached |
| You manage | Cluster size | Cluster size, nodes |
ElastiCache: Managed in-memory cache service (Redis or Memcached). Use for RDS/Aurora caching, session storage, leaderboards, real-time analytics.
With ElastiCache (for RDS/Aurora):
┌─────────┐ ┌─────────────┐ ┌─────────────┐
│ App │────►│ ElastiCache │────►│ Aurora/RDS │
│ │◄────│ (fast, │ │ (only cache │
│ │ │ in-memory) │ │ misses) │
└─────────┘ └─────────────┘ └─────────────┘
Transactions
ACID transactions across multiple items (up to 100 items, 4MB).
# Transfer money: debit one account, credit another (atomic)
client.transact_write_items(
TransactItems=[
{'Update': {'TableName': 'Accounts', 'Key': {'id': 'A'},
'UpdateExpression': 'SET balance = balance - :amt'}},
{'Update': {'TableName': 'Accounts', 'Key': {'id': 'B'},
'UpdateExpression': 'SET balance = balance + :amt'}}
]
)
# Both succeed or both fail
Encryption
| Type | Details |
|---|---|
| At rest | Always on (AWS owned key, or your KMS key) |
| In transit | HTTPS (always) |
Feature Comparison: Aurora vs DynamoDB
| Feature | Aurora | DynamoDB |
|---|---|---|
| Built-in HA | 6 copies across 3 AZs | 3 copies across 3 AZs |
| Cross-region | Global Database | Global Tables |
| Cross-region mode | Active-passive | Active-active |
| Point-in-time recovery | Yes (35 days) | Yes (35 days) |
| Caching | ElastiCache (separate) | DAX (integrated) |
| Change capture | Binlog/CDC | Streams |
| Auto-scaling storage | Yes | Yes |
| Auto-scaling compute | No (manual instance change) | Yes (on-demand mode) |
When to Use DynamoDB vs RDS/Aurora
Use DynamoDB when:
- Simple access patterns (key-value lookups)
- Need massive scale (millions of requests/sec)
- Want serverless (no instance management)
- Unpredictable traffic
- Need active-active multi-region
Use RDS/Aurora when:
- Complex queries (JOINs, aggregations)
- Need full SQL
- Existing relational schema
- Strong consistency requirements
- Complex transactions