Amazon DynamoDB¶
1. What is DynamoDB?¶
DynamoDB is a fully managed, serverless, multi-model NoSQL database built for internet-scale workloads requiring single-digit millisecond latency at any volume. You provision no servers, manage no OS, and write no patching scripts — only design your data model and interact via API.
Core trade-off:
RDS: SQL + ACID + complex queries → structured, relational data
DynamoDB: Flexible schema + speed → unstructured, high-volume, low latency
2. Core Data Model¶
Table
└── Item (one record — equivalent to a row)
├── Attribute: { "user_id": "123" } ← Partition Key (required)
├── Attribute: { "timestamp": "2026-…" } ← Sort Key (optional)
├── Attribute: { "name": "Ibtisam" }
└── Attribute: { "tags": ["aws","k8s"] } ← can differ per item (schema-less)
| Concept | RDS Equivalent | DynamoDB |
|---|---|---|
| Database | Database | Table |
| Row | Record | Item |
| Column | Field | Attribute |
| Primary Key | Primary Key | Partition Key (+ optional Sort Key) |
| Index | Index | GSI / LSI |
| Max row size | Varies | 400 KB per item |
3. Primary Key Types ⭐¶
Simple Primary Key (Partition Key Only)¶
Table: Users
PK: user_id
Items:
{ "user_id": "u-001", "name": "Ibtisam" }
{ "user_id": "u-002", "name": "Ali" }
Query: GetItem(user_id="u-001") → exact match only
Composite Primary Key (Partition Key + Sort Key)¶
Table: Orders
PK: user_id (partition key)
SK: order_date (sort key)
Items in same partition (same user):
{ "user_id": "u-001", "order_date": "2026-01-01", "amount": 99 }
{ "user_id": "u-001", "order_date": "2026-03-15", "amount": 150 }
{ "user_id": "u-001", "order_date": "2026-04-01", "amount": 200 }
Query: all orders for user u-001 in 2026
→ KeyConditionExpression: user_id = "u-001" AND order_date BETWEEN "2026-01-01" AND "2026-12-31"
Partition key determines physical placement (via hash function). Sort key determines order within the partition (sorted by value). Composite key enables range queries within a partition — essential design pattern.
4. Partitioning — How DynamoDB Scales ⭐¶
DynamoDB hashes partition key → determines which physical partition stores the item
Each partition: max 10 GB data, max 3,000 RCUs, max 1,000 WCUs
Table grows → DynamoDB splits partitions automatically (no downtime)
Hot Partition Problem ⭐¶
Bad design: status as partition key
"PENDING": 10,000 writes/sec → all hit one partition → throttled ❌
"COMPLETE": 100 writes/sec
"FAILED": 10 writes/sec
Good design: user_id or order_id as partition key (high cardinality)
user-001: 5 writes/sec
user-002: 3 writes/sec
user-003: 8 writes/sec
→ evenly distributed across partitions ✅
Partition key best practices:
- Use high cardinality keys: user IDs, order IDs, session IDs
- Avoid low cardinality: boolean flags, status codes, dates, country codes
- If forced to use low-cardinality key: add random suffix (
STATUS#PENDING#7) then scatter-gather query across all suffixes on read
5. Data Types¶
| Category | Types |
|---|---|
| Scalar | String (S), Number (N), Binary (B), Boolean (BOOL), Null (NULL) |
| Set | String Set (SS), Number Set (NS), Binary Set (BS) — all items same type, no duplicates |
| Document | List (L) — ordered, any types; Map (M) — key-value, any types |
{
"user_id": { "S": "u-001" },
"age": { "N": "25" },
"active": { "BOOL": true },
"tags": { "SS": ["aws", "k8s", "devops"] },
"address": {
"M": {
"city": { "S": "Rawalpindi" },
"zip": { "S": "46000" }
}
},
"scores": { "L": [{ "N": "95" }, { "N": "87" }] }
}
6. Capacity Modes ⭐¶
On-Demand (Pay Per Request)¶
No capacity planning — DynamoDB scales instantly
Pay: per actual read/write request
Use when: unpredictable traffic, new applications, spiky workloads
Cost: ~2.5× more expensive per request than provisioned
Provisioned (Fixed Capacity)¶
You set: RCUs (read capacity units) per second
WCUs (write capacity units) per second
Pay: per provisioned RCU/WCU per hour regardless of usage
Use when: predictable, steady workloads
Add: Auto Scaling to adjust provisioned capacity based on utilization
7. RCU / WCU Calculations ⭐¶
Write Capacity Unit (WCU)¶
1 WCU = 1 write per second for item up to 1 KB
Formula:
WCU = writes_per_second × CEIL(item_size_KB / 1 KB)
Examples:
Item = 0.5 KB, 100 writes/sec → 100 × CEIL(0.5) = 100 × 1 = 100 WCU
Item = 1.5 KB, 100 writes/sec → 100 × CEIL(1.5) = 100 × 2 = 200 WCU
Item = 3 KB, 50 writes/sec → 50 × CEIL(3) = 50 × 3 = 150 WCU
Read Capacity Unit (RCU)¶
1 RCU = 1 strongly consistent read per second for item up to 4 KB
OR
0.5 RCU = 1 eventually consistent read per second for item up to 4 KB
Formula:
RCU (strong) = reads_per_second × CEIL(item_size_KB / 4 KB)
RCU (eventual) = reads_per_second × CEIL(item_size_KB / 4 KB) × 0.5
Examples:
Item = 4 KB, 100 strongly consistent reads/sec → 100 × 1 = 100 RCU
Item = 4 KB, 100 eventually consistent reads/sec → 100 × 0.5 = 50 RCU
Item = 6 KB, 50 strongly consistent reads/sec → 50 × CEIL(6/4) = 50 × 2 = 100 RCU
Item = 10 KB, 80 eventually consistent reads/sec → 80 × CEIL(10/4) × 0.5 = 80 × 3 × 0.5 = 120 RCU
Transactional reads/writes consume 2× RCU/WCU — each transactional operation uses double the capacity of a standard operation.
8. Consistency Models ⭐¶
Default: Eventually Consistent (faster, costs 0.5 RCU)
→ Read may return data that is milliseconds behind
→ Use for: high-throughput read workloads, leaderboards, caches
Strongly Consistent (slower, costs 1 RCU)
→ Always returns latest committed data
→ Use for: financial balances, inventory, any "must be latest" data
→ Add --consistent-read flag in API call
Not available for:
→ Global Tables (always eventually consistent cross-region)
→ GSI reads (always eventually consistent)
9. Secondary Indexes ⭐¶
Local Secondary Index (LSI)¶
Same partition key as base table, DIFFERENT sort key
Must be created AT TABLE CREATION — cannot add later
Max: 5 LSIs per table
Storage: shares table storage
Reads: supports strongly consistent reads
Table: Orders (PK: user_id, SK: order_date)
LSI: Orders-by-amount (PK: user_id, SK: amount)
Query: "get all orders for user u-001 sorted by amount"
→ Use LSI (same partition key = user_id)
Global Secondary Index (GSI)¶
DIFFERENT partition key + optional different sort key
Can be added/deleted at ANY TIME (even after table creation)
Max: 20 GSIs per table
Storage: separate storage (own RCU/WCU)
Reads: eventually consistent only
Table: Orders (PK: user_id, SK: order_date)
GSI: Orders-by-status (PK: status, SK: order_date)
Query: "get all PENDING orders sorted by date"
→ Cannot do on base table (status not a key)
→ Use GSI (new partition = status) ✅
GSI Projection Types¶
| Type | What is copied to GSI | Storage cost |
|---|---|---|
KEYS_ONLY | Only PK + SK + GSI key | Lowest |
INCLUDE | Keys + specified attributes | Medium |
ALL | All attributes | Highest (= full item copy) |
LSI vs GSI Comparison¶
| Feature | LSI | GSI |
|---|---|---|
| Partition key | Same as table | Different |
| Create timing | At table creation only | Anytime |
| Strongly consistent reads | ✅ Yes | ❌ No (eventually consistent) |
| Storage | Shared with table | Separate |
| Capacity | Uses table RCU/WCU | Own RCU/WCU |
| Max count | 5 | 20 |
10. Query vs Scan ⭐¶
| Operation | Uses Index | Reads | Cost |
|---|---|---|---|
| GetItem | Exact PK lookup | Single item | Very cheap |
| Query | Partition key required | Items in one partition | Cheap |
| Scan | No index used | Entire table | ❌ Expensive |
| BatchGetItem | Up to 100 exact PK lookups | Multiple items | Cheap |
Scan is dangerous:
Table: 50 million items × 400 KB max = up to 20 TB scan
→ Consumes massive RCUs
→ Can throttle production traffic
→ Use FilterExpression to reduce returned data (but scan still reads ALL items)
Design principle:
"Design your table around your access patterns — never design and then query"
→ Know your queries first → then design PK/SK/GSI to serve those queries
Expression Types¶
| Expression | Purpose |
|---|---|
KeyConditionExpression | Filter by PK and SK (Query only) |
FilterExpression | Filter after reading (Scan or Query) |
ProjectionExpression | Return only specific attributes |
ConditionExpression | Conditional write — only write IF condition is true |
UpdateExpression | Define what to update in UpdateItem |
11. Transactions ⭐¶
DynamoDB supports full ACID transactions across multiple tables and items:
# All-or-nothing: both writes succeed or both fail
dynamodb.transact_write_items(
TransactItems=[
{
'Put': {
'TableName': 'Orders',
'Item': {'order_id': {'S': 'o-123'}, 'status': {'S': 'CONFIRMED'}}
}
},
{
'Update': {
'TableName': 'Inventory',
'Key': {'product_id': {'S': 'p-456'}},
'UpdateExpression': 'SET stock = stock - :val',
'ConditionExpression': 'stock >= :val',
'ExpressionAttributeValues': {':val': {'N': '1'}}
}
}
]
)
# If inventory check fails (stock < 1) → entire transaction rolls back
Cost: Transactions consume 2× RCU/WCU per operation vs standard. Limit: Up to 100 items or 4 MB per transaction.
12. DynamoDB Streams ⭐¶
Captures a time-ordered log of every change (INSERT / MODIFY / REMOVE) to items:
Insert/Update/Delete item
→ Event written to Stream (retention: 24 hours)
→ Lambda trigger reads Stream
→ Process event: audit log, replication, search index update, notifications
Stream record contains:
KEYS_ONLY: Only key attributes
NEW_IMAGE: New item state after change
OLD_IMAGE: Old item state before change
NEW_AND_OLD_IMAGES: Both states (most common — enables delta comparison)
Use cases: - Cross-region replication (foundation of Global Tables) - Triggers: send email on order status change - Search: sync changes to Elasticsearch/OpenSearch - Audit trail: every change logged immutably
13. DynamoDB Accelerator (DAX) ⭐¶
In-memory cache purpose-built for DynamoDB — reduces read latency from milliseconds to microseconds:
Without DAX:
App → DynamoDB API → 1–10ms latency per read
With DAX:
App → DAX Cluster (cache) → ~microseconds if cached
→ DynamoDB if cache miss (~1–10ms)
DAX is a cluster (primary + replicas) deployed in your VPC
→ Only accessible within VPC (not public)
→ Drop-in replacement: change endpoint in SDK, code stays same
Write-through cache:
Write → DAX → DynamoDB → both updated
→ Reads immediately see updated data from cache
| Property | Value |
|---|---|
| Latency | Microseconds (cache hit) |
| Access | VPC only (in-VPC cluster) |
| Use case | Read-heavy, repeated same item reads (product catalog, gaming) |
| Does NOT help | Write-heavy workloads, strongly consistent reads (bypasses cache) |
14. Global Tables ⭐¶
Active-active multi-Region replication — same table writable in multiple Regions:
Table: Users (Global Table)
us-east-1 replica: App writes user-001 ← write here
eu-west-1 replica: EU users read/write ← replicated from us-east-1
ap-southeast-1: APAC users read/write
Replication: DynamoDB Streams-powered, async, typically < 1 second
Active-active: any region can accept writes
→ Last-writer-wins for conflict resolution (based on timestamp)
Requirements:
- DynamoDB Streams enabled with
NEW_AND_OLD_IMAGES - All replicas: same table name, same partition key, same capacity settings
- Replicas must be empty when adding a new Region
Use case: Global apps where multi-region read AND write latency matters.
15. Backup and Restore¶
On-Demand Backup¶
Manual snapshot → full table backup
Stored indefinitely (until deleted)
Restores to new table (does not overwrite existing)
No RCU consumption during backup
Point-in-Time Recovery (PITR)¶
Enable PITR → DynamoDB continuously backs up table
Restore to any second in last 35 days
Restore creates new table — original table untouched
No performance impact on production table
16. TTL (Time To Live) ⭐¶
Automatically deletes expired items — no WCU consumed for deletions:
Add attribute: "expires_at": 1780000000 (Unix timestamp)
Enable TTL on table, point to "expires_at"
DynamoDB: periodically scans for expired items → deletes them within ~48 hours
Important:
- Not real-time — deletion can be up to 48 hours after expiry
- Expired-but-not-deleted items still returned in reads until deleted
- Add FilterExpression to exclude expired items if needed:
FilterExpression: "expires_at > :now"
Use cases: sessions, OTPs, cache, temporary tokens, rate limiting counters
17. DynamoDB Table Classes¶
| Class | Storage cost | Use Case |
|---|---|---|
| Standard | $0.25/GB/month | Frequently accessed data |
| Standard-Infrequent Access (IA) | $0.10/GB/month | Rarely accessed, large tables |
Standard-IA has higher read/write costs but 60% lower storage cost. Use for tables with large data but infrequent access (audit logs, historical data).
18. PartiQL — SQL-Like Interface¶
DynamoDB supports PartiQL — a SQL-compatible query language (SELECT/INSERT/UPDATE/DELETE):
-- PartiQL query
SELECT * FROM Orders WHERE user_id = 'u-001' AND order_date > '2026-01-01'
-- Still uses partition key internally — no full table scans without PK
-- Easier for developers who know SQL but want DynamoDB's scale
19. Access Control — Fine-Grained IAM¶
IAM conditions allow restricting access to specific items within a table:
{
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:PutItem"],
"Resource": "arn:aws:dynamodb:us-east-1:123:table/UserData",
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": ["${aws:userid}"]
}
}
}
This IAM policy only lets a user access items where the partition key equals their own user ID. Users cannot read each other's data. This is fine-grained access control — a key DynamoDB security feature.
20. DynamoDB vs RDS — When to Use Which ⭐¶
| Need | Use |
|---|---|
| Complex JOINs across multiple tables | RDS |
| Strict ACID transactions | RDS (or DynamoDB transactions for simpler cases) |
| Flexible schema, items differ per record | DynamoDB |
| Millions of requests/sec with ms latency | DynamoDB |
| Known, stable access patterns | DynamoDB (design table for patterns) |
| Ad-hoc queries and reporting | RDS |
| Serverless, no capacity planning | DynamoDB on-demand |
| Gaming leaderboards, session data, IoT | DynamoDB |
| Financial records, ERP, CRM | RDS |
21. Common Mistakes¶
| ❌ Wrong | ✅ Correct |
|---|---|
| DynamoDB uses SQL | DynamoDB uses API operations (GetItem, Query, Scan) + PartiQL as optional interface |
| Scan is equivalent to Query | Scan reads the entire table — extremely expensive at scale |
| GSI supports strongly consistent reads | GSI reads are always eventually consistent |
| LSI can be added after table creation | LSI must be created at table creation only |
| DAX caches all reads | DAX does NOT cache strongly consistent reads |
| TTL deletes items immediately | TTL deletion happens within up to 48 hours of expiry |
| Transactions are free | Transactions consume 2× RCU/WCU per operation |
| Any attribute can be partition key | Partition key must be high cardinality — low cardinality creates hot partitions |
| DynamoDB is eventually consistent always | Strongly consistent reads available (but cost 2× RCU and not for GSI/Global Tables) |
| Global Tables = read-only replicas | Global Tables are active-active — all replicas accept writes |
22. Interview Questions Checklist¶
- DynamoDB vs RDS — when do you choose each?
- What is a partition key? Why does cardinality matter?
- Simple vs composite primary key — example of when to use composite?
- Calculate RCU for: 6 KB item, 100 strongly consistent reads/sec
- Calculate WCU for: 3.5 KB item, 200 writes/sec
- Eventually consistent vs strongly consistent — cost difference, when to use
- LSI vs GSI — 5 differences (creation time, consistency, storage, PK, count)
- What is a hot partition? How do you fix it?
- Query vs Scan — why is Scan dangerous at scale?
- What are the five expression types in DynamoDB?
- Transactions — what do they cost in RCU/WCU? Max items?
- DynamoDB Streams — what does NEW_AND_OLD_IMAGES give you?
- DAX — what does it cache? What does it NOT cache?
- Global Tables — replication model? Conflict resolution? Requirements?
- TTL — when are items actually deleted?
- On-demand vs provisioned — when do you choose each?
- Fine-grained IAM — how do you restrict a user to their own items only?
- What is PITR? Retention window?