Amazon DynamoDB
LearningTree · AWS · Database

Amazon DynamoDB —
Serverless NoSQL at Any Scale

DynamoDB is not just a NoSQL database — it is a paradigm shift. You stop designing tables and start designing access patterns. Single-digit millisecond latency at any scale, serverless with no capacity planning, and a key-value/document model purpose-built for cloud-native applications at massive throughput.

⚡ DynamoDB in 30 Seconds

  • Fully managed, serverless NoSQL — no instances, no patching, no capacity planning
  • Single-digit millisecond read/write latency at any scale
  • Key-value and document model — items accessed by partition key (+ optional sort key)
  • Scales to unlimited throughput and storage automatically
  • On-demand mode: pay per request — zero idle cost
  • Built-in replication across 3 AZs in every region — always on
01
Chapter One

What is Amazon DynamoDB

The Problem — When SQL Becomes the Bottleneck Introductory

Relational databases are exceptional at complex queries across normalised tables. But they have a structural problem at massive scale: every write must go through a single writer node, joins across large tables become expensive, and schemas are rigid. When your application needs to handle millions of requests per second with predictable single-digit millisecond latency, a traditional RDBMS hits a ceiling.

👉 The core problem SQL can't easily solve: A chat application sending 2 million messages per second. A gaming leaderboard with 50 million players. An IoT platform ingesting 100,000 sensor readings per second. These workloads need horizontal write scaling and predictable latency — which SQL architectures must be heavily engineered to provide. DynamoDB is designed for exactly this.

What is Amazon DynamoDB Introductory

Amazon DynamoDB is a fully managed, serverless NoSQL database. There are no servers to manage, no storage to provision, no replication to configure. You create a table, define a key, and DynamoDB handles everything else — from partitioning data across thousands of storage nodes to replicating across AZs to scaling throughput up and down automatically.

Performance

Single-digit millisecond read and write latency — consistently — regardless of whether your table has 1,000 items or 1 billion items.

💾

Key-Value + Document

Items are accessed by a key (partition key ± sort key). Each item can hold arbitrary attributes — no fixed schema per row.

🧱

Serverless Scale

No instances to size, no capacity to pre-provision in on-demand mode. DynamoDB scales throughput and storage automatically as your workload grows.

DynamoDB vs Relational — The Core Mental Shift Core

This is not just a technology difference — it is a design philosophy difference. The table below shows the fundamental shifts you must internalise before anything else in DynamoDB makes sense:

Aspect Relational (RDS / Aurora) DynamoDB
Data model Tables, rows, columns, foreign keys Tables, items, attributes — no joins
Schema Fixed — all rows same columns Flexible — each item can differ
Query language SQL — arbitrary queries Key-based lookups — no ad-hoc queries
Design first Normalise schema, then query it Define access patterns, then model data
Joins Yes — JOIN across tables No joins — denormalise data
Write scaling Single writer (bottleneck) Horizontally partitioned writes
Managed Managed (RDS) but still instances Serverless — no instances at all
🧠 Mental Model — The Most Important Idea in DynamoDB Introductory

Most People Think (Wrong):

“DynamoDB is just like a SQL table but without SQL. I'll design my schema and add queries later.”

✨ Correct Model:

In DynamoDB you design your access patterns first, then design your keys and table structure to serve those patterns. The question is never "what does my data look like?" — it is "how will my application read and write this data?" Build the query, then build the schema to answer it in one key lookup.

🛑

SQL Thinking (Wrong for DynamoDB)

  • Design normalised tables first
  • Add foreign keys and relationships
  • Write queries after schema is set
  • Let SQL handle flexible retrieval
  • Assume you can add any query later

DynamoDB Thinking (Correct)

  • List every query your app needs
  • Design key structure for those exact queries
  • Denormalise — duplicate data for fast access
  • One table can serve multiple entity types
  • Schema evolves per item, not per table
💡

Why This Matters

  • Every DynamoDB operation must use a key
  • No key = full table scan = expensive + slow
  • Good key design = every read hits one partition
  • Bad key design = hot partitions = throttling
  • The key IS the architecture
Concept Diagram — Key-Value vs Relational Model Core
DynamoDB model — items accessed by key; no joins; flexible attributes per item
RELATIONAL — FIXED SCHEMA, JOINS id | name | email | plan | created_at 1 | Alice | alice@.. | pro | 2024-01 2 | Bob | bob@... | free | 2024-02 3 | Carol | carol@. | pro | 2024-03 JOIN orders: id | user_id | amount | status 10 | 1 | $99 | shipped 11 | 1 | $49 | pending SELECT * FROM users u JOIN orders o ON u.id = o.user_id WHERE u.id = 1 DYNAMODB — KEY LOOKUP, NO JOINS PK: USER#1 name=Alice, email=alice@.., plan=pro PK: USER#1 SK: ORDER#10 amount=$99, status=shipped PK: USER#1 SK: ORDER#11 amount=$49, status=pending Query: PK = "USER#1" Returns user + all orders in 1 request ← single-digit ms • no JOIN needed
AWS Architecture Diagram — DynamoDB in a Serverless Stack Core
DynamoDB as the data layer for a serverless API — the most common architecture
API GW
API Gateway
REST / HTTP API
Routes requests
Lambda
Lambda
Business logic
Auth + validation
DynamoDB
DynamoDB
GetItem / Query
PutItem / UpdateItem
<5ms latency
No servers • No patching • No capacity planning • Pay per request • 3-AZ replication always on
When to Choose DynamoDB Core

DynamoDB is the Right Choice When

  • Need single-digit ms latency at any scale
  • Traffic is unpredictable or massively spiky
  • Workload is key-based lookups — get user, get order, get session
  • Application is event-driven or serverless (Lambda)
  • No complex multi-table JOINs required
  • Scale to millions of requests/sec without rearchitecting
  • Session stores, shopping carts, leaderboards, IoT, chat
📌

Stick with SQL When

  • Your app relies on complex ad-hoc SQL queries across multiple tables
  • Strong relational integrity (foreign keys, constraints) is essential
  • Reporting and analytics with GROUP BY, SUM, JOIN at query time
  • Access patterns are not yet defined (exploratory data analysis)
  • Team is unfamiliar with access-pattern-first design
  • OLAP / data warehouse workloads (use Redshift instead)
The Database Triangle — Where DynamoDB Fits Core
Need Use Why
Relational, managed, standard RDS SQL, ACID, patching managed
Relational, high performance / global Aurora Shared storage, 15 replicas, Global DB
Key-value / document, massive scale DynamoDB Serverless, ms latency, unlimited scale
Sub-ms caching ElastiCache Redis / Memcached front-end cache
DynamoDB + microsecond caching DynamoDB + DAX In-memory cache purpose-built for DynamoDB
🧠 Key Insight

DynamoDB is not a NoSQL version of RDS. It is a fundamentally different tool for a fundamentally different problem. When your access patterns are key-based lookups at massive scale, DynamoDB is unmatched. The moment you find yourself wanting JOINs, you are either on the wrong tool or you haven't yet modelled your DynamoDB table correctly.

Chapter Summary Introductory
  • DynamoDB = serverless NoSQL — no instances, no patching, scales automatically
  • Key-value + document model: items accessed by partition key (± sort key), flexible attributes
  • Access patterns first: design your queries before you design your keys — this is the defining shift
  • No joins: denormalise data, store what you need per access pattern
  • Single-digit ms latency at any scale: 1 item or 1 billion items — same response time
  • When NOT to use: ad-hoc SQL, complex JOINS, undefined access patterns, analytics/OLAP
02
Chapter Two

Core Concepts — Tables, Items, Keys & Attributes

The Building Blocks of DynamoDB Introductory

DynamoDB has just a few core concepts. Once you understand tables, items, attributes, partition keys, and sort keys, you have the vocabulary for everything else. The simplicity is intentional — DynamoDB trades complex query capability for predictable performance at any scale.

🗃️

Table

  • A collection of items — like a SQL table
  • Each table has a primary key (defined at creation)
  • Tables are region-scoped (or global with Global Tables)
  • No fixed schema — each item can have different attributes
  • Capacity can be provisioned or on-demand
📄

Item

  • A single record in a table — like a SQL row
  • Must have a primary key (PK or PK+SK)
  • Max item size: 400 KB
  • Can contain any number of attributes
  • Each item can have different attributes from other items
📝

Attribute

  • A key-value pair within an item — like a SQL column
  • Types: String, Number, Binary, Boolean, Null, List, Map, Set
  • Nested attributes supported (Maps, Lists)
  • No predefined columns — fully flexible per item
  • Only the primary key is required
Primary Key — The Core of Every Item Core

Every DynamoDB item is uniquely identified by its primary key. There are two types of primary key: simple (partition key only) or composite (partition key + sort key). Choosing the right primary key is the most important design decision you will make.

🔑

Simple Primary Key (PK only)

  • Single attribute called partition key (PK)
  • Each item uniquely identified by PK alone
  • Example: userId = "U123"
  • Use when: one item per entity (one user, one product)
  • GetItem: provide PK → returns exactly one item
🗝️

Composite Primary Key (PK + SK)

  • Two attributes: partition key (PK) + sort key (SK)
  • PK + SK together must be unique
  • Example: PK = "USER#123", SK = "ORDER#456"
  • Use when: multiple related items per entity (user + orders)
  • Query: PK + SK condition → returns multiple sorted items
Concept Diagram — Simple vs Composite Primary Key Core
Simple primary key (PK only) vs Composite primary key (PK + SK)
SIMPLE PRIMARY KEY (PK ONLY) PK (partition key) attributes... userId: U001 name=Alice, email=alice@.. userId: U002 name=Bob, plan=pro userId: U003 name=Carol, age=30 GetItem: PK="U001" → returns 1 item COMPOSITE PRIMARY KEY (PK + SK) PK SK (sort key) attrs USER#1 PROFILE name=Alice USER#1 ORDER#001 $99 USER#1 ORDER#002 $49 USER#2 PROFILE name=Bob Query: PK="USER#1" → returns 3 items sorted by SK
Partition Key vs Sort Key — The Difference Core
Aspect Partition Key (PK) Sort Key (SK)
Required? Always required Optional
Purpose Determines which partition stores the item Sorts items within the same partition
Query condition Must be exact match (=) Can use ranges (=, <, >, begins_with)
Data grouping Groups related items together Orders items within the group
Example USER#123 ORDER#2024-01-15#001
DynamoDB Operations — The Core API Core
📥

Single-Item Operations

  • GetItem: retrieve one item by primary key
  • PutItem: create or replace an item
  • UpdateItem: modify specific attributes
  • DeleteItem: remove one item
  • All require the full primary key (PK or PK+SK)
  • Consistent — operate on exactly one item
🔍

Multi-Item Operations

  • Query: retrieve items by PK (+ optional SK condition) — efficient
  • Scan: read every item in the table — expensive, avoid
  • BatchGetItem: retrieve multiple items by keys (up to 100)
  • BatchWriteItem: put/delete multiple items (up to 25)
  • Query is what you use; Scan is a last resort

⚠️ The Golden Rule of DynamoDB:

Query is efficient — it reads only items matching the PK (and SK condition). It uses indexes and is O(log n) + items returned.
Scan is expensive — it reads every single item in the table. It is O(n) and consumes capacity for every item scanned, not just returned. Design your keys so you never need to Scan.

Data Types in DynamoDB Core
🔤

Scalar Types

  • String (S): UTF-8 text
  • Number (N): numeric (no distinction int/float)
  • Binary (B): base64-encoded binary data
  • Boolean (BOOL): true / false
  • Null (NULL): null value
📂

Document Types

  • Map (M): nested key-value object
  • List (L): ordered array of any types
  • Can be nested up to 32 levels deep
  • Enables document-style data modeling
  • Example: { "address": { "city": "NYC" } }
🗃️

Set Types

  • String Set (SS): set of unique strings
  • Number Set (NS): set of unique numbers
  • Binary Set (BS): set of unique binaries
  • No duplicates within a set
  • Useful for tags, roles, categories
AWS Architecture Diagram — Items in a DynamoDB Table Core
DynamoDB table with composite key — users and their orders stored together
PK: USER#alice SK: PROFILE name=Alice, email=alice@example.com, plan=pro
PK: USER#alice SK: ORDER#2024-01-10 amount=$99, status=shipped
PK: USER#alice SK: ORDER#2024-02-15 amount=$49, status=pending
PK: USER#bob SK: PROFILE name=Bob, plan=free
🔍 Query PK="USER#alice" → returns profile + all orders in one request, sorted by SK
🔍 Query PK="USER#alice" SK begins_with "ORDER#" → returns only orders
📥 GetItem PK="USER#alice" SK="PROFILE" → returns exactly one item
Item Size & Limits Core
📏

Item Size Calculation

  • Max item size: 400 KB
  • Size = sum of all attribute names + values + type overhead
  • Each attribute adds ~3 bytes overhead
  • 400 KB limit is total — not just the data payload
  • For large objects (>400 KB): store in S3, reference key in DynamoDB
⚠️

Reserved Words

  • name, value, date, order, status are reserved
  • Use projection-expression with attribute placeholder aliases
  • Example: #n for name in ExpressionAttributeNames
  • Better practice: avoid reserved words as attribute names
  • Exam trap: queries fail if reserved words used without placeholders
Conditional Writes — Optimistic Concurrency Advanced

DynamoDB supports conditional expressions that only execute a write if certain conditions are met. This enables optimistic locking, create-if-not-exists patterns, and idempotent updates without transactions.

🔒

Conditional Expression Examples

  • attribute_not_exists(PK) — create only if item doesn't exist
  • attribute_exists(userId) — update only if item exists
  • version = :v — optimistic locking (increment version on each write)
  • balance >= :amount — debit only if sufficient funds
  • Condition fails → ConditionalCheckFailedException
💰

Cost & Behavior

  • Consumes WCU regardless of condition result
  • If condition fails, still charged for the write attempt
  • Use for: preventing overwrites, idempotency, version control
  • Cheaper alternative to transactions for single-item atomicity
  • Exam: “prevent concurrent update overwrites” → conditional write with version
Batch Operations — Limits & Partial Success Core
📥

BatchGetItem

  • Retrieve up to 100 items per request
  • Max response size: 16 MB
  • Can span multiple tables
  • Parallel reads — more efficient than multiple GetItem calls
  • Returns UnprocessedKeys for partial success — retry with backoff
📤

BatchWriteItem

  • Write up to 25 items per request
  • Max request size: 16 MB
  • Supports PutItem and DeleteItem (not UpdateItem)
  • Can span multiple tables
  • Returns UnprocessedItems for partial success — retry with backoff

👉 Exam tip: “Retrieve multiple items by key efficiently” → BatchGetItem. “Partial success” → check UnprocessedKeys/UnprocessedItems and retry with exponential backoff.

🧠 Key Insight

A DynamoDB table is not like a SQL table. Items are accessed by key, not by arbitrary query. The partition key determines where data lives; the sort key orders data within that partition. By prefixing your keys (USER#, ORDER#), you can store multiple entity types in one table and query them together — this is the foundation of single-table design.

Chapter Summary Introductory
  • Table: collection of items, each identified by primary key; no fixed schema
  • Item: single record (max 400 KB); only primary key is required; attributes flexible
  • Primary key: simple (PK only) or composite (PK + SK); uniquely identifies every item
  • Partition key (PK): determines partition; must be exact match in queries
  • Sort key (SK): optional; enables ordering and range queries within a partition
  • Conditional writes: attribute_exists / attribute_not_exists for optimistic locking
  • Batch operations: BatchGetItem (100 items), BatchWriteItem (25 items); handle UnprocessedKeys
  • Reserved words: use ExpressionAttributeNames placeholders for name, value, date, etc.
03
Chapter Three

Partitioning & Scaling — The Critical Chapter

Why This Chapter Matters More Than Any Other Introductory

If you understand only one thing about DynamoDB, let it be this: your partition key design determines your scalability. DynamoDB can scale to handle millions of requests per second — but only if your data is distributed across partitions evenly. A bad partition key creates “hot partitions” that bottleneck your entire table.

👉 The exam will test this: When given a scenario where DynamoDB is throttling despite having provisioned capacity, the answer is almost always a hot partition caused by poor partition key selection. Fix the partition key design, or use write sharding.

How DynamoDB Partitions Data Core

DynamoDB automatically splits your table into partitions — internal storage units spread across many servers. Each partition can handle up to 3,000 RCU (read capacity units) and 1,000 WCU (write capacity units), and stores up to 10 GB. When a partition nears these limits, DynamoDB splits it.

📦

Partition Limits

  • 3,000 RCU per partition
  • 1,000 WCU per partition
  • 10 GB storage per partition
  • Partitions are invisible to you
  • DynamoDB manages splits automatically
🎯

How Items Map to Partitions

  • DynamoDB hashes the partition key
  • Hash value determines which partition stores the item
  • All items with the same PK → same partition
  • Sort keys do NOT affect partition placement
  • Good PK = items spread across many partitions
📈

Capacity Distribution

  • Provisioned capacity is distributed across partitions
  • Example: 10,000 WCU ÷ 10 partitions = 1,000 WCU each
  • One hot partition can max out while others sit idle
  • On-demand mode: same partition limits apply
  • Adaptive capacity helps but doesn't cure bad design
Concept Diagram — How Data Is Partitioned Core
DynamoDB partitioning — partition key hash determines storage location
Items PK: USER#alice PK: USER#bob PK: USER#carol PK: USER#alice PK: USER#bob hash(PK) determines partition PARTITIONS (managed by DynamoDB) Partition 1 USER#alice USER#alice 3000 RCU / 1000 WCU max 10 GB Partition 2 USER#bob USER#bob 3000 RCU / 1000 WCU max 10 GB Partition 3 USER#carol 3000 RCU / 1000 WCU max 10 GB
The Hot Partition Problem Core

A hot partition occurs when one partition key receives a disproportionate amount of traffic. Even if your table has 100,000 WCU provisioned, if 90% of writes go to one PK, that partition can only handle 1,000 WCU — and you will get ProvisionedThroughputExceeded errors.

🔥

Bad Partition Key Examples

  • Date: 2024-01-15 — all writes today hit one partition
  • Status: active — almost all users are active
  • Country: US — 80% of users in one country
  • Constant: ALL_USERS — one partition for everything
  • Low cardinality = hot partition

Good Partition Key Examples

  • userId: U123456 — high cardinality, evenly distributed
  • orderId: ORD-2024-00001 — unique per order
  • deviceId: sensor-abc123 — many devices, random access
  • sessionId: sess_xyz789 — unique per session
  • High cardinality + uniform access = good
Concept Diagram — Hot Partition vs Balanced Distribution Core
Hot partition (bad) vs balanced partition distribution (good)
❌ HOT PARTITION (PK = date) 2024-01-15 🔥 95% traffic THROTTLED! 2024-01-14 3% 2024-01-13 older ✅ BALANCED (PK = userId) user_001 ~20% user_002 ~20% user_003 ~20% user_... ~20% ✔ Even distribution = full capacity used
Write Sharding — Fixing Hot Partitions Advanced

If your access pattern requires a low-cardinality key (e.g., querying all orders by date), use write sharding: append a random suffix to the partition key. This spreads writes across multiple partitions. You then query all shards in parallel and merge results.

Without Sharding (Hot)

  • PK = "2024-01-15"
  • All writes to today's date hit one partition
  • Max 1,000 WCU for all today's orders
  • Throttling under load

With Sharding (Balanced)

  • PK = "2024-01-15#" + random(0–9)
  • Creates 10 partitions: 2024-01-15#0 ... #9
  • 10 × 1,000 WCU = 10,000 WCU capacity
  • Query all 10 shards in parallel, merge results
Adaptive Capacity & Burst Capacity Advanced

Adaptive Capacity

  • DynamoDB automatically moves capacity to hot partitions
  • Kicks in within minutes when imbalance detected
  • Mitigates hot partitions but doesn't solve them
  • If your entire table's capacity is consumed by one PK, you're still stuck
  • Design for balance — don't rely on adaptive capacity
💥

Burst Capacity

  • Unused capacity is banked for up to 5 minutes
  • Allows short bursts above provisioned throughput
  • In on-demand mode: no burst limit (throttles only at partition limit)
  • Useful for spiky workloads — not for sustained high traffic
  • Still respects the 3,000 RCU / 1,000 WCU per partition limit
🧠 Key Insight

DynamoDB scales horizontally — but only if your partition key distributes data evenly. Pick a high-cardinality, uniformly accessed attribute as your partition key. If you must query by a low-cardinality attribute (date, status), use write sharding. The partition key is not just an identifier — it is your scaling architecture.

Chapter Summary Introductory
  • Partitions: internal storage units; each supports 3,000 RCU, 1,000 WCU, 10 GB
  • Partition key hash: determines which partition stores an item; sort key does NOT affect partition
  • Hot partition: one PK gets disproportionate traffic; causes throttling even with high provisioned capacity
  • Good PK: high cardinality, uniform access (userId, orderId, deviceId)
  • Bad PK: low cardinality (date, status, country)
  • Write sharding: append random suffix to spread writes; query all shards in parallel
  • Exam: throttling despite high capacity = hot partition → fix PK design or use write sharding
04
Chapter Four

Indexes — GSI & LSI

Why You Need Indexes Introductory

In DynamoDB, you can only Query efficiently by primary key. If you need to query by a different attribute — say, find all orders by status when your PK is orderId — you would have to Scan the entire table. That's expensive and slow. Secondary indexes let you create alternate key structures so you can Query by different attributes efficiently.

👉 The core idea: A secondary index is like a copy of your table with a different primary key. DynamoDB automatically keeps the index in sync with the base table. You Query the index just like you Query the table — but using the index's key structure.

Global Secondary Index (GSI) Core
🌐

What is a GSI

  • An index with a different partition key (and optionally different sort key) from the base table
  • Can be created at any time (after table creation)
  • Data is automatically replicated to the GSI
  • Up to 20 GSIs per table
  • Has its own provisioned capacity (WCU/RCU)
  • Supports eventually consistent reads only
💡

When to Use GSI

  • Query by an attribute that is NOT the table's PK
  • Example: table PK = orderId, GSI PK = customerId
  • Now you can query all orders for a customer efficiently
  • Each additional access pattern often needs a GSI
  • GSIs are the primary tool for flexible querying
Local Secondary Index (LSI) Core
📍

What is an LSI

  • An index with the same partition key but a different sort key
  • Must be created at table creation time — cannot add later
  • Up to 5 LSIs per table
  • Shares capacity with the base table (no separate WCU/RCU)
  • Supports strongly consistent reads
  • Limited to 10 GB per partition (item collection limit)
🔧

When to Use LSI

  • Need to sort items within the same partition by a different attribute
  • Example: PK = userId, base SK = orderId, LSI SK = orderDate
  • Now you can query user's orders sorted by date
  • Use LSI only if you need strongly consistent reads on that alternate sort
  • Prefer GSI unless LSI's constraints fit your use case
GSI vs LSI — Comparison Table Core
Aspect Global Secondary Index (GSI) Local Secondary Index (LSI)
Partition key Different from base table Same as base table
Sort key Any attribute (optional) Different from base table
Create when Anytime Table creation only
Max per table 20 5
Capacity Separate WCU/RCU Shares with base table
Consistency Eventually consistent only Strongly consistent available
Size limit No limit 10 GB per partition
Concept Diagram — How GSI Works Core
GSI creates an alternate view of your data with a different primary key
BASE TABLE (PK: orderId) orderId (PK) customerId amount ORD#001 CUST#A $99 ORD#002 CUST#B $50 ORD#003 CUST#A $75 ✔ Query by orderId: efficient ❌ Query by customerId: needs Scan GSI auto-sync GSI (PK: customerId) customerId (PK) orderId amount CUST#A ORD#001 $99 CUST#A ORD#003 $75 CUST#B ORD#002 $50 ✔ Query by customerId: efficient Returns all orders for CUST#A
Attribute Projections Advanced

When you create an index, you specify which attributes to project (copy) into the index. This controls storage cost and what data is available when you query the index.

KEYS_ONLY

  • Only the key attributes are projected
  • Smallest storage footprint
  • Use when you only need the keys, then fetch full item from base table

INCLUDE

  • Keys + specified non-key attributes
  • Choose exactly which attributes you need
  • Balance between storage and data availability

ALL

  • All attributes from base table projected
  • Largest storage (doubles storage if all items indexed)
  • Use when you need full item data from index queries
🧠 Key Insight

GSIs are your primary tool for flexible querying in DynamoDB. Each access pattern that queries by a different attribute typically needs its own GSI. Plan your GSIs based on your access patterns — not your data model. LSIs are niche: use them only when you need strongly consistent reads on an alternate sort order within the same partition.

Chapter Summary Introductory
  • Secondary indexes: alternate key structures for querying by non-PK attributes
  • GSI: different PK (and optionally SK); up to 20; create anytime; eventually consistent only
  • LSI: same PK, different SK; up to 5; create at table creation only; strongly consistent available
  • Projections: KEYS_ONLY / INCLUDE / ALL — controls what data is copied to index
  • GSI capacity: separate WCU/RCU; LSI shares with base table
  • Exam: “query by different attribute” → GSI; “strongly consistent on alternate sort” → LSI
05
Chapter Five

Access Patterns & Data Modeling

Design Your Queries First Introductory

This is the most important chapter. In relational databases, you normalise your schema then write queries against it. In DynamoDB, you flip the process: list every query your application needs, then design your keys and table structure to serve those queries efficiently. If you don't know your access patterns upfront, DynamoDB is the wrong choice.

🎯 The Process:

  1. List all access patterns — every query your app will make
  2. Design primary key — PK + SK that satisfies the most critical patterns
  3. Add GSIs — for access patterns that can't use the base table's key
  4. Denormalise — duplicate data where needed for fast reads
  5. Test — verify every access pattern is served by a Query (not Scan)
Example — E-Commerce Access Patterns Core

Let's model an e-commerce application. Here are the access patterns the app needs:

📋

Required Access Patterns

  • AP1: Get user profile by userId
  • AP2: Get all orders for a user
  • AP3: Get order details by orderId
  • AP4: Get all orders by status (pending, shipped)
  • AP5: Get recent orders for a user (sorted by date)
🔑

Key Design

  • Base table: PK = userId, SK = type#id
  • AP1: PK = USER#123, SK = PROFILE
  • AP2: PK = USER#123, SK begins_with ORDER#
  • AP5: PK = USER#123, SK begins_with ORDER# (SK includes date)
  • GSI1: PK = orderId → for AP3
  • GSI2: PK = status, SK = orderDate → for AP4
Single-Table Design Core

Single-table design stores multiple entity types (users, orders, products) in one DynamoDB table. By using prefixed keys (USER#, ORDER#), you can store related data together and retrieve it in a single Query. This is the recommended approach for most DynamoDB applications.

Single-table design — users, orders, and products in one table
PK: USER#alice SK: PROFILE name=Alice, email=alice@.., plan=pro
PK: USER#alice SK: ORDER#2024-01-10#001 orderId=ORD001, amount=$99, status=shipped
PK: USER#alice SK: ORDER#2024-02-15#002 orderId=ORD002, amount=$49, status=pending
PK: ORDER#ORD001 SK: META userId=alice, amount=$99, items=[...]
PK: PRODUCT#SKU123 SK: INFO name=Widget, price=$25, stock=100
💡 Query PK="USER#alice" → returns profile + all orders in one request
💡 Query PK="ORDER#ORD001" → returns order metadata (via GSI or direct lookup)
⚠️ One table, multiple entity types, no joins needed
Denormalisation — Embrace Data Duplication Core

In relational databases, you avoid duplication through normalisation. In DynamoDB, you embrace duplication. If you need to display a user's name on every order, store the name in the order item. This trades storage (cheap) for fast reads (valuable).

Relational Approach (Wrong for DynamoDB)

  • Order item stores only userId
  • To display order with user name: need JOIN
  • DynamoDB has no JOINs — you'd need two queries
  • Two queries = more latency, more cost

DynamoDB Approach (Correct)

  • Order item stores userId AND userName
  • Single Query returns all data needed for display
  • Yes, you update userName in multiple places if it changes
  • User name rarely changes; reads happen constantly — optimise for reads
Composite Sort Keys — Hierarchical Data Advanced

By constructing sort keys with multiple segments (e.g., 2024-01-15#ORDER#001), you enable powerful range queries and hierarchical access patterns.

📅

Date-Prefixed SK Example

  • SK = 2024-01-15#ORDER#001
  • Query: SK begins_with "2024-01" → all January orders
  • Query: SK begins_with "2024-01-15" → all orders on Jan 15
  • Query: SK between "2024-01-01" and "2024-01-31"
  • Natural time-series ordering
📂

Hierarchical SK Example

  • SK = COUNTRY#USA#STATE#CA#CITY#LA
  • Query: SK begins_with "COUNTRY#USA" → all US locations
  • Query: SK begins_with "COUNTRY#USA#STATE#CA" → California only
  • Enables flexible drill-down queries
  • Same pattern for org hierarchies, file paths, etc.
Overloaded Keys — Generic PK/SK Names Advanced

In single-table design, your partition key might hold different entity types. Instead of naming it userId, name it generically as PK or pk. Same for sort key (SK). The meaning comes from the value prefix, not the attribute name.

Overloaded keys — generic PK and SK attributes hold different entity types
PK: USER#alice SK: USER#alice entityType=User, name=Alice ← user profile
PK: USER#alice SK: ORDER#2024-01#001 entityType=Order, amount=$99 ← user's order
PK: ORDER#ORD001 SK: ORDER#ORD001 entityType=Order, userId=alice ← order by orderId
Attribute name is always "PK" and "SK" — the value prefix indicates entity type
🧠 Key Insight

DynamoDB data modeling is access-pattern-driven. List your queries first. Then design your keys to serve those queries with a single Query operation (not Scan). Embrace denormalisation — duplicate data for fast reads. Use composite sort keys for hierarchical and time-based access. Single-table design keeps related data together for efficient retrieval.

Chapter Summary Introductory
  • Access patterns first: list every query, then design keys to serve them
  • Single-table design: multiple entity types in one table; use prefixed keys (USER#, ORDER#)
  • Denormalise: duplicate data for fast reads; optimise for read-heavy workloads
  • Composite sort keys: enable hierarchical and time-range queries (begins_with, between)
  • Overloaded keys: generic PK/SK attribute names; value prefix indicates entity type
  • GSIs: add for each access pattern that can't use the base table's key
06
Chapter Six

Performance & Consistency

Capacity Modes — Provisioned vs On-Demand Introductory

DynamoDB offers two capacity modes. Provisioned mode requires you to specify read and write capacity units upfront. On-demand mode automatically scales with your workload and charges per request. Choose based on your traffic predictability and cost optimization needs.

📊

Provisioned Mode

  • You specify RCU (Read Capacity Units) and WCU (Write Capacity Units)
  • 1 RCU = 1 strongly consistent read/sec (up to 4 KB) or 2 eventually consistent
  • 1 WCU = 1 write/sec (up to 1 KB)
  • Auto Scaling available (scales within min/max)
  • Cheaper for predictable, steady workloads
  • Throttled if you exceed capacity

On-Demand Mode

  • No capacity planning — scales instantly
  • Pay per RRU (Read Request Unit) and WRU (Write Request Unit)
  • More expensive per request (~5–7× provisioned at steady load)
  • Ideal for unpredictable, spiky, or new workloads
  • No throttling (up to partition limits — 3000 RCU/1000 WCU)
  • Switch between modes once per 24 hours
Provisioned Auto Scaling Core
📈

How Auto Scaling Works

  • Set min/max RCU/WCU and target utilization (%)
  • DynamoDB scales within range based on CloudWatch metrics
  • Default target: 70% utilization (leaves room for bursts)
  • Scale adjustments happen every few minutes (not instantaneous)
  • Works alongside adaptive capacity
⚠️

Auto Scaling Limitations

  • Reacts to past traffic — not predictive
  • May not react fast enough for sudden spikes
  • Still subject to partition-level limits (3000 RCU / 1000 WCU)
  • For truly unpredictable traffic, consider on-demand mode
  • Exam: Auto Scaling ≠ instant scaling; still provisioned mode under the hood
Read Consistency — Eventually vs Strongly Consistent Core

DynamoDB replicates data across 3 Availability Zones. When you write, DynamoDB confirms after writing to 2 of 3 AZs. When you read, you can choose between eventually consistent (may read from any replica) or strongly consistent (reads the leader replica to get latest data).

🔄

Eventually Consistent Reads (Default)

  • May not reflect the most recent write
  • Data typically consistent within 1 second
  • Costs 0.5 RCU per 4 KB (half price)
  • Higher throughput, lower latency
  • Use for: most reads where milliseconds of staleness is OK
✔️

Strongly Consistent Reads

  • Returns the most up-to-date data
  • Costs 1 RCU per 4 KB (full price)
  • Reads from leader replica
  • Slightly higher latency
  • Use for: reads that must see the latest write immediately

👉 Exam tip: GSI only supports eventually consistent reads. LSI supports both. If the question requires strongly consistent reads on an alternate sort key, you need an LSI (but remember LSI must be created at table creation time).

DynamoDB Accelerator (DAX) — In-Memory Caching Core

DAX is a fully managed, in-memory cache for DynamoDB. It sits between your application and DynamoDB, caching reads and providing microsecond latency for cached data. DAX is API-compatible with DynamoDB — just change the endpoint.

DAX architecture — in-memory cache between app and DynamoDB
App
Application
Lambda / EC2
Uses DAX SDK
DAX
DAX Cluster
In-memory cache
Microsecond reads
Multi-AZ nodes
DynamoDB
DynamoDB
Source of truth
Cache miss → fetch
Cache hit: microseconds • Cache miss: fall through to DynamoDB • Write-through: writes update cache + DynamoDB

When to Use DAX

  • Read-heavy workloads with repeated reads
  • Need microsecond latency (vs single-digit ms)
  • Reduce load on DynamoDB (save costs)
  • Cache GetItem and Query operations
  • Same API — minimal code changes

When NOT to Use DAX

  • Write-heavy workloads (DAX is read-optimised)
  • Need strongly consistent reads (DAX is eventually consistent)
  • Infrequent reads (cache won't help)
  • Cost-sensitive low-traffic apps (DAX adds infra cost)
  • Already using ElastiCache (evaluate if DAX adds value)

👉 DAX consistency: DAX only supports eventually consistent reads. If your application requires strongly consistent reads, query DynamoDB directly — not through DAX. The DAX SDK is a drop-in replacement, but you must bypass DAX for strongly consistent operations.

Transactions Advanced

DynamoDB supports ACID transactions across multiple items and tables. Use TransactWriteItems and TransactGetItems for all-or-nothing operations (up to 100 items per transaction).

💳

Transaction Use Cases

  • Transfer money between accounts (debit + credit)
  • Create order + decrement inventory
  • Maintain business invariants across items
  • All succeed or all fail — no partial updates
💰

Transaction Cost

  • 2× the WCU/RCU of standard operations
  • Each item in the transaction counts
  • Up to 100 items per transaction
  • Use when atomicity is required; avoid for simple operations
TTL — Time to Live Core

TTL automatically deletes expired items from your table at no cost. You specify a TTL attribute containing a Unix timestamp; DynamoDB deletes items when the timestamp passes. Deletion happens within 48 hours of expiration (not instant).

How TTL Works

  • Enable TTL on an attribute (e.g., expiresAt)
  • Store Unix epoch timestamp in that attribute
  • DynamoDB deletes expired items in background
  • No WCU consumed for TTL deletions
  • Deleted items can trigger DynamoDB Streams
💡

TTL Use Cases

  • Session storage (expire after 24 hours)
  • Temporary tokens / OTPs
  • Log retention (delete after 30 days)
  • GDPR data retention compliance
  • Cache invalidation
Backups & Point-in-Time Recovery Core
📸

On-Demand Backups

  • Full backups triggered manually or via API
  • Retained indefinitely until you delete them
  • No performance impact during backup
  • Stored in S3 (managed by DynamoDB)
  • Use for: pre-migration snapshots, compliance archives
⏱️

Point-in-Time Recovery (PITR)

  • Continuous backups to any second in last 35 days
  • Enable at table level (not enabled by default)
  • Additional storage charge (separate from table storage)
  • Restore creates a new table — cannot restore in-place
  • Exam: “recover to specific time” → PITR

👉 Exam tip: DynamoDB restore always creates a new table. You cannot restore over the existing table. Plan for DNS/endpoint changes if you need to switch to the restored table. PITR must be enabled before you need it — there is no retroactive recovery.

🧠 Key Insight

Provisioned mode is cheaper for predictable workloads; on-demand is simpler for spiky or unpredictable traffic. Default to eventually consistent reads (half the cost) unless you need the latest data. Use DAX for read-heavy workloads requiring microsecond latency. TTL is free and automatic — use it to expire transient data. Enable PITR for critical tables before you need disaster recovery.

Chapter Summary Introductory
  • Provisioned: specify RCU/WCU; Auto Scaling adjusts within min/max at 70% target
  • On-demand: pay per request; no provisioning; ~5× cost but scales instantly
  • Eventually consistent: 0.5 RCU per 4 KB; may have 1-sec staleness (default)
  • Strongly consistent: 1 RCU per 4 KB; latest data; not supported on GSI or DAX
  • DAX: in-memory cache; microsecond reads; eventually consistent only
  • TTL: auto-delete expired items; free; use for sessions, tokens, logs
  • PITR: recover to any second in last 35 days; restore creates new table
  • Transactions: ACID across items; 2× cost; use for atomicity requirements
07
Chapter Seven

Architecture Patterns

Pattern 1 — Serverless API (API Gateway + Lambda + DynamoDB) Core

The most common DynamoDB pattern: a fully serverless REST API. API Gateway handles routing, Lambda handles business logic, and DynamoDB provides the data layer. Zero servers to manage, auto-scaling from zero to millions of requests.

Pattern 1 — Serverless API with DynamoDB backend
API GW
API Gateway
REST/HTTP API
Auth + routing
Lambda
Lambda
Business logic
Validation
DynamoDB
DynamoDB
On-demand mode
Single-digit ms
✅ No servers • Pay per request • Scales to millions • Best for web/mobile backends
Pattern 2 — Event-Driven with DynamoDB Streams Core

DynamoDB Streams captures a time-ordered sequence of item-level changes (inserts, updates, deletes). Lambda can process these events in real-time for replication, analytics, or triggering downstream workflows.

Pattern 2 — DynamoDB Streams triggers Lambda on data changes
DynamoDB
DynamoDB
Writes happen
Streams
DDB Streams
Change events
24h retention
Lambda
Lambda
Process changes
Trigger workflows
Downstream
SNS / SQS
Elasticsearch
Analytics
Use cases: audit logs, search index sync, notifications, cross-region replication
💾

Streams Limits & Retention

  • 24-hour retention (cannot be extended)
  • Records can be replayed within retention window
  • ~1 shard per 1,000 WCU (approximately)
  • For longer retention: use Kinesis Data Streams (up to 365 days)
  • Kinesis Adapter available for Kinesis-compatible consumption
⚠️

Streams + Lambda Error Handling

  • Failed Lambda invocations retry until success or expiration
  • Configure maximumRetryAttempts (default: infinite)
  • Configure maximumRecordAgeInSeconds (default: 24h)
  • Use DLQ (SQS/SNS) for failed records
  • Exam: “handle stream processing failures” → DLQ + error handling config
Pattern 3 — Global Tables (Multi-Region) Advanced

Global Tables replicate your DynamoDB table across multiple AWS regions for low-latency global access and disaster recovery. Writes in any region are automatically replicated to all other regions within seconds.

🌐

How Global Tables Work

  • Active-active: read and write in any region
  • Automatic replication across regions
  • Conflict resolution: last writer wins
  • Replication typically under 1 second
  • Uses DynamoDB Streams under the hood
🎯

When to Use

  • Global user base needing local latency
  • Disaster recovery with RPO/RTO < 1 second
  • Multi-region active-active applications
  • Gaming, social media, global e-commerce
  • Exam: “multi-region DynamoDB” → Global Tables
Pattern 4 — Session Storage Core

DynamoDB is ideal for storing user sessions. Fast key-value lookups, auto-expiry via TTL, and seamless scaling make it better than sticky sessions or in-memory caches for distributed web apps.

Pattern 4 — Stateless web tier with DynamoDB session storage
ALB
ALB
Load balance
No sticky sessions
EC2
EC2
EC2
EC2
DynamoDB
DynamoDB
Session table
TTL enabled
PK = sessionId
Stateless servers • Any instance can serve any request • Sessions auto-expire via TTL
Pattern 5 — High-Throughput DAX Caching Advanced
Pattern 5 — DAX for microsecond caching on read-heavy workloads
App
Application
High read volume
Leaderboard / catalog
DAX
DAX Cluster
microsecond latency
Cache hot reads
Multi-AZ
DynamoDB
DynamoDB
Cache miss fallback
Source of truth
Use cases: gaming leaderboards, product catalogs, news feeds — any hot key read pattern
Decision Guide — DynamoDB vs Other Databases Core
You Need... Use Why
Key-value lookups, ms latency at scale DynamoDB Serverless, unlimited scale, single-digit ms
Microsecond latency on hot reads DynamoDB + DAX In-memory cache purpose-built for DynamoDB
Multi-region active-active DynamoDB Global Tables Write in any region, replicate globally
SQL, complex JOINs, ad-hoc queries RDS / Aurora Relational model, flexible querying
Sub-ms caching, not DynamoDB-specific ElastiCache Redis / Memcached general purpose cache
Data warehouse / analytics Redshift Columnar storage for OLAP
Exam Cheatsheet Core

🎯 Exam Keywords → DynamoDB Answer

  • “serverless NoSQL” → DynamoDB
  • “single-digit ms latency at any scale” → DynamoDB
  • “key-value / document store” → DynamoDB
  • “throttling despite high capacity” → hot partition → fix partition key or write sharding
  • “query by different attribute” → GSI
  • “strongly consistent reads on alternate sort” → LSI (table creation only)
  • “microsecond latency” → DynamoDB + DAX (eventually consistent only)
  • “multi-region DynamoDB” → Global Tables
  • “event-driven, react to data changes” → DynamoDB Streams + Lambda
  • “stream processing failures” → DLQ + maximumRetryAttempts + maximumRecordAgeInSeconds
  • “auto-expire session data” → TTL
  • “unpredictable traffic, no capacity planning” → on-demand mode
  • “atomic multi-item operations” → DynamoDB Transactions (2× cost)
  • “prevent concurrent update overwrites” → conditional write with version attribute
  • “recover to specific point in time” → PITR (enable before you need it; restore creates new table)
  • “retrieve multiple items by key” → BatchGetItem (up to 100 items)
  • “GSI / DAX consistency” → eventually consistent only
  • “partition key design” → high cardinality, uniform access
  • “item >400 KB” → store in S3, reference key in DynamoDB
🧠 Final Insight

DynamoDB is purpose-built for access-pattern-driven applications at massive scale. Design your queries first, then model your data to serve them. Embrace denormalisation, single-table design, and GSIs. The partition key is your architecture — get it right and DynamoDB scales infinitely. Get it wrong and you hit hot partition throttling no matter how much capacity you provision.