Serverless (DynamoDB and serverless patterns) — DVA-C02 Mastery Guide
- Sources: course chapter 15 (DynamoDB), used as the syllabus only; all wording, examples and questions here are original.
- Written 2026-10-12 · values checked against AWS docs on 2026-10-12.
⚠️ Changed: <current>. Older material may say <old>.marks a value or behaviour that differs from the course or older practice tests: learn the current value (prices and quotas are volatile).⚠️ unverifiedmarks a detail that AWS documentation did not confirm, or on which AWS pages disagree: never base an exam answer on it. Every change is listed in Changed vs older material; the rows you confirm are also kept innotes/current-facts.md.- Tier 1 (highest exam weight), bank target 40 questions. Domains: Development with AWS Services (primary), Troubleshooting and Optimization.
- How to use it:
/explain <topic>teaches from these sections (concept → exam facts → developer angle → traps → 3 check questions);/quiz dynamodbasks bank questions whosesource_notelinks point at the## N.headings below;/drill dynamodbfires short recall prompts fromnotes/dynamodb-cheat-sheet.md. Suggested loop: read a section and answer its practice questions before opening the answers →/explainwhat is still unclear →/quiz dynamodb→/drill dynamodb.
How Serverless (DynamoDB and serverless patterns) appears on DVA-C02
DynamoDB is the default answer whenever a scenario says "serverless", "key-value", "single-digit millisecond" or "scales without management". Questions treat it as something your code calls through the SDK, so they test what a developer decides and debugs:
- Data modelling: the partition and sort key chosen from the access patterns; an LSI or GSI when a new query appears.
- Capacity: RCU/WCU from item size, rate and consistency; on-demand vs provisioned; diagnosing and fixing throttling (hot keys, GSI back-pressure, retries with backoff,
UnprocessedItems). - API choice:
GetItemvsQueryvsScan; conditional writes vs atomic counters vs transactions; batch calls; PartiQL; pagination and projections. - Event-driven designs: DynamoDB Streams feeding Lambda, TTL for automatic expiry, DAX in front of hot reads.
- Security: item-level access with
dynamodb:LeadingKeys, encryption keys, VPC endpoints, resource-based policies.
Typical question shapes: a calculation with four numeric options; a symptom to diagnose (ProvisionedThroughputExceededException, ConditionalCheckFailedException, items missing after a batch, stale reads); "Which TWO…" design choices; the exact API, parameter or condition key that does the job.
Deciding constraints: LEAST operational overhead (TTL instead of a cleanup job, on-demand instead of capacity planning, DAX instead of a hand-written cache); MOST cost-effective (Query over Scan, eventually consistent reads, small projections, Standard-IA for cold tables); LOWEST latency (DAX); fewest code changes (DAX is API-compatible; a GSI instead of a new table); least privilege (item-level IAM conditions).
Domain map: Development with AWS Services → §1, §5–§9, §11–§14 · Troubleshooting and Optimization → §2, §3, §4, §6 (GSI throttling), §10.
Out of scope here (owned by other modules): IAM policy grammar and evaluation → notes/iam-guide.md; Cognito identity pools and web identity federation → notes/advanced-identity-guide.md; Lambda concurrency, scaling and async destinations in general → notes/lambda-guide.md; SDK retry modes, backoff with jitter and the credential chain → notes/cli-sdk-policies-guide.md (role credentials on EC2: notes/ec2-mastery-guide.md §1); KMS key types and policies → notes/security-encryption-guide.md; ElastiCache engines and caching strategies → notes/rds-guide.md; gateway vs interface endpoint design → notes/vpc-guide.md; CloudWatch alarms and CloudTrail → notes/monitoring-guide.md; Kinesis Data Streams and KCL → notes/sqs-sns-kinesis-guide.md; S3 event notifications → notes/s3-guide.md; table resources in templates → notes/sam-guide.md and notes/cloudformation-guide.md.
| # | Topic | Why it is on the exam |
|---|---|---|
| 1 | Tables items and primary keys | Key design decides cost, speed and throttling; data types and the 400 KB item limit |
| 2 | Read and write capacity units | The classic calculation question: size rounding, consistency and transactional multipliers |
| 3 | Capacity modes provisioned and on demand | Choosing the mode under a cost or traffic constraint; switching, auto scaling, warm throughput |
| 4 | Throttling errors and retries | Reading the error and metric to find the real cause: hot key, GSI, quota or provisioned limit |
| 5 | Reading data with GetItem Query and Scan | The cheapest correct read; filters, projections, pagination and parallel scans |
| 6 | Local and global secondary indexes | New access patterns without a new table; consistency and throughput differences |
| 7 | Writing items and conditional writes | Preventing lost updates: conditions, optimistic locking, atomic counters |
| 8 | Batch operations and transactions | Fewer round trips vs all-or-nothing; limits, partial failures and capacity cost |
| 9 | PartiQL | SQL-style access, its three APIs, limits and IAM actions |
| 10 | DAX and caching choices | Microsecond reads without a rewrite, and when ElastiCache fits better |
| 11 | DynamoDB Streams and Lambda | Change data capture into Lambda: view types, ordering, retries and failure handling |
| 12 | Time to Live | Free automatic expiry; timing, format and Streams side effects |
| 13 | Security and fine grained access control | Per-user item access, encryption, private connectivity and cross-account access |
| 14 | Serverless patterns and table operations | Session store, write sharding, large objects in S3, backups, copies and global tables |
1. Tables items and primary keys
Concept
- A table holds items, and an item is a set of named attributes. Only the key attributes are required; there is no other schema, so two items in one table can carry entirely different attributes.
- The primary key is set by
CreateTableand can never be changed:- Simple key: a partition key alone (
KeyTypeHASH); every item needs its own value. - Composite key: partition key + sort key (
HASH+RANGE); two items may share a partition key value as long as their sort keys differ. Items that share a partition key form an item collection, stored together in sort key order, which is what makes range reads (begins_with,BETWEEN) cheap.
- Simple key: a partition key alone (
- DynamoDB hashes the partition key value to choose a partition: SSD storage replicated across several Availability Zones of the Region. Partitions are added and split for you as data and throughput grow.
- A good partition key has many distinct values that are requested evenly (user ID, order ID, device ID). A key with few or skewed values (status, country, language, a date, a flag) funnels traffic into a few partitions — the root cause behind most throttling questions (§4).
- Design from the access patterns, not the entities: there are no joins and no server-side aggregation, so store data the way it is read (pre-joined items, composite sort keys such as
ORDER#2026-10-12, totals kept up to date from a stream).
Exam facts
- Item size: 400 KB maximum, counting attribute names (UTF-8 bytes) as well as values; long names repeated on every item cost storage and capacity.
- Key values: partition key 1–2,048 bytes; sort key 1–1,024 bytes. Key attributes of the table and of every index must be String, Number or Binary.
- Data types: scalar
S,N,B,BOOL,NULL; documentLandM(nested up to 32 levels); setsSS,NS,BS(unique values of one type, never empty). Empty strings and binaries are allowed in non-key attributes; empty lists and maps are allowed, empty sets are not. - Numbers hold up to 38 digits and travel as strings; binary values are base64-encoded in the low-level API; there is no date type — use ISO 8601 strings (they sort correctly) or epoch numbers (required by TTL, §12).
- Tables: no practical size limit; an initial quota of 2,500 tables per Region (the AWS account team can raise it to 10,000). Table and index names: 3–255 characters from
A-Z a-z 0-9 _ - .. - Per partition: 3,000 RCU and 1,000 WCU (independent limits) and 10 GB of data at most.
- ⚠️ Changed: DynamoDB publishes no partition-count formula, and capacity is not locked to an equal share per partition: adaptive capacity (always on, free, instant) lets a busy partition use more of the table's throughput up to the partition maximum, and DynamoDB moves very hot items onto partitions of their own. Older material may say partitions = ceil(max(RCU/3,000 + WCU/1,000, size/10 GB)), each holding an equal, fixed slice of the capacity.
- The control plane is asynchronous:
CreateTablereturnsTableStatusCREATING; reads and writes work once it isACTIVE. Calling too early →ResourceNotFoundException; creating a table that exists →ResourceInUseException; more than 500 tables or indexes inCREATING/UPDATING/DELETINGat once →LimitExceededException. - ARNs for IAM: table
arn:aws:dynamodb:<region>:<account>:table/<name>, index…:table/<name>/index/<index>, stream…:table/<name>/stream/<label>.
Developer angle
Create a composite-key table and wait until it is usable:
aws dynamodb create-table --table-name Orders \
--attribute-definitions AttributeName=CustomerId,AttributeType=S AttributeName=OrderDate,AttributeType=S \
--key-schema AttributeName=CustomerId,KeyType=HASH AttributeName=OrderDate,KeyType=RANGE \
--billing-mode PAY_PER_REQUEST
aws dynamodb wait table-exists --table-name Orders # returns once the table is ACTIVE
--attribute-definitionslists only key attributes (of the table and its indexes); declaring one that no key uses fails with aValidationException.- The low-level API takes typed
AttributeValuemaps ({"Amount": {"N": "42.50"}}); higher-level layers convert native types: the boto3 resource layer (passdecimal.Decimal, neverfloat),DynamoDBDocumentClientfrom@aws-sdk/lib-dynamodbin JavaScript, the Enhanced Client in Java. - With
OrderDate=2026-10-12#A17, oneQuerywithbegins_with(OrderDate, "2026-10")returns a customer's October orders. - IAM actions map one-to-one to API calls (
dynamodb:PutItem,dynamodb:Query, …): scopeResourceto the table ARN and addtable/<name>/index/<index>only where an index is queried. Code on EC2, Lambda or ECS gets these permissions from its role credentials (notes/ec2-mastery-guide.md§1), never from access keys in the code.
Traps
- Trap: "Every item needs its own partition key value." → Reality: only with a simple key; with a composite key, items share partition key values and the full key (partition + sort) identifies each one.
- Trap: "Change the sort key later with
UpdateTable." → Reality: the key schema is immutable; add a GSI for the new access pattern (§6) or migrate to a new table. - Trap: "Only attribute values count toward 400 KB." → Reality: names count too.
- Trap: "DynamoDB spreads any key evenly, so
statusis a fine partition key." → Reality: a handful of values means a handful of hot partitions, each capped at 3,000 RCU / 1,000 WCU. - Trap: "Raising provisioned capacity cures a hot key." → Reality: one key value still hits the per-partition ceiling; spread its writes (§14) or cache its reads (§10).
Practice questions
-
A developer is designing a table for a fitness app. The main screen lists one member's workouts from newest to oldest, and members can open a single workout. Traffic is spread across millions of members. Which primary key design meets these needs?
- A. Partition key
workoutType, sort keystartTime - B. Partition key
startDate, sort keymemberId - C. Partition key
memberId, sort keystartTime - D. Partition key
workoutIdonly, filtering onmemberIdwhen reading
- A. Partition key
-
Writes to a user-profile table started failing with
ValidationException: Item size has exceeded the maximum allowed size. The team must keep all profile data available to the application. Which TWO changes address the cause? (Select TWO.)- A. Move each profile's large avatar image to Amazon S3 and store only the object key in the item.
- B. Switch the table from provisioned to on-demand capacity mode.
- C. Add a global secondary index that projects the avatar attribute.
- D. Shorten the long attribute names that every item repeats.
- E. Enable DynamoDB Streams with the
NEW_IMAGEview type.
Answers
- C.
memberIdhas high cardinality and keeps each member's workouts in one item collection;startTimeas sort key gives a newest-firstQuery(ScanIndexForward=false) and a directGetItem. A concentrates traffic on a few workout types; B turns "one member's workouts" into a cross-partition search; D needs a scan with a filter becausememberIdis not a key. - A, D. The 400 KB limit counts attribute names and values, so moving large binaries to S3 and shortening names both shrink the items. B changes billing, not item size; C copies the attribute into an index entry and leaves the base item as large as before; E only publishes changes.
2. Read and write capacity units
Concept
- Throughput is metered in capacity units per second: RCU/WCU in provisioned mode, read/write request units (RRU/WRU) in on-demand mode. The unit sizes are the same, so one set of arithmetic serves both modes; on-demand simply bills every unit consumed.
- Each second, 1 WCU pays for writing up to 1 KB of one item; 1 RCU pays for one strongly consistent read of up to 4 KB, and the same unit covers two such reads when they are eventually consistent. Transactional reads and writes cost double (a prepare and a commit for every item).
- Sizes are binary (1 KB = 1,024 bytes), and each item is rounded up to the next 1 KB (writes) or 4 KB (reads) before anything is multiplied.
- Recipe: (1) convert the rate to per second; (2) round the item size up to whole units; (3) multiply; (4) halve for eventually consistent reads or double for transactional ones; (5) round the result up.
Exam facts
| Workload | Working | Capacity |
|---|---|---|
| 25 writes/s of 2.5 KB | 25 × ⌈2.5⌉ = 25 × 3 | 75 WCU |
| 90 writes/min of 3 KB | 1.5 writes/s × 3 = 4.5 | 5 WCU |
| 8 transactional writes/s of 1.5 KB | 8 × 2 × 2 | 32 WCU |
| 40 strongly consistent reads/s of 9 KB | 40 × ⌈9/4⌉ = 40 × 3 | 120 RCU |
| Same reads, eventually consistent | 120 ÷ 2 | 60 RCU |
| Same reads, transactional | 120 × 2 | 240 RCU |
| 12 eventually consistent reads/s of 1 KB | 12 × 1 ÷ 2 | 6 RCU |
- Per-operation rules:
GetItem: one item rounded up to 4 KB; reading a key that doesn't exist still costs the minimum (1 RCU strong, 0.5 eventual).BatchGetItem/BatchWriteItem: each item is rounded separately, then summed (a 1 KB and a 5 KB item read strongly = 4 KB + 8 KB = 12 KB = 3 RCU).Query: the sizes of all items it reads, filtered-out ones included, are added first, then rounded up once (30 items × 300 bytes ≈ 8.8 KB → 12 KB → 3 RCU strong, 1.5 eventual).Scan: charged on the items evaluated, not on the items returned.PutItemover an existing item andUpdateItem: the larger of the old and new item, even when one attribute changes;DeleteItem: the size of the deleted item.- A conditional write whose condition fails still consumes WCU (at least 1).
ProjectionExpressionandFilterExpressionshrink the response, never the capacity consumed;Select=COUNTcosts as much as reading the items.- GSI queries are always eventually consistent (½ RCU per 4 KB of projected data); writes that change an index consume the index's WCU on top of the table's; LSIs use the table's capacity (§6).
ReturnConsumedCapacity=INDEXES(table plus each index),TOTALorNONE(default) — the way to measure the real cost from code.
Developer angle
Measure instead of guessing:
aws dynamodb get-item --table-name Orders \
--key '{"CustomerId":{"S":"C-1001"},"OrderDate":{"S":"2026-10-12#A17"}}' \
--consistent-read --return-consumed-capacity TOTAL
# ... "ConsumedCapacity": {"TableName": "Orders", "CapacityUnits": 1.0}
put_item(..., ReturnConsumedCapacity="INDEXES")shows what each GSI adds to a write — the quickest way to spot an expensiveALLprojection.- CloudWatch
ConsumedReadCapacityUnits/ConsumedWriteCapacityUnitsare one-minute sums: divide theSumby 60 before comparing it with a per-second provisioned value.
Traps
- Trap: "Add up the item sizes, then round" for single-item or batch calls. → Reality: round each item first;
QueryandScanround the total. - Trap: "Per-minute rate × item size = WCU." → Reality: convert to per second first (90 per minute = 1.5 per second).
- Trap: "Eventually consistent reads cover 8 KB per RCU, so round each item up to 8 KB." → Reality: round up to 4 KB blocks, then halve: 10 eventually consistent reads/s of 3 KB items = 10 × 1 × ½ = 5 RCU, not 10.
- Trap: "A filter that drops 90 % of the items cuts RCU by 90 %." → Reality: you pay for everything read before the filter.
- Trap: "A 100-byte write costs 0.1 WCU." → Reality: the minimum is 1 WCU.
Practice questions
-
An IoT backend writes 300 sensor readings per minute to a provisioned table. Each item is 2.2 KB and uses a standard
PutItem. How many WCU must the table provision to sustain this rate without throttling?- A. 5 WCU
- B. 15 WCU
- C. 11 WCU
- D. 30 WCU
-
A table stores 7 KB product items, and an API performs 20 strongly consistent
GetItemcalls per second on them. Some endpoints tolerate slightly stale data, and rarely used data may move to cheaper storage. A developer must lower the read capacity this API consumes. Which TWO changes reduce the consumed RCU? (Select TWO.)- A. Use eventually consistent reads for the endpoints that tolerate slightly stale data.
- B. Add a
ProjectionExpressionthat returns only the three attributes the API displays. - C. Move a rarely used 4 KB description attribute to Amazon S3 so that each item drops below 4 KB.
- D. Add a
FilterExpressionthat discards inactive products. - E. Replace
GetItemwithTransactGetItemsfor consistent snapshots.
Answers
- B. 300 per minute = 5 writes/s; 2.2 KB rounds up to 3 KB → 5 × 3 = 15 WCU. A ignores the item size; C multiplies by 2.2 without rounding up; D doubles the result as if the writes were transactional.
- A, C. Eventually consistent reads halve the cost (2 RCU → 1 RCU per read of a 7 KB item); shrinking the item from 7 KB (two 4 KB blocks) to under 4 KB (one block) halves it again. B and D change what is returned, not what is read; E doubles the cost.
3. Capacity modes provisioned and on demand
Concept
- Each table has one capacity mode (
BillingMode), and its GSIs follow it:- On-demand (
PAY_PER_REQUEST): no capacity planning; you pay per request unit; the table instantly serves any traffic level it has reached before. - Provisioned (
PROVISIONED): you set RCU and WCU per second and pay for them by the hour, used or not; add auto scaling (Application Auto Scaling target tracking) and, for long-term steady use, reserved capacity.
- On-demand (
- ⚠️ Changed: after a 50 % on-demand price cut effective 2024-11-01, AWS calls on-demand the default and recommended mode for most workloads; provisioned + auto scaling still wins for steady, predictable, well-used traffic. Older material may say on-demand costs about 2.5 times provisioned and suits only unknown or spiky workloads.
- Spike absorbers: burst capacity (up to 300 s of unused provisioned capacity kept in reserve), adaptive capacity (§1) and warm throughput — the read/write rate a table or GSI can serve instantly, which you can raise ahead of an event.
- Table classes are independent of the mode: Standard (default) or Standard-Infrequent Access — cheaper storage, dearer requests, same performance and APIs.
Exam facts
- ⚠️ Changed: switch provisioned → on-demand up to 4 times per rolling 24 hours (since August 2025); on-demand → provisioned at any time. Older material may say a table may change its capacity mode only once per day.
- A switch to on-demand takes several minutes without downtime (the table keeps its previous provisioned throughput meanwhile). The console deletes auto scaling settings on that switch; the CLI and SDKs keep them for a later switch back.
- On-demand scaling: a new on-demand table sustains up to 4,000 writes/s and 12,000 reads/s; any table instantly serves up to double its previous peak; growing beyond 2× the previous peak within 30 minutes may throttle → pre-warm, or spread the growth over at least 30 minutes.
- ⚠️ Changed: on-demand can throttle — at the per-table quota (40,000 RRU and 40,000 WRU by default, adjustable), at an optional maximum throughput you set (
OnDemandThroughputwithMaxReadRequestUnits/MaxWriteRequestUnits, since May 2024, best effort,-1removes it; excess requests getThrottlingException), beyond 2× the previous peak within 30 minutes, and on hot partitions. Older material may say on-demand capacity is unlimited and never throttles. - ⚠️ Changed: since 2024-11-13 every table and GSI shows its warm throughput for free. Pre-warm with
WarmThroughput(ReadUnitsPerSecond,WriteUnitsPerSecond) onCreateTableorUpdateTable: charged, asynchronous (UPDATING→ACTIVE), cannot be lowered afterwards, works in both modes and on global tables. Older material may say pre-warm by provisioning high capacity for a while and then switching to on-demand. - Provisioned quotas: 40,000 RCU and 40,000 WCU per table; 80,000 RCU and 80,000 WCU per account per Region (adjustable); minimum 1 RCU / 1 WCU. Increases any time; decreases: 4 available at the start of each UTC day plus 1 more each hour (never more than 4 banked) → up to 27 per day, counted separately for each GSI.
- Auto scaling: target utilization 20–90 % (console default 70 %); it acts after the target has been breached for 2 consecutive minutes (scale-in after 15 consecutive low data points), then needs minutes to apply, so short spikes rely on burst capacity. Each GSI needs its own policy, and a GSI added to an existing table is not auto scaled until its backfill finishes.
- Price (us-east-1, Standard class, on-demand): $0.625 per million WRU and $0.125 per million RRU; the same price change cut global-table replicated writes by 67 % (on-demand) and 33 % (provisioned).
- Standard-IA pays off when storage costs more than about 50 % of the throughput cost; at most 2 table-class changes per trailing 30 days; GSIs use the table's class.
- Commitment discounts: reserved capacity (provisioned mode) and Database Savings Plans, which also cover DynamoDB (
notes/current-facts.md, ec2 rows).
Developer angle
# provisioned -> on-demand (at most 4 per rolling 24 h); back to PROVISIONED works any time
aws dynamodb update-table --table-name Orders --billing-mode PAY_PER_REQUEST
# cap on-demand spend: requests above the cap get ThrottlingException (-1 removes the cap)
aws dynamodb update-table --table-name Orders \
--on-demand-throughput MaxReadRequestUnits=2000,MaxWriteRequestUnits=1000
# pre-warm before a launch (charged, cannot be decreased later)
aws dynamodb update-table --table-name Orders \
--warm-throughput ReadUnitsPerSecond=30000,WriteUnitsPerSecond=12000
- Auto scaling is two Application Auto Scaling calls:
register-scalable-target(--service-namespace dynamodb,--resource-id table/Orders,--scalable-dimension dynamodb:table:WriteCapacityUnits, min/max) andput-scaling-policy --policy-type TargetTrackingScalingwith the predefined metricDynamoDBWriteCapacityUtilizationand aTargetValuesuch as 70. Repeat withdynamodb:index:WriteCapacityUnitsandtable/Orders/index/<name>for each GSI. - Choosing under a constraint: unknown or spiky traffic, new apps, idle dev tables → on-demand; steady 24/7 traffic with a known floor → provisioned + auto scaling (+ reserved capacity); a known one-off spike → pre-warm (either mode); a guard against runaway on-demand cost →
OnDemandThroughput.aws dynamodb describe-limitsshows the Region's throughput quotas.
Traps
- Trap: "On-demand never throttles." → Reality: the table quota, a maximum-throughput cap, more than 2× the previous peak within 30 minutes and hot partitions all throttle.
- Trap: "You can switch modes only once a day." → Reality: 4 switches to on-demand per rolling 24 hours; back to provisioned any time.
- Trap: "Auto scaling absorbs a 20-second spike." → Reality: it reacts after minutes; burst capacity or on-demand handles short spikes.
- Trap: "Raise the target utilization to 90 % for more headroom." → Reality: a higher target leaves less spare capacity.
- Trap: "Standard-IA tables are slower." → Reality: same performance, durability and APIs; only the split between storage and request prices changes.
Practice questions
-
A retailer's on-demand table peaked at about 3,000 writes per second last month. A flash sale starting at 09:00 is expected to push writes to 20,000 per second within the first minute. Which action prevents throttling with the LEAST operational overhead?
- A. Switch the table to provisioned mode with auto scaling at a 70 % target.
- B. Put a DynamoDB Accelerator (DAX) cluster in front of the table.
- C. Add exponential backoff to the order service's retry logic.
- D. Raise the table's write warm throughput before the sale starts.
-
A provisioned table with auto scaling (target 70 %) throttles writes during bursts lasting 20–40 seconds, even though auto scaling raises capacity a few minutes later. Which TWO statements correctly explain or resolve the behaviour? (Select TWO.)
- A. Raising the target utilization to 90 % gives the table more headroom during bursts.
- B. Auto scaling acts only after the target has been breached for consecutive minutes, so short bursts outrun it.
- C. Switching the table to on-demand mode lets it absorb bursts up to double its previous peak instantly.
- D. Burst capacity is not available to tables that use auto scaling.
- E. Adding a local secondary index spreads the writes across more partitions.
Answers
- D. Pre-warming raises the instantly available write rate ahead of a known event with one API call; 20,000 writes/s is far beyond double the previous peak, so on-demand alone would throttle. A still lags behind a one-minute ramp; B accelerates reads, not writes; C only retries after throttling has already happened.
- B, C. Target tracking needs sustained breaches and then minutes to apply, while on-demand serves up to 2× the previous peak at once. A shrinks the headroom; D is false — burst capacity still applies but runs out once the saved 300 seconds are spent; E is impossible on an existing table, and LSIs share the table's capacity anyway.
4. Throttling errors and retries
Concept
- Throttling is DynamoDB rejecting requests above a limit. The four documented causes: provisioned throughput exceeded (the table or a GSI used more than its RCU/WCU); key range throughput exceeded (one partition passed 3,000 RCU or 1,000 WCU — a hot key, even while the table has spare capacity; both modes); account limit exceeded (an on-demand table reached its per-table quota); maximum on-demand throughput exceeded (the cap you set with
OnDemandThroughput). - ⚠️ Changed: since August 2025 every throttling exception carries
ThrottlingReasons: each entry has areasonbuilt as<Table|Index><Read|Write><LimitType>(for exampleTableWriteKeyRangeThroughputExceeded,IndexWriteProvisionedThroughputExceeded) and theresourceARN of the table or index. DynamoDB also added 16 cause-specific CloudWatch metrics and a Contributor Insights throttled keys mode. Older material may say you only getProvisionedThroughputExceededExceptionand must guess the cause. - The exception class can be
ProvisionedThroughputExceededException,ThrottlingExceptionorRequestLimitExceeded(all HTTP 400, all retryable): choose the fix from the reason, not from the class. - GSI back-pressure: when a GSI cannot absorb the index updates (too little write capacity, or a hot index partition), DynamoDB throttles the base-table write, and the reason and ARN point at the index.
- AWS SDKs retry throttled calls automatically with exponential backoff; your code sees the error only when the retries run out (retry modes and jitter:
notes/cli-sdk-policies-guide.md). Batch calls don't raise per-item throttles — they returnUnprocessedItems/UnprocessedKeys. A throttled transaction fails as a whole withTransactionCanceledException(reasonThrottlingErrororProvisionedThroughputExceeded).
Exam facts
| Error | HTTP | Retry? | Typical cause / fix |
|---|---|---|---|
ProvisionedThroughputExceededException |
400 | Yes | Over the provisioned RCU/WCU of the table or a GSI, or a hot partition; the reason says which |
ThrottlingException |
400 | Yes | On-demand cap, hot partition or on-demand scaling; also control-plane calls sent too fast |
RequestLimitExceeded |
400 | Yes | Account-level throughput quota reached → request an increase |
ConditionalCheckFailedException |
400 | No | The condition evaluated to false (§7): re-read instead of resending |
TransactionConflictException |
400 | Yes, once the transaction ends | A single-item write hit an item inside an ongoing transaction |
TransactionCanceledException |
400 | Depends | Read CancellationReasons (§8); SDKs don't retry it |
ItemCollectionSizeLimitExceededException |
400 | Yes | An LSI item collection reached 10 GB (§6) |
ValidationException |
400 | No | Item over 400 KB, type mismatch, bad expression, strongly consistent read on a GSI |
ResourceNotFoundException |
400 | No | Wrong table name or Region, or the table is not ACTIVE yet |
AccessDeniedException |
400 | No | Missing IAM permission or an incorrectly signed request |
InternalServerError / ServiceUnavailable |
500 / 503 | Yes | Transient; a 500 on a write may have applied → read first, or make the write conditional or idempotent |
- Metrics:
ThrottledRequests(+1 per throttled request, however many events it contained),ReadThrottleEvents/WriteThrottleEvents, and the cause-specificRead…/Write…+ProvisionedThroughput,KeyRangeThroughput,MaxOnDemandThroughput,AccountLimit+ThrottleEventsmetrics (for exampleWriteKeyRangeThroughputThrottleEvents), per table and per GSI.OnlineIndexConsumedWriteCapacityshows the writes of a GSI backfill. - Contributor Insights for DynamoDB: mode
THROTTLED_KEYS(processes only throttled requests, cheap enough to leave on) orACCESSED_AND_THROTTLED_KEYS; it lists the top keys (at most 25 contributors) to anyone with CloudWatch access — don't enable it when key values are sensitive. - Without an SDK: retry 5xx errors and throttling 400s with exponential backoff and stop after about a minute; fix any other 400 before resending.
- Remedies by reason:
…ProvisionedThroughputExceeded→ more capacity, auto scaling or on-demand;…KeyRangeThroughputExceeded→ better key or write sharding (§14), DAX for read-hot keys (§10), eventually consistent reads (half the RCU), pre-warming;…MaxOnDemandThroughputExceeded→ raise or remove the cap;…AccountLimitExceeded→ quota increase in Service Quotas;Index…reasons → fix the GSI (capacity, key cardinality, slimmer projection), not the table.
Developer angle
import random
import time
import boto3
from botocore.config import Config
ddb = boto3.client("dynamodb", config=Config(retries={"max_attempts": 10, "mode": "standard"}))
def write_all(table, put_requests): # put_requests: up to 25 {"PutRequest": {...}} entries
pending, attempt = {table: put_requests}, 0
while pending:
response = ddb.batch_write_item(RequestItems=pending)
pending = response.get("UnprocessedItems", {})
if pending: # throttled items come back here, not as an exception
attempt += 1
time.sleep(random.uniform(0, min(20, 0.05 * 2 ** attempt)))
- Find the hot key:
aws dynamodb update-contributor-insights --table-name Orders --contributor-insights-action ENABLE --contributor-insights-mode THROTTLED_KEYS. - When the SDK's retries run out, catch
botocore.exceptions.ClientError, branch onerr.response["Error"]["Code"]and log the error details, which includeThrottlingReasons. Alarm onReadThrottleEvents/WriteThrottleEventsabove 0 for user-facing tables (alarm mechanics:notes/monitoring-guide.md). - A read-hot key that tolerates staleness → DAX; a write-hot key → shard it; a scan marching through the key space → rate-limit it or spread it with a parallel scan.
Traps
- Trap: "Throttling means the table needs more capacity." → Reality: read the reason: a hot partition or a GSI is the usual culprit, and raising table capacity fixes neither.
- Trap: "Raise the base table's WCU to fix
IndexWriteProvisionedThroughputExceeded." → Reality: raise the GSI's write capacity (at least equal to the table's write rate) or fix the GSI key. - Trap: "
BatchWriteItemthrows when some items are throttled." → Reality: it returns them inUnprocessedItems; ignoring that silently loses writes. - Trap: "Retry immediately in a tight loop." → Reality: use exponential backoff (with jitter for concurrent clients); instant retries keep hitting the same limit.
- Trap: "
ThrottledRequestscounts every throttled index write." → Reality: it rises once per request; the…ThrottleEventsmetrics give the detail.
Practice questions
-
A provisioned
Orderstable has 4,000 WCU and consumes about 1,500 WCU, yet clients receiveProvisionedThroughputExceededExceptiononPutItem. TheThrottlingReasonslist showsIndexWriteProvisionedThroughputExceededwith the ARN of theByStatusglobal secondary index. Which change resolves the throttling with the FEWEST changes?- A. Increase the write capacity of the
ByStatusindex, or enable auto scaling on it. - B. Increase the base table's provisioned WCU to 8,000.
- C. Switch the API's reads to eventually consistent mode.
- D. Place a DynamoDB Accelerator (DAX) cluster in front of the table.
- A. Increase the write capacity of the
-
A nightly job loads 2 million records into an on-demand table with
BatchWriteItem. The job reports success, but about 3 % of the records are missing and no exceptions were logged. The table normally handles a few hundred writes per second. Which TWO actions should the developer take? (Select TWO.)- A. Check
UnprocessedItemsin every response and resubmit those items with exponential backoff. - B. Replace
BatchWriteItemwithTransactWriteItemsso that each batch is all-or-nothing. - C. Enable DynamoDB Streams so that DynamoDB retries the failed writes automatically.
- D. Switch the table to provisioned mode with a 90 % auto scaling target.
- E. Raise the table's write warm throughput before the job, or ramp the load up gradually.
- A. Check
Answers
- A. The reason and ARN point at the GSI: an under-provisioned index back-pressures base-table writes, so the index needs capacity. B adds capacity where none is missing; C changes reads, not writes; D caches reads and does nothing for index writes.
- A, E. Throttled batch items come back in
UnprocessedItemsinstead of raising an error, and jumping far above the previous peak of an on-demand table throttles unless it is pre-warmed or ramped. B doubles the capacity cost and caps each call at 100 items; C only records writes that succeeded; D at 90 % leaves even less headroom, and auto scaling lags.
5. Reading data with GetItem Query and Scan
Concept
GetItemreads one item by its full primary key — the cheapest and fastest read;BatchGetItemdoes the same for up to 100 keys (§8). Neither can read from an index.Queryreads one item collection: equality on the partition key plus an optional sort key condition, returned in sort key order — from the table, an LSI or a GSI.Scanreads every item of a table or index page by page and applies any filter afterwards. Keep it for exports, migrations and small tables, not for request paths.- Three expressions shape a read:
KeyConditionExpression(which items are read —Queryonly),FilterExpression(which of the read items come back) andProjectionExpression(which attributes come back). Only the first one reduces the work and the bill. - A
QueryorScanpage holds at most 1 MB, measured before the filter. When a response carriesLastEvaluatedKey, send the same request again with it asExclusiveStartKey; its absence is the only sign that the last page has arrived.
Exam facts
- Key condition: partition key
=only; sort key=,<,<=,>,>=,BETWEEN … AND …orbegins_with()— nocontains,<>orORon keys. - Filters add
<>,OR,NOT,IN,contains,size,attribute_exists/attribute_not_exists. A Query filter cannot use key attributes; a Scan filter can. Limitcaps the items evaluated per call, before the filter:Limit=50with a filter can return fewer than 50 items while more matches remain.Count= items returned after the filter,ScannedCount= items evaluated; a wide gap means wasted capacity.ScanIndexForward=false→ descending sort key order (newest first).Select=COUNTreturns only the count.- Reads are eventually consistent by default;
ConsistentRead=true(double cost) works on tables and LSIs withGetItem,BatchGetItem,QueryandScan. GSIs and streams are eventually consistent only: a strongly consistent read on a GSI →ValidationException. - Scan capacity: a table scan uses the table's RCU, a GSI scan the index's RCU, an LSI scan the table's RCU. A sequential scan reads one partition at a time.
- Parallel scan: each worker sends its own
Segment(0-based) with the sameTotalSegments; items map to segments by a hash of the partition key. It finishes sooner but burns throughput faster — setLimitand run it off-peak. - Expression limits: 4 KB per expression string; 300 operators or functions per expression; 100 operands for
IN; 255 bytes per expression attribute name or value placeholder. Reserved words (Status,Name,Date,Totaland hundreds more) need a#placeholderfromExpressionAttributeNames, or the request fails with aValidationException. - AWS CLI pagination (the CLI follows
LastEvaluatedKeyfor you):
| Option | Effect |
|---|---|
| none | every page is fetched and printed |
--max-items N |
prints at most N items plus a NextToken |
--starting-token <token> |
resumes from that NextToken |
--page-size N |
N items per underlying call: more calls, same output |
--no-paginate |
one call, first page only |
--limit N |
sends the API Limit; the output carries LastEvaluatedKey |
- ⚠️ Changed: the CLI's default page size is set per service (Amazon S3 uses 1,000), and each DynamoDB call is bounded by the 1 MB page. Older material may say
--page-sizedefaults to 1,000 items for every command.
Developer angle
# newest-first October orders of one customer, shipped only, two attributes, at most 20 printed
aws dynamodb query --table-name Orders \
--key-condition-expression "CustomerId = :c AND begins_with(OrderDate, :m)" \
--filter-expression "#st = :shipped" \
--projection-expression "OrderDate, Amount" \
--expression-attribute-names '{"#st":"Status"}' \
--expression-attribute-values '{":c":{"S":"C-1001"},":m":{"S":"2026-10"},":shipped":{"S":"SHIPPED"}}' \
--no-scan-index-forward --max-items 20
- In code, loop: call
query(**request), keep theItems, and while the response hasLastEvaluatedKey, setrequest["ExclusiveStartKey"]to it and call again; SDK paginators (client.get_paginator("query")or("scan")) hide that loop. boto3'sKey(...)/Attr(...)condition builders insert the name placeholders for you. - Parallel scan: N workers each call
scan(Segment=i, TotalSegments=N, Limit=...)with their ownExclusiveStartKeyloop. - IAM:
dynamodb:GetItem,dynamodb:Queryanddynamodb:Scanare separate actions — grantScanonly to the jobs that need it; reading an index also needs the…/index/<name>ARN.
Traps
- Trap: "A filter expression lowers the RCU bill." → Reality: capacity is charged on the items read; only a tighter key condition or an index reduces it.
- Trap: "
Limit=50returns 50 matching items." → Reality: it evaluates 50; the filter may leave fewer, withLastEvaluatedKeyto continue. - Trap: "One
Querycall returns every match." → Reality: pages stop at 1 MB; loop onLastEvaluatedKey. - Trap: "Query on a non-key attribute by adding a filter." → Reality: that only filters one partition's items; a new access pattern needs a GSI (§6).
- Trap: "
--page-sizelimits how many items the CLI returns." → Reality:--max-itemslimits the output;--page-sizeonly splits the work into smaller calls.
Practice questions
-
A reporting Lambda function queries one customer's order history with a
FilterExpressionthat keeps only shipped orders and withLimitset to 50. It often receives fewer than 50 orders although the customer has hundreds of shipped orders. What is the cause?- A. A
FilterExpressioncannot reference non-key attributes in aQuery. - B. The query reads from an eventually consistent replica that lacks recent items.
- C.
Limitcaps the items evaluated before the filter runs, and the response carriesLastEvaluatedKeyfor the next page. - D.
Queryreturns at most 50 items per call whenever a filter is present.
- A. A
-
A nightly job scans a 200 GB table with
ConsistentRead=trueto find items whose status is expired, about 1 % of the items. The job is the only workload at night, and data a few minutes old is acceptable. The developer must cut the read capacity the job consumes. Which TWO changes achieve this? (Select TWO.)- A. Add a
FilterExpressionon the status attribute so that only expired items are returned. - B. Write an
expiredAtattribute only on expired items, create a GSI keyed on it, and scan that sparse index instead of the table. - C. Raise
TotalSegmentsso that more workers scan in parallel. - D. Add a
ProjectionExpressionthat returns only the key attributes. - E. Remove
ConsistentRead=trueso that the scan uses eventually consistent reads.
- A. Add a
Answers
- C.
Limitbounds the items read per call; the filter then drops the non-shipped orders, so a page can be short whileLastEvaluatedKeysays more data remains. A is false — filters work on non-key attributes; B does not describe how a table query behaves; D invents a cap — pages are bounded byLimitand 1 MB. - B, E. A sparse GSI holds only the expired items, so scanning it reads about 1 % of the data, and eventually consistent reads cost half. A and D shrink the response, not the capacity consumed; C makes the scan finish sooner while reading exactly as much.
6. Local and global secondary indexes
Concept
- A secondary index gives the same items another key so that
Query(orScan) can serve another access pattern. DynamoDB maintains indexes automatically; applications never write to them. - The projection decides which attributes are copied into the index:
KEYS_ONLY(table and index keys),INCLUDE(plus the listedNonKeyAttributes) orALL. Bigger projections cost more storage and more index write capacity. - LSI (local secondary index): same partition key as the table, another sort key (one scalar attribute). It lives in the base table's partitions, so it shares the table's throughput, supports strongly consistent reads and can fetch non-projected attributes from the table (extra latency and RCU). Defined only in
CreateTable. - GSI (global secondary index): any partition key and optional sort key from top-level
S,NorBattributes; key values need not be unique. Stored separately with its own throughput on provisioned tables; eventually consistent only; queries see only projected attributes. Create or delete one at any time. - Sparse index: items that lack the index key attribute are simply absent from the index — a cheap way to index only flagged items.
- ⚠️ Changed: a GSI key can be multi-attribute — up to 4 attributes for the partition key and up to 4 for the sort key; a query needs equality on every partition-key attribute and uses the sort-key attributes left to right, with any range condition last. Older material may say a GSI key is one partition-key attribute plus at most one sort-key attribute, so values must be concatenated into a synthetic key.
Exam facts
| LSI | GSI | |
|---|---|---|
| Partition key | Same as the table | Any attribute |
| Created | Only with CreateTable; fixed for the table's life |
Any time; UpdateTable creates and deletes them (with a backfill) |
| Per table | 5 (fixed) | 20 (default quota) |
| Read consistency | Eventual or strong | Eventual only |
| Throughput | The table's RCU/WCU | Its own RCU/WCU (provisioned) or the table's on-demand mode |
| Non-projected attributes | Fetched from the table (extra RCU and latency) | Not available |
| Size constraint | 10 GB item collection per partition key value | None |
- Projected attributes: at most 100
NonKeyAttributesacross all indexes of a table (INCLUDEonly; the same name in two indexes counts twice). - Index write cost: a new item carrying the index key = 1 index write; changing an index key value = 2 (delete + put); removing the key attribute = 1; changing only projected attributes = 1; changing other attributes = 0. Each index entry adds about 100 bytes of storage overhead.
- GSI back-pressure (§4): a GSI short of write capacity throttles writes to the base table — keep a provisioned GSI's WCU at least equal to the table's write rate and give it a high-cardinality partition key.
- GSI propagation is asynchronous (normally a fraction of a second), so a query right after a write may miss it. A backfill on an existing table consumes write capacity (
OnlineIndexConsumedWriteCapacity); watchIndexStatusandBackfillinginDescribeTable. - LSI side effects: the 400 KB item limit includes the item's LSI entries; an item collection above 10 GB →
ItemCollectionSizeLimitExceededException(track it withReturnItemCollectionMetrics=SIZE); item collections of a table with an LSI can't be split across partitions; MRSC global tables don't support LSIs. - ⚠️ Changed: a GSI inherits the table's capacity mode, so only GSIs of provisioned tables get their own RCU/WCU; on an on-demand table they need no throughput settings (an optional
OnDemandThroughputcap is possible). Older material may say every GSI must be provisioned with its own RCU and WCU. GSIs also use the base table's table class.
Developer angle
Add a GSI to a live on-demand table, then query it:
aws dynamodb update-table --table-name Orders \
--attribute-definitions AttributeName=WarehouseId,AttributeType=S AttributeName=ShipDate,AttributeType=S \
--global-secondary-index-updates '[{"Create": {"IndexName": "ByWarehouse",
"KeySchema": [{"AttributeName": "WarehouseId", "KeyType": "HASH"},
{"AttributeName": "ShipDate", "KeyType": "RANGE"}],
"Projection": {"ProjectionType": "INCLUDE", "NonKeyAttributes": ["Amount"]}}}]'
aws dynamodb query --table-name Orders --index-name ByWarehouse \
--key-condition-expression "WarehouseId = :w AND ShipDate BETWEEN :a AND :b" \
--expression-attribute-values '{":w":{"S":"WH-7"},":a":{"S":"2026-10-01"},":b":{"S":"2026-10-12"}}'
- An LSI goes into the
create-tablecall itself (--local-secondary-indexes, with the table's partition key asHASHand the new sort key asRANGE);update-tablehas no LSI option. - On a provisioned table, put
ProvisionedThroughputinside the GSICreateblock and give the index its own scaling policy (§3); wait forIndexStatusACTIVEbefore routing traffic to it. - IAM: querying the index needs
dynamodb:Queryonarn:aws:dynamodb:<region>:<account>:table/Orders/index/ByWarehouse(ortable/Orders/index/*), not only on the table ARN.
Traps
- Trap: "Add an LSI to the production table." → Reality: LSIs exist only from
CreateTable; add a GSI or rebuild the table. - Trap: "Query the GSI with
ConsistentRead=true." → Reality: not supported; read the table or an LSI when strong consistency is required. - Trap: "GSI key values must be unique." → Reality: many items may share a GSI key; uniqueness applies only to the table's primary key.
- Trap: "A GSI query can return any attribute of the item." → Reality: only projected ones; an LSI can fetch the rest from the table, a GSI cannot.
- Trap: "A throttled GSI only slows index queries." → Reality: it throttles the table's writes as well.
Practice questions
-
An existing production table
Orders(partition keyCustomerId, sort keyOrderId) must now support a screen that lists one warehouse's orders sorted by ship date. The change must not interrupt the table. What should the developer do?- A. Add a local secondary index with partition key
WarehouseIdand sort keyShipDate. - B. Add a global secondary index with partition key
WarehouseIdand sort keyShipDate. - C. Scan the table with a
FilterExpressiononWarehouseIdand sort the results in code. - D. Stream every change into a second table keyed on
WarehouseIdandShipDate.
- A. Add a local secondary index with partition key
-
A developer is designing a new
Ticketstable (partition keyProjectId, sort keyTicketId). Support agents need a project's tickets ordered by priority, and the results must include writes made a moment earlier. Which TWO steps meet these requirements? (Select TWO.)- A. Define a local secondary index with sort key
Priorityin theCreateTablerequest. - B. Create a global secondary index on
Priorityafter the table goes live. - C. Query the GSI with
ConsistentReadset totrue. - D. Query the local secondary index with
ConsistentReadset totrue. - E. Run a
ScanwithScanIndexForward=falseonPriority.
- A. Define a local secondary index with sort key
Answers
- B. A GSI can use a new partition key and can be added to a live table, which keeps serving traffic while the index backfills. A is impossible — LSIs keep the table's partition key and exist only from table creation; C reads the whole table on every request; D works but adds a pipeline and a second table to operate.
- A, D. An LSI keeps
ProjectIdas the partition key, addsPriorityas an alternate sort key, and accepts strongly consistent queries. B and C fail because GSIs are eventually consistent only; E —ScanIndexForwardbelongs toQuery, and a scan reads the whole table anyway.
7. Writing items and conditional writes
Concept
PutItemwrites a whole item: a new key creates it, an existing key is overwritten completely (any attribute you leave out is lost).UpdateItemtouches only the attributes named in its update expression, and a key that doesn't exist yet gets a new item (upsert).DeleteItemremoves one item by key. Each call is atomic for its single item.- Without a condition, writes are unconditional: the last writer wins, so two clients doing read-modify-write lose each other's updates.
- A condition expression is evaluated against the item's current state; when it is false, DynamoDB rejects the write with
ConditionalCheckFailedException(HTTP 400). Conditions work onPutItem,UpdateItem,DeleteItemand every action ofTransactWriteItems— not onBatchWriteItem. - Update expression clauses:
SET(assign;+/-;if_not_exists();list_append()),REMOVE(attributes or list elements),ADD(numbers and sets only),DELETE(elements of a set). All actions in one expression read the item's pre-update values. - Atomic counter:
SET hits = hits + :one(orADD hits :one) increments without a read and never loses increments under concurrency — but a retried call increments twice, so use it where small over- or under-counting is acceptable. - Optimistic locking: keep a
versionnumber on each item; write with the conditionversion = :expectedand increment it in the same update. A concurrent writer makes the condition fail; the losing client re-reads, then retries or reports the conflict. No lock is ever held.
Exam facts
- Condition functions:
attribute_exists,attribute_not_exists,attribute_type,begins_with,contains,size; comparators=,<>,<,<=,>,>=,BETWEEN,IN; logicalAND,OR,NOT. Function names are case-sensitive. - "Insert only if new":
attribute_not_exists(<partition key name>). The condition is checked against the item with the request's full key, so testing the partition key attribute is enough even on a composite-key table. - A failed condition still consumes write capacity (at least 1 WCU) and by default says nothing about the item — unless you set
ReturnValuesOnConditionCheckFailure=ALL_OLD, which puts the current item into the exception without an extra read and without RCU. ReturnValues:PutItemandDeleteItem→NONE(default) orALL_OLD;UpdateItem→NONE,ALL_OLD,UPDATED_OLD,ALL_NEW,UPDATED_NEW. Returned values cost no RCU.- A conditional update can be idempotent when it checks the same attribute it changes against the value you read (
version = :expected, orprice = :seenon an update that adds toprice): once the first attempt has applied, a retry after a timeout fails withConditionalCheckFailedExceptioninstead of applying twice. A range check such asstock >= :qtyon a decrement can still be true after the first attempt, so a retry decrements again. AWS also recommendsSETrather thanADDin general to keep operations idempotent: resendingSET price = :newleaves the same value, while every resentADD(orSET n = n + :inc) applies again. - Updating a nested map path whose parent map doesn't exist →
ValidationException(create empty maps when the item is first written). - SDK helpers: the Java
DynamoDBMapper(SDK for Java 1.x) manages a@DynamoDBVersionAttribute; in the SDK for Java 2.x Enhanced Client the defaultVersionedRecordExtensiondoes the same for a@DynamoDbVersionAttributefield. - Global tables (MREC): the condition is checked against the local Region's copy, and concurrent cross-Region writes resolve by last writer wins, so optimistic locking does not protect across Regions.
Developer angle
# create only if the username is free; on conflict return the existing item inside the error
aws dynamodb put-item --table-name Users \
--item '{"username":{"S":"mira"},"createdAt":{"N":"1760256000"}}' \
--condition-expression "attribute_not_exists(username)" \
--return-values-on-condition-check-failure ALL_OLD
# atomic counter: no read needed, but a retried call counts twice
aws dynamodb update-item --table-name PageViews \
--key '{"page":{"S":"/pricing"}}' \
--update-expression "ADD hits :one" \
--expression-attribute-values '{":one":{"N":"1"}}' \
--return-values UPDATED_NEW
Optimistic locking with a version attribute:
import boto3
ddb = boto3.client("dynamodb")
key = {"caseId": {"S": "CASE-9"}}
seen = ddb.get_item(TableName="SupportCases", Key=key, ConsistentRead=True)["Item"]["version"]
try:
ddb.update_item(TableName="SupportCases", Key=key,
UpdateExpression="SET #t = :title, #v = #v + :one",
ConditionExpression="#v = :seen",
ExpressionAttributeNames={"#t": "title", "#v": "version"},
ExpressionAttributeValues={":title": {"S": "Refund approved"},
":one": {"N": "1"}, ":seen": seen})
except ddb.exceptions.ConditionalCheckFailedException:
pass # another writer won: re-read, merge or tell the user
- IAM:
dynamodb:PutItem,dynamodb:UpdateItem,dynamodb:DeleteItem; the condition keydynamodb:ReturnValuescan limit which return options a caller may request (§13).
Traps
- Trap: "Use an atomic counter for account balances or stock levels." → Reality: retries can apply it twice; use a conditional update or a transaction.
- Trap: "
PutItemupdates only the attributes I send." → Reality: it replaces the whole item; useUpdateItemfor partial changes. - Trap: "
UpdateItemfails when the item doesn't exist." → Reality: it creates the item, unless a condition such asattribute_exists(<key>)forbids it. - Trap: "Retry a
ConditionalCheckFailedExceptionwith backoff." → Reality: the condition will keep failing; re-read the item and decide. - Trap: "Guard a
PutItemwith aFilterExpression, or put a condition onBatchWriteItem." → Reality: single writes and transaction actions take aConditionExpression; filters exist only onQuery/Scan, and batches accept no conditions.
Practice questions
-
Two support agents open the same case in a web app, and the second agent's save silently overwrites the first agent's changes. The team wants conflicting saves detected without holding locks and with the LEAST additional infrastructure. What should the developer implement?
- A. Wrap every save in
TransactWriteItemswithout a condition expression. - B. Read the case with
ConsistentRead=trueimmediately before every save. - C. Process the table's DynamoDB stream to detect overwrites after they happen.
- D. Keep a version number on each item and save with a condition on the expected version.
- A. Wrap every save in
-
A sign-up service must create a user item only if the username, which is the table's partition key, is not taken. When the name is taken, the response must include the existing user's
createdAtvalue without another read request. Which TWO actions meet these requirements? (Select TWO.)- A. Call
BatchWriteItemwith a condition on the username. - B. Call
PutItemwith theConditionExpressionattribute_not_exists(username). - C. Set
ReturnValuesOnConditionCheckFailuretoALL_OLDon the request. - D. Set
ReturnValuestoALL_NEWon thePutItemrequest. - E. Query a GSI on
usernamebefore writing to check whether the name exists.
- A. Call
Answers
- D. Optimistic locking with a conditional write rejects the stale save with
ConditionalCheckFailedException, and the app reloads the case. A without a condition still overwrites; B narrows the window but doesn't close the race; C notices only after the data is lost. - B, C. The condition makes the put fail when the key exists, and
ALL_OLDon failure returns the existing item inside the exception at no read cost. A — batches accept no conditions; D —PutItemsupports onlyNONEandALL_OLDand returns nothing when the condition fails; E adds a read and leaves a race between the check and the write.
8. Batch operations and transactions
Concept
- A batch call packs many independent single-item requests into one round trip. It is not atomic: some requests may succeed while others come back unprocessed.
BatchWriteItem: up to 25 put or delete requests (no updates, no conditions) across one or more tables.BatchGetItem: up to 100 keys across one or more tables, with read consistency chosen per table.
- A transaction makes a group of actions all-or-nothing (ACID) across one or more tables of the same account and Region:
TransactWriteItems:Put,Update,DeleteandConditionCheckactions, each with an optional condition.TransactGetItems:Getactions that return one consistent snapshot of several items.
- Choose: bulk loads and fan-out reads → batch; an order plus its stock reservation, a balance transfer, uniqueness enforced across several items, anything "all or none" → transaction.
Exam facts
- Batch limits:
BatchWriteItem25 requests, 16 MB per call, 400 KB per item;BatchGetItem100 items, 16 MB. A batch fails as a whole only when every request fails; otherwise inspectUnprocessedItems/UnprocessedKeysand resubmit them with exponential backoff (the usual cause is throttling). Capacity is computed per item. - High-level SDK helpers resend unprocessed items for you: boto3
Table.batch_writer(), the Java Enhanced Client andDynamoDBMapper. - ⚠️ Changed: a transaction holds up to 100 actions on 100 distinct items, 4 MB in total. Older material may say 25 items per transaction.
- One action per item per transaction (no
ConditionCheckandUpdateon the same key), and transactions cannot target indexes. - Cost: every item is read or written twice (prepare + commit) → 2 RCU per 4 KB for a transactional read, 2 WCU per 1 KB for a transactional write; the capacity is consumed even when the transaction is cancelled, and both operations appear in CloudWatch.
- Idempotency: pass a
ClientRequestToken(at most 36 characters); repeating the call within 10 minutes of completion changes nothing; the same token with different parameters →IdempotentParameterMismatchException. The AWS SDKs add a token automatically. - Failure:
TransactionCanceledExceptionwithCancellationReasonsin request order, each with a code such asNone,ConditionalCheckFailed,TransactionConflict,ProvisionedThroughputExceeded,ThrottlingError,ValidationErrororItemCollectionSizeLimitExceeded. SDKs don't retry this exception; they do retryTransactionInProgressException. - Isolation: serializable between transactions and single-item operations (
GetItem,PutItem,UpdateItem,DeleteItem); read-committed againstQuery,ScanandBatchGetItemas a whole; a single-item write that collides with an ongoing transaction →TransactionConflictException. - Side effects: a transaction's changes reach GSIs, streams and backups gradually, so stream consumers must not expect them together. On MREC global tables a transaction is atomic only in its own Region; MRSC global tables don't support transactions. Through DAX, see §10.
- IAM: transactions are authorized action by action —
dynamodb:PutItem,dynamodb:UpdateItem,dynamodb:DeleteItem,dynamodb:GetItem,dynamodb:ConditionCheckItemon every table involved;dynamodb:EnclosingOperation(TransactWriteItems,TransactGetItems) can allow or block transactional use. Batches needdynamodb:BatchWriteItem/dynamodb:BatchGetItem. - AWS advises against transactions for bulk ingestion: use
BatchWriteItem, whose writes cost half as much per item.
Developer angle
An order that must reserve stock, check the customer and be recorded as one unit, safe to resend after a timeout:
import uuid
import boto3
from botocore.exceptions import ClientError
ddb = boto3.client("dynamodb")
order_id = str(uuid.uuid4()) # doubles as the idempotency token: resend with the SAME value
try:
ddb.transact_write_items(
ClientRequestToken=order_id,
TransactItems=[
{"ConditionCheck": {"TableName": "Customers", "Key": {"customerId": {"S": "C-7"}},
"ConditionExpression": "accountStatus = :active",
"ExpressionAttributeValues": {":active": {"S": "ACTIVE"}}}},
{"Update": {"TableName": "Inventory", "Key": {"sku": {"S": "SKU-42"}},
"UpdateExpression": "SET stock = stock - :qty",
"ConditionExpression": "stock >= :qty",
"ExpressionAttributeValues": {":qty": {"N": "2"}}}},
{"Put": {"TableName": "Orders",
"Item": {"orderId": {"S": order_id}, "sku": {"S": "SKU-42"}, "qty": {"N": "2"}},
"ConditionExpression": "attribute_not_exists(orderId)"}},
],
)
except ClientError as err:
if err.response["Error"]["Code"] == "TransactionCanceledException":
print([reason["Code"] for reason in err.response.get("CancellationReasons", [])])
# for example ['None', 'ConditionalCheckFailed', 'None'] -> not enough stock
else:
raise
- Bulk loading:
with table.batch_writer() as batch:thenbatch.put_item(Item=...)in a loop — boto3 groups the puts into 25-item calls and resends leftovers. With the CLI (aws dynamodb batch-write-item --request-items file://events.json), every item was written only when the response shows"UnprocessedItems": {}. - Repeated
TransactionConflictcancellations mean many writers fight over the same items: shrink the transaction or redesign the hot item rather than retrying harder.
Traps
- Trap: "
BatchWriteItemis atomic." → Reality: partial success is normal; handleUnprocessedItems. - Trap: "
BatchWriteItemcan update attributes." → Reality: put and delete only; updates needUpdateItemor a transaction. - Trap: "Transactions cost the same as normal writes." → Reality: double, and cancelled transactions still pay.
- Trap: "A transaction can span Regions or accounts." → Reality: one account, one Region; global tables replicate its changes non-atomically.
- Trap: "Retry
TransactionCanceledExceptionuntil it works." → Reality: readCancellationReasonsfirst; a failed condition will fail again.
Practice questions
-
A checkout service must decrement the stock of a product item, create an order item in a separate
Orderstable, and reject the order when the customer's account is suspended — either everything happens or nothing does. Client timeouts sometimes make the service send the same request twice. Which approach meets these requirements?- A.
TransactWriteItemswith a stock condition on the update, aConditionCheckon the customer item, and the sameClientRequestTokenon resend - B.
BatchWriteItemcontaining the order and the new stock value, retried with exponential backoff - C. A
GetItemon the customer followed byUpdateItemandPutItemcalls, each retried on failure - D.
TransactGetItemsto read the stock and the customer, then aBatchWriteItemwith the new values
- A.
-
A feature runs 20
TransactWriteItemscalls per second, and each call writes two items of 1.5 KB. Which TWO statements about the capacity this feature consumes are correct? (Select TWO.)- A. Cancelled transactions consume no write capacity.
- B. Supplying a
ClientRequestTokenhalves the capacity each transaction consumes. - C. Each item write consumes 4 WCU, so the feature needs 160 WCU.
- D. One call could instead write 200 items, as long as each item is under 1 KB.
- E. The prepare and commit writes of each item are visible in the table's CloudWatch capacity metrics.
Answers
- A. One transaction makes the three steps atomic, the conditions block overselling and suspended customers, and the reused token turns a resend into a no-op for 10 minutes. B is not atomic and accepts no conditions; C can leave stock decremented without an order and races with concurrent checkouts; D separates the read from the write, so nothing is atomic.
- C, E. 1.5 KB rounds up to 2 KB = 2 WCU, doubled for a transaction = 4 WCU; 20 calls × 2 items × 4 WCU = 160 WCU, and both underlying writes show in CloudWatch. A — cancelled transactions still consume capacity; B — tokens give idempotency, not a discount; D — the limit is 100 actions per transaction.
9. PartiQL
Concept
- PartiQL for DynamoDB is a SQL-compatible syntax (a subset of the open PartiQL language) for
SELECT,INSERT,UPDATEandDELETEon items. It is another door into the same engine: same latency, same capacity rules, same 400 KB item and 1 MB page limits. - Each statement targets one table or one index (
SELECT … FROM "Orders"."ByWarehouse"): no joins, no aggregates, no multi-table statement. - A
SELECTis cheap only when itsWHEREclause pins the partition key with=orIN(plus optional sort key conditions) — it then runs likeGetItemorQuery. Anything else, including anORwith a non-key attribute, becomes a full table scan.ORDER BYworks on key attributes. - Three APIs:
ExecuteStatement(one statement),BatchExecuteStatement(a batch of reads or a batch of writes) andExecuteTransaction(an all-or-nothing group). Use them from the console's PartiQL editor, NoSQL Workbench, the AWS CLI or any SDK.
Exam facts
BatchExecuteStatement: up to 25 statements, all reads or all writes (never mixed); each read must give the full primary key (it maps toBatchGetItem); errors are reported per statement.ExecuteTransaction: up to 100 statements, all reads or all writes;EXISTS(SELECT …)plays the role of aConditionCheck; one failing statement cancels everything withTransactionCanceledException, whose reasons name the failing statement.INSERTnever overwrites: an existing key →DuplicateItemException(unlikePutItem).UPDATEandDELETEare single-item writes that need the full primary key inWHERE.- Parameters:
?placeholders plus a typedParameterslist — no string concatenation. A statement is at most 8,192 characters. - Reads support
ConsistentRead,Limit(items evaluated) and paging withNextToken;ReturnConsumedCapacityandReturnValuesOnConditionCheckFailureexist too. Contributor Insights doesn't track PartiQL requests. - IAM uses PartiQL actions:
dynamodb:PartiQLSelect,dynamodb:PartiQLInsert,dynamodb:PartiQLUpdate,dynamodb:PartiQLDeleteon the table or index ARN. Condition keys:dynamodb:FullTableScan(Bool) to deny scanningSELECTs;dynamodb:EnclosingOperation=ExecuteTransactionto allow or block transactional statements.
Developer angle
# keyed SELECT: runs as a Query (partition key equality + sort key condition)
aws dynamodb execute-statement \
--statement "SELECT OrderDate, Amount FROM Orders WHERE CustomerId = ? AND begins_with(OrderDate, ?)" \
--parameters '[{"S":"C-1001"},{"S":"2026-10"}]'
# INSERT fails with DuplicateItemException when the key already exists
aws dynamodb execute-statement \
--statement "INSERT INTO Orders VALUE {'CustomerId': ?, 'OrderDate': ?, 'Amount': ?}" \
--parameters '[{"S":"C-1001"},{"S":"2026-10-12#A19"},{"N":"12"}]'
# an index is addressed as "table"."index"
aws dynamodb execute-statement \
--statement "SELECT OrderDate FROM \"Orders\".\"ByWarehouse\" WHERE WarehouseId = 'WH-7'"
aws dynamodb batch-execute-statement --statements file://reads.json # at most 25 statements
aws dynamodb execute-transaction --transact-statements file://order-writes.json # at most 100, all-or-nothing
- Allow keyed
SELECTs but block full scans with two statements on the table ARN: anAllowofdynamodb:PartiQLSelect, plus aDenyof the same action with"Condition": {"Bool": {"dynamodb:FullTableScan": "true"}}. - boto3:
client.execute_statement(Statement=..., Parameters=[{"S": "C-1001"}]), looping while the response containsNextToken.
Traps
- Trap: "PartiQL lets you join two tables." → Reality: one table or index per statement.
- Trap: "A PartiQL
SELECTis efficient because it looks like SQL." → Reality: unless the partition key is pinned with=orIN, it scans the whole table. - Trap: "Granting
dynamodb:Queryanddynamodb:Scanlets users run PartiQL." → Reality: PartiQL needs thedynamodb:PartiQL*actions. - Trap: "
INSERToverwrites likePutItem." → Reality: it raisesDuplicateItemException. - Trap: "
BatchExecuteStatementtakes 100 statements, reads and writes mixed." → Reality: 25, all reads or all writes (transactions take 100).
Practice questions
-
Analysts run ad hoc PartiQL
SELECTstatements against theOrderstable from the console. A security engineer must stop statements that would read the whole table while still allowing statements that specify the partition key. Which solution follows least privilege?- A. Remove
dynamodb:Scanfrom the analysts' IAM policy. - B. Set a maximum on-demand read throughput on the
Orderstable. - C. Allow
dynamodb:PartiQLSelectand add a statement that denies it whendynamodb:FullTableScanistrue. - D. Require
dynamodb:Selectto equalSPECIFIC_ATTRIBUTESin the analysts' policy.
- A. Remove
-
A developer is porting a data-fix script to PartiQL. The script inserts 60 new order items, and the business requires that either all 60 are inserted or none are. Which TWO statements are correct? (Select TWO.)
- A. A single
ExecuteTransactioncall can hold all 60INSERTstatements and makes them all-or-nothing. - B. A single
BatchExecuteStatementcall can hold all 60INSERTstatements. - C. A batch or transaction may combine
SELECTandINSERTstatements in the same call. - D. If one of the keys already exists, the whole transaction is cancelled with
TransactionCanceledException, and the reasons identify the failingINSERT. - E. The script's role needs
dynamodb:PutItem, because PartiQLINSERTis authorized asPutItem.
- A. A single
Answers
- C.
dynamodb:FullTableScanidentifies scanningSELECTs, so an explicit deny on it blocks only those. A has no effect on PartiQL, which is authorized bydynamodb:PartiQLSelect; B limits throughput, not access; D controls the returned attributes, not scans. - A, D.
ExecuteTransactionaccepts up to 100 statements atomically, and a duplicate key in anyINSERTcancels the whole transaction with a reason for that statement. B — batches stop at 25 statements and aren't atomic; C — reads and writes can't be mixed; E — the action isdynamodb:PartiQLInsert.
10. DAX and caching choices
Concept
- DynamoDB Accelerator (DAX) is a managed, in-memory, write-through cache cluster inside your VPC that speaks the DynamoDB API. Point the DAX client (Java, .NET, Node.js, Python, Go) at the cluster endpoint and existing
GetItem/Querycode keeps working — cache hits return in microseconds. - Two independent caches, each with a 5-minute default TTL (parameter group settings
record-ttl-millisandquery-ttl-millis) and LRU eviction:- Item cache — results of
GetItem/BatchGetItem, keyed by primary key; refreshed by writes that go through DAX. - Query cache — result sets of
Query/Scan, keyed by the request parameters; not refreshed by writes, so it can serve stale results until its TTL expires.
- Item cache — results of
- Eventually consistent reads are served from the cache; strongly consistent reads and
TransactGetItemspass straight through and are not cached. A write goes to DynamoDB first and then into the item cache; a failed write is never cached. Empty results are cached too (negative caching).TransactWriteItemswrites through, then DAX fills its cache with backgroundTransactGetItemscalls (extra RCU). - ElastiCache (Valkey, Redis OSS, Memcached) is the general-purpose alternative: your code decides what to cache, so it suits computed or aggregated results, data combined from several sources, sessions and leaderboards — at the price of cache logic in the application (
notes/rds-guide.md§9–§10).
Exam facts
- ⚠️ Changed: the DynamoDB Developer Guide allows up to 11 nodes per cluster — 1 primary plus up to 10 read replicas. Older material may say up to 10 nodes. ⚠️ unverified which figure is current: the
CreateClusterAPI and CLI references still give aReplicationFactorof at most 10 (1 primary + 9 replicas), so don't hinge an answer on 10 vs 11. - Production: at least 3 nodes in different AZs (fault tolerant; the primary fails over to a replica); 1–2 nodes only for dev and test.
- Scale out for reads by adding replicas (
increase-replication-factor, online); scale up only by creating a new cluster with a larger node type. Writes and cache misses load the primary. - A cluster serves tables of its own Region only; keep it at or below 500 tables. At most 50 nodes per account per Region.
- Networking: reachable only inside its VPC (subnet group; a security group allowing TCP 8111 for
dax://or 9111 for TLSdaxs://endpoints). Lambda functions must be attached to that VPC. Encryption in transit can't be added to an existing cluster, and a TLS cluster's ACM certificate puts the cluster name into public Certificate Transparency logs — keep names free of sensitive data. - Security model: a service role trusted by
dax.amazonaws.commakes the DynamoDB calls; callers needdax:GetItem,dax:Query,dax:PutItem… on the cluster ARNarn:aws:dax:<region>:<account>:cache/<cluster>. DAX does not enforce per-user DynamoDB permissions such asdynamodb:LeadingKeys: anyone allowed into the cluster reads what the cluster role can — isolate data with separate clusters. DAX management APIs accept only"Resource": "*". - An overloaded node returns
ThrottlingException(metricThrottledRequestCount) → add replicas or move to a larger node type. - Writes that bypass DAX (other applications, global-table replication, direct SDK calls) leave cached data stale until the TTL expires. TTL
0on the item cache = refresh only through eviction or write-through;0on the query cache = no query caching. Parameter group changes apply only to items cached afterwards. - Choose DAX for read-heavy or bursty reads, hot read keys, microsecond latency and minimal code change; avoid it for write-heavy tables, strongly consistent reads, or results that must reflect other writers immediately.
Developer angle
aws dax create-parameter-group --parameter-group-name catalog-ttl
aws dax update-parameter-group --parameter-group-name catalog-ttl \
--parameter-name-values "ParameterName=record-ttl-millis,ParameterValue=120000" \
"ParameterName=query-ttl-millis,ParameterValue=60000"
aws dax create-cluster --cluster-name catalog-cache --node-type dax.r5.large \
--replication-factor 3 --cluster-endpoint-encryption-type TLS \
--iam-role-arn arn:aws:iam::111122223333:role/DaxCatalogServiceRole \
--subnet-group-name catalog-private --parameter-group-name catalog-ttl
aws dax increase-replication-factor --cluster-name catalog-cache --new-replication-factor 5
- Caller policy for reads through the cluster: allow
dax:GetItem,dax:BatchGetItemanddax:Queryonarn:aws:dax:eu-west-1:111122223333:cache/catalog-cache; the cluster's service role separately needs the matchingdynamodb:*actions on the table. - Bulk loads: write around DAX, straight to DynamoDB, so the cache keeps frequently read items rather than freshly written ones.
Traps
- Trap: "DAX speeds up strongly consistent reads." → Reality: they bypass the cache.
- Trap: "Writes through DAX refresh cached
Queryresults." → Reality: only the item cache is updated; query results stay until their TTL. - Trap: "DAX keeps per-user
LeadingKeysrestrictions." → Reality: access through DAX is governed by the cluster role and the caller'sdax:*permissions. - Trap: "A Lambda function outside the VPC can use DAX." → Reality: DAX is reachable only inside its VPC.
- Trap: "ElastiCache is the no-code-change cache for DynamoDB." → Reality: DAX is API-compatible; ElastiCache needs caching logic in your code.
- Trap: "DAX helps a write-heavy table." → Reality: every write still reaches DynamoDB, plus one network hop.
Practice questions
-
A product catalog API runs on Lambda functions in a VPC and reads a few thousand very popular items with
GetItem. Read latency must drop from milliseconds to microseconds, data a few minutes old is acceptable, and the team wants the fewest code changes. Which solution meets these requirements?- A. Add an Amazon ElastiCache for Valkey cluster and implement lazy loading in the functions.
- B. Create a DAX cluster in the VPC and switch the functions to the DAX client.
- C. Switch the table to on-demand capacity mode.
- D. Create a global secondary index for the popular items.
-
After DAX was added, users see search results (served by
Query) that stay outdated for up to 5 minutes after an item is updated through DAX, whileGetItemcalls through DAX return the new values at once. Which TWO statements explain or fix this? (Select TWO.)- A. Sending the updates directly to DynamoDB instead of through DAX would refresh the query cache.
- B. Writes through DAX update the item cache but do not invalidate cached
Queryresults. - C. Adding read replicas to the cluster shortens the time results stay stale.
- D. Propagation to the table's GSI causes the 5-minute delay.
- E. Lowering
query-ttl-millisin a custom parameter group shortens how long query results stay cached.
Answers
- B. DAX returns cache hits in microseconds for eventually consistent reads and keeps the DynamoDB API, so only the client changes. A also caches but needs new cache-handling code; C changes billing, not latency; D adds another millisecond read path.
- B, E. The query cache is independent of the item cache and expires by TTL (5 minutes by default), so a shorter
query-ttl-millisreduces the staleness. A makes things worse — writes that bypass DAX don't touch either cache; C adds read capacity, not freshness; D — GSI propagation takes a fraction of a second.
11. DynamoDB Streams and Lambda
Concept
- DynamoDB Streams records every item-level change (
INSERT,MODIFY,REMOVE) in near real time and keeps it for 24 hours. Each change appears exactly once, and changes to the same item appear in the order they happened (no ordering promise across different items). StreamViewTypesets what a record carries:KEYS_ONLY,NEW_IMAGE,OLD_IMAGEorNEW_AND_OLD_IMAGES. It can't be edited: disable the stream and enable a new one (which gets a new stream ARN).- A stream holds only changes made after it was enabled. Shards are created, split and closed automatically (each open shard serves one table partition), and a parent shard must be read before its children. Streams have their own endpoint (
streams.dynamodb.<region>.amazonaws.com) and SDK client, and enabling one doesn't slow the table. - Consumers: a Lambda event source mapping (ESM) — the usual exam answer; KCL applications through the DynamoDB Streams Kinesis Adapter; EventBridge Pipes (other targets, enrichment); Apache Flink.
- Kinesis Data Streams for DynamoDB is the alternative feed: changes go to a Kinesis data stream you own (longer retention, more consumers, enhanced fan-out, Firehose), but records can arrive out of order or twice — use
ApproximateCreationDateTimeto order and deduplicate. One Kinesis stream per table, same account and Region (notes/sqs-sns-kinesis-guide.md).
Exam facts
- ESM mechanics: Lambda polls each shard 4 times per second and invokes the function synchronously with a batch; its
GetRecordscalls are not charged (unless the function runs on Lambda Managed Instances). One concurrent batch per shard by default.
| ESM setting | Default | Range / note |
|---|---|---|
BatchSize |
100 | up to 10,000; batch payload at most 6 MB |
MaximumBatchingWindowInSeconds |
0 | up to 300 s |
StartingPosition |
required | TRIM_HORIZON or LATEST (LATEST can miss events while the mapping is created) |
ParallelizationFactor |
1 | up to 10 batches per shard; order kept per partition key |
MaximumRetryAttempts |
-1 (until the record expires) | 0–10,000 |
MaximumRecordAgeInSeconds |
-1 (until the record expires) | up to 604,800 |
BisectBatchOnFunctionError |
false | splits a failing batch to isolate bad records |
FunctionResponseTypes |
none | ReportBatchItemFailures enables partial batch responses |
FilterCriteria |
none | up to 5 filter patterns, evaluated before invocation |
- Default failure behaviour: a failing batch is retried until it succeeds or its records expire (24 h), blocking that shard. Set retries, a maximum record age, bisecting and an on-failure destination.
- ⚠️ Changed: the on-failure destination can be an SQS queue, SNS topic or S3 bucket; SQS and SNS receive only metadata (shard ID, sequence numbers) — fetch the records from the stream before they expire — while S3 also receives the batch payload. Older material may say only an SQS queue or SNS topic.
- Partial batch response: return
{"batchItemFailures": [{"itemIdentifier": "<SequenceNumber>"}]}; Lambda retries from the lowest failed sequence number; an empty list means full success. - Filtering: patterns match the
dynamodbfield (Keys,NewImage,OldImage) and record metadata such aseventNameoruserIdentity; numeric operators don't work on DynamoDB records (numbers arrive as strings). Filtered-out records are dropped, not retried. - Permissions: the execution role needs
dynamodb:DescribeStream,dynamodb:GetRecords,dynamodb:GetShardIteratoranddynamodb:ListStreams(managed policyAWSLambdaDynamoDBExecutionRole) plus the destination's permission (sqs:SendMessage,sns:Publish, ors3:PutObjectands3:ListBucket). - Readers per shard: at most 2 concurrent readers on a single-Region table, 1 on a global table; more readers get throttled. A function in another account can consume the stream once the stream's resource-based policy allows its execution role.
- A
PutItemorUpdateItemthat changes nothing writes no record. Disabling a stream closes its shards; the records stay readable for 24 h. - ⚠️ Changed: KCL consumers should use the DynamoDB Streams Kinesis Adapter with KCL 3.5.x+ and the SDK for Java 2.x; KCL 1.x reached end of support on 2026-01-30. Older material may say use the adapter with KCL 1.x and the SDK for Java 1.x.
Developer angle
aws dynamodb update-table --table-name Users \
--stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES
STREAM_ARN=$(aws dynamodb describe-table --table-name Users \
--query "Table.LatestStreamArn" --output text)
aws lambda create-event-source-mapping --function-name welcome-mailer \
--event-source-arn "$STREAM_ARN" --starting-position TRIM_HORIZON \
--batch-size 100 --maximum-batching-window-in-seconds 5 \
--maximum-retry-attempts 3 --maximum-record-age-in-seconds 3600 \
--bisect-batch-on-function-error \
--function-response-types ReportBatchItemFailures \
--destination-config '{"OnFailure":{"Destination":"arn:aws:sqs:eu-west-1:111122223333:mailer-failures"}}' \
--filter-criteria '{"Filters":[{"Pattern":"{\"eventName\":[\"INSERT\"]}"}]}'
def handler(event, context): # send_welcome() is the app's own mail code
failures = []
for record in event["Records"]: # only INSERT records arrive, thanks to the filter
try:
send_welcome(record["dynamodb"]["NewImage"]["email"]["S"])
except Exception:
failures.append({"itemIdentifier": record["dynamodb"]["SequenceNumber"]})
break # stop at the first failure: Lambda retries from there
return {"batchItemFailures": failures}
- Make handlers idempotent: event source mappings deliver at least once, and a retried batch replays records the function has already seen. A growing
IteratorAgemeans the consumer is falling behind or stuck on a failing batch. ESM concurrency and scaling shared with Kinesis and SQS sources:notes/lambda-guide.md.
Traps
- Trap: "Stream records are kept for 7 days." → Reality: 24 hours; use Kinesis Data Streams for DynamoDB for longer retention or more consumers.
- Trap: "Enabling a stream replays the existing items." → Reality: only later changes are captured; backfill another way (for example from an export).
- Trap: "Switch the stream from
KEYS_ONLYtoNEW_IMAGEin place." → Reality: disable it and enable a new stream. - Trap: "A bad record is skipped automatically." → Reality: by default it is retried until it expires and blocks its shard.
- Trap: "Attach five functions to the same stream." → Reality: more than two readers per shard throttles; fan out from one consumer or use Kinesis Data Streams or EventBridge Pipes.
Practice questions
-
A Lambda function processes a table's DynamoDB stream. One malformed record makes every invocation fail; the function's
IteratorAgekeeps growing and newer changes aren't processed. The team wants good records to keep flowing and the bad record kept for later analysis, with the LEAST effort. What should the developer configure on the event source mapping?- A. Raise the batch size to 10,000 so that the bad record is processed together with more good ones.
- B. Change the starting position to
LATESTand recreate the mapping. - C. Increase the function timeout to 15 minutes.
- D. Enable
BisectBatchOnFunctionError, setMaximumRetryAttemptsto a small number, and add an SQS on-failure destination.
-
A team wants a Lambda function to send a welcome email only when a new user item is created; the email address is an attribute of the item. The design must use the fewest function invocations and make no extra reads from the table. Which TWO steps meet these requirements? (Select TWO.)
- A. Enable the table's stream with the
NEW_IMAGEview type. - B. Enable the table's stream with the
KEYS_ONLYview type and callGetItemin the function. - C. Add a filter on
eventNameequal toINSERTto the event source mapping. - D. Configure an
OnSuccessdestination so that the mapping invokes the function asynchronously. - E. Set
ParallelizationFactorto 10 so that each record is processed once.
- A. Enable the table's stream with the
Answers
- D. Bisecting isolates the failing record, a small retry limit stops it from blocking the shard, and the on-failure destination keeps the batch details for analysis. A keeps failing a bigger batch; B skips unprocessed history and doesn't handle the next bad record; C can't fix a data error.
- A, C.
NEW_IMAGEputs the email address into the record, and theINSERTfilter limits invocations to new items. B adds a read per record; D — stream mappings support only on-failure destinations and always invoke synchronously; E adds concurrency, not filtering.
12. Time to Live
Concept
- Time to Live (TTL) deletes items automatically once a per-item timestamp has passed. Enable it per table and name the attribute; each item that should expire carries that attribute as a Number holding Unix epoch seconds.
- Deletion is a background process: expired items disappear typically within a few days of their timestamp, without consuming write capacity and with no charge for the delete.
- Reads don't skip items that have expired but are not deleted yet: add a
FilterExpressionon the TTL attribute (or check it in code) wherever stale sessions or tokens must not be served. An expired item can still be updated — moving its timestamp forward, or removing it, keeps the item. - A TTL delete acts like any other delete: the item's LSI and GSI entries go too, and the stream gets a
REMOVErecord marked as a service deletion, so Streams + Lambda can archive or react to expirations. - Typical uses: sessions, tokens, carts, idempotency keys, logs, data with a retention rule.
Exam facts
- ⚠️ Changed: expired items are deleted typically within a few days (no fixed deadline). Older material may say within 48 hours.
- The attribute must be type
Nin epoch seconds; strings such as ISO dates and other types are ignored (the item never expires), and an epoch-milliseconds value lies thousands of years ahead, so in practice it never expires either. - Items whose timestamp is more than 5 years in the past are not considered for deletion. There is no minimum TTL duration.
- Stream record of a TTL delete:
userIdentity.type="Service"anduserIdentity.principalId="dynamodb.amazonaws.com"(on global tables, only in the Region where the item expired). UpdateTimeToLiveallows one enable or disable per table per hour, and the change can take up to an hour; another call within that hour →ValidationException.- ⚠️ Changed: TTL deletes are free in the Region where the item expires, but on global tables (MREC) each replicated delete consumes a replicated write unit in every other replica Region (and can throttle provisioned replicas). Older material may say TTL deletes never cost anything. MRSC global tables don't support TTL.
- The exam comparison: a scheduled
Scan+DeleteItemjob costs RCU and WCU and adds code to run; TTL is the LEAST-overhead answer whenever deletion may lag.
Developer angle
aws dynamodb update-time-to-live --table-name Sessions \
--time-to-live-specification "Enabled=true,AttributeName=expiresAt"
aws dynamodb describe-time-to-live --table-name Sessions
# archive expirations only: invoke the function for TTL deletes, never for user deletes
# (STREAM_ARN = the table's LatestStreamArn, as in §11)
aws lambda create-event-source-mapping --function-name archive-expired \
--event-source-arn "$STREAM_ARN" --starting-position TRIM_HORIZON \
--filter-criteria '{"Filters":[{"Pattern":"{\"userIdentity\":{\"type\":[\"Service\"],\"principalId\":[\"dynamodb.amazonaws.com\"]}}"}]}'
- In code, write the expiry in seconds (Python
int(time.time()) + 1800; JavaScriptMath.floor(Date.now() / 1000) + 1800) and hide expired-but-present items withFilterExpression=Attr("expiresAt").gt(now). - Use the
OLD_IMAGEorNEW_AND_OLD_IMAGESview type when archiving, so the record still holds the item's data. - When extending a session that may already have expired, add a condition such as
attribute_exists(sessionId)so the update doesn't recreate an item that TTL has just removed.
Traps
- Trap: "TTL deletes the item at the exact second." → Reality: typically within a few days; filter at read time.
- Trap: "Store the expiry as an ISO 8601 string." → Reality: TTL needs a Number in epoch seconds; other formats are ignored.
- Trap: "Use
Date.now()(milliseconds) as the TTL value." → Reality: divide by 1,000; milliseconds never expire in practice. - Trap: "TTL deletes consume WCU." → Reality: free in the source Region; only replicated deletes on global tables cost.
- Trap: "TTL deletions can't be told apart from user deletes." → Reality: their stream records carry the DynamoDB service identity.
Practice questions
-
Session items store
expiresAtas an ISO 8601 string, and TTL is enabled onexpiresAt. A week later, sessions that expired days ago are still in the table. What is the most likely cause?- A. TTL deletes only items whose expiration attribute is a Number containing Unix epoch seconds.
- B. TTL works only on tables that use on-demand capacity mode.
- C. TTL requires DynamoDB Streams to be enabled on the table.
- D. TTL deletions start 30 days after the expiration time.
-
A compliance rule requires that order items be removed 400 days after creation and archived to Amazon S3 when they are removed. The solution must not pay for the deletes and must need minimal custom code. Which TWO actions should the developer take? (Select TWO.)
- A. Schedule an EventBridge rule that runs a Lambda function to
ScanandDeleteItemexpired orders every night. - B. Enable point-in-time recovery so that expired orders can be restored later.
- C. Enable TTL on an attribute that stores each order's expiry time in epoch seconds.
- D. Enable a stream with the
OLD_IMAGEview type and a Lambda mapping, filtered on the DynamoDB service identity, that writes the items to S3. - E. Enable a stream with the
KEYS_ONLYview type and copy the keys to S3.
- A. Schedule an EventBridge rule that runs a Lambda function to
Answers
- A. TTL ignores items whose attribute isn't a Number of epoch seconds, so string timestamps never expire. B, C and D are false: TTL works in both capacity modes, needs no stream, and typically deletes within a few days.
- C, D. TTL deletes at no cost, and the stream's old image plus a filter on the service identity delivers exactly the expired items to an archiving function. A pays for reads and writes and adds a scheduler; B keeps restore points, not an archive; E loses the item data.
13. Security and fine grained access control
Concept
- Every DynamoDB request is a signed AWS API call authorized by IAM — identity-based policies on users and roles and, since March 2024, resource-based policies on tables, indexes and streams. There are no database users or passwords.
- Fine-grained access control (FGAC) narrows a policy to particular items and attributes with DynamoDB condition keys. It is the standard way to let end users of a web or mobile app call DynamoDB directly:
- The user signs in (Cognito user pool, Login with Amazon, Google, Facebook or another OIDC provider).
- A Cognito identity pool (or web identity federation through STS) exchanges the token for temporary AWS credentials of an IAM role (
notes/advanced-identity-guide.md). - The role's policy puts a policy variable for the caller's identity into
dynamodb:LeadingKeys, so each user can touch only items whose partition key equals their own ID.
- Encryption at rest is always on and cannot be turned off: AWS owned key (default, free), AWS managed key
aws/dynamodb, or a customer managed key — switchable later; the table's key also protects its indexes, streams and backups. Endpoints use HTTPS/TLS in transit (KMS details:notes/security-encryption-guide.md). - Private access: a gateway VPC endpoint (free, route-table based, usable only from inside the VPC) or an interface endpoint via AWS PrivateLink (since March 2024; billed; reachable from on-premises and from peered VPCs). Design details:
notes/vpc-guide.md. - ABAC (since November 2024) grants access by tags with
aws:ResourceTag/<key>,aws:RequestTag/<key>andaws:TagKeys; indexes inherit their table's tags.
Exam facts
- FGAC condition keys:
dynamodb:LeadingKeys— the partition key values of the request; use it withForAllValues:StringEquals(the name is plural even for single-item calls).dynamodb:FirstPartitionKeyValuesis equivalent;…Second/Third/FourthPartitionKeyValuescover multi-attribute keys.dynamodb:Attributes— the top-level attributes named in the request; list the key attributes too, or DynamoDB can't use them.dynamodb:Select— requireSPECIFIC_ATTRIBUTES, otherwise a read without a projection returns every attribute.dynamodb:ReturnValues— limit it toNONE,UPDATED_OLD,UPDATED_NEWso that writes can't return hidden attributes.- Also
dynamodb:ReturnConsumedCapacity,dynamodb:EnclosingOperation(transactions, PartiQL) anddynamodb:FullTableScan(PartiQL).
- Policy variables:
${cognito-identity.amazonaws.com:sub},${www.amazon.com:user_id},${graph.facebook.com:id},${accounts.google.com:sub}; the policy must declare"Version": "2012-10-17". - In an item-level policy, never grant
dynamodb:Scan(it ignores leading keys and returns everyone's items); when hiding attributes, don't grantPutItem,DeleteItemorBatchWriteItem, which replace whole items. AWS recommends allow-list FGAC policies over deny-based ones, and FGAC conditions must not be applied to the principals that replicate global tables. - ⚠️ Changed: tables, indexes and streams accept resource-based policies (at most 20 KB; a table's policy also covers its indexes). They grant cross-account access without assuming a role — the caller still needs an identity-based allow in its own account and passes the table ARN as
TableName— and work with IAM Access Analyzer and Block Public Access. Attaching one needsdynamodb:PutResourcePolicy(plusdynamodb:CreateTableat creation). Older material may say cross-account access always requires assuming a role in the table's account. - ⚠️ Changed: besides gateway endpoints, DynamoDB supports interface endpoints (PrivateLink), and DynamoDB Streams has had PrivateLink since March 2025. Clients call the endpoint-specific DNS name (
https://vpce-….dynamodb.<region>.vpce.amazonaws.com); private DNS isn't supported. Older material may say DynamoDB supports only gateway endpoints, so on-premises clients need a proxy in the VPC. - Restrict access to a VPC endpoint with
aws:SourceVpce(one endpoint) oraws:SourceVpc(one VPC) in the table's resource policy, or with an endpoint policy. - Paths around item-level controls: DAX (its cluster role reads on the callers' behalf, §10) and Contributor Insights (key values visible to anyone with CloudWatch access).
- ⚠️ Changed: the AWS managed policy
AmazonDynamoDBFullAccesswas deprecated on 2025-04-25 and replaced by the narrowerAmazonDynamoDBFullAccess_v2. Older material may say attachAmazonDynamoDBFullAccess. - Encryption key switches: up to 4 per 24 hours per table, plus 1 more after six hours without a change; switching to the AWS owned key is always allowed. If a customer managed key is disabled or scheduled for deletion, the table becomes inaccessible; restore access within 7 days or DynamoDB archives the table (keeping a backup).
Developer angle
Item-level and attribute-level access for users of a Cognito identity pool (drop the Attributes, Select and ReturnValues conditions when users may see whole items):
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "OwnNotesSelectedAttributes",
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:Query", "dynamodb:UpdateItem"],
"Resource": "arn:aws:dynamodb:eu-west-1:111122223333:table/UserNotes",
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": ["${cognito-identity.amazonaws.com:sub}"],
"dynamodb:Attributes": ["userId", "noteId", "title", "body", "updatedAt"]
},
"StringEqualsIfExists": {
"dynamodb:Select": "SPECIFIC_ATTRIBUTES",
"dynamodb:ReturnValues": ["NONE", "UPDATED_OLD", "UPDATED_NEW"]
}
}
}]
}
- Cross-account reads: a resource-based policy on the table with
"Principal": {"AWS": "arn:aws:iam::444455556666:role/ReportingFunctionRole"}allowingdynamodb:GetItemanddynamodb:Queryon the table ARN, attached withaws dynamodb put-resource-policy --resource-arn <table ARN> --policy file://orders-cross-account.json; the reporting role also needs the same actions in its own identity policy. - Switch the table key:
aws dynamodb update-table --table-name Orders --sse-specification Enabled=true,SSEType=KMS,KMSMasterKeyId=alias/orders-cmk. - A Lambda function in private subnets without NAT reaches DynamoDB most cheaply through a gateway endpoint; add an interface endpoint only when on-premises or peered-VPC clients need private access.
Traps
- Trap: "Ship an IAM user's access keys in the mobile app and filter by user ID in code." → Reality: use identity-pool credentials with
dynamodb:LeadingKeys. - Trap: "Add
dynamodb:Scanto the per-user policy for convenience." → Reality: a scan isn't limited by leading keys and leaks other users' items. - Trap: "
dynamodb:Attributesalone hides sensitive attributes." → Reality: also requiredynamodb:Select=SPECIFIC_ATTRIBUTESand restrictdynamodb:ReturnValues. - Trap: "Turn on encryption at rest for compliance." → Reality: it is always on; the decision is which key.
- Trap: "On-premises servers can use the gateway endpoint over Direct Connect." → Reality: gateway endpoints serve only traffic routed by the VPC's route tables; use an interface endpoint.
- Trap: "Cross-account access always needs
sts:AssumeRole." → Reality: a resource-based policy on the table, index or stream can grant it directly.
Practice questions
-
A mobile app lets signed-in users read and write only their own items in the
UserNotestable, whose partition key holds the user's Amazon Cognito identity ID. The app calls DynamoDB directly. Which solution is MOST secure?- A. Embed the access keys of an IAM user with DynamoDB permissions in the app and filter items by user ID in the app code.
- B. Put the table behind an API Gateway API key that every app installation sends.
- C. Give the Cognito identity pool's role a policy that allows the item actions when
dynamodb:LeadingKeysequals${cognito-identity.amazonaws.com:sub}. - D. Grant the identity pool's role
dynamodb:Scanwith adynamodb:Attributescondition on the user ID attribute.
-
A Lambda function runs in private subnets with no NAT gateway and must read a DynamoDB table in the same Region. A security policy requires that the table accept requests only when they arrive through the VPC endpoint the team creates. Which TWO actions meet these requirements? (Select TWO.)
- A. Create a gateway VPC endpoint for DynamoDB and associate it with the subnets' route tables.
- B. Attach a resource-based policy to the table that denies requests whose
aws:SourceVpceis not the endpoint's ID. - C. Add a NAT gateway so that the function can reach the public DynamoDB endpoint.
- D. Put a DAX cluster in the subnets and route all reads through it.
- E. Add a
dynamodb:LeadingKeyscondition to the function's execution role.
Answers
- C. Temporary identity-pool credentials plus a
LeadingKeyscondition bound to the caller's identity ID enforce per-user item access in IAM. A ships long-term secrets and trusts the client; B identifies app installations, not users; D —Scanignores leading keys, so every user could read all notes. - A, B. The gateway endpoint gives a private route without NAT, and the table's resource-based policy enforces that endpoint with
aws:SourceVpce. C adds a public path and cost; D caches reads but doesn't restrict the table; E scopes items, not network paths.
14. Serverless patterns and table operations
Concept
- Session store: session state must be readable from every instance or function, expire on its own and survive the loss of any one server.
| Option for shared session state | Fit |
|---|---|
| DynamoDB + TTL | Serverless key-value store reachable from every instance and Lambda function; TTL ages sessions out (§12); nothing to size in on-demand mode |
| ElastiCache (Valkey, Redis OSS) | In-memory, so lower latency, but a cluster you size and operate (notes/rds-guide.md §10) |
| Amazon S3 | Per-request latency and cost don't suit tiny objects rewritten on every request |
| Amazon EFS | A file system every client must mount — clumsy for many small key lookups |
| EBS volume or instance store | Attached to one instance, so other instances can't see the session |
- Write sharding: when one partition key value takes too many writes (a live event, today's date, a popular device), spread it over N key values by appending a suffix:
- Random suffix (
event#2026-10-12#1…#N): the best spread, but reading one logical item means querying all N keys and merging. - Calculated suffix (
hash(ticketId) % N): just as even, and a reader who knows the input attribute can compute the exact key for aGetItem; totals still fan out over N.
- Random suffix (
- Large objects: keep blobs above the 400 KB item limit (images, documents, video) in Amazon S3 and store the bucket, key and searchable metadata in DynamoDB. No transaction spans S3 and DynamoDB, so handle failures and clean up orphaned objects. For medium-size data, compress it into a Binary attribute or split it across items of one item collection.
- Indexing S3 metadata: an S3 event notification invokes Lambda, which writes each object's metadata (owner, size, upload date, tags) to DynamoDB; the app then queries by owner or date range instead of listing buckets (
notes/s3-guide.md). - Table operations a developer must know:
- Emptying a table:
DeleteTable+CreateTabletakes minutes and costs nothing per item; aScan+DeleteItemloop reads and deletes every item, paying RCU and WCU for each. - Copying a table: export to S3 + import from S3 (managed, no table capacity used); restore an AWS Backup or DynamoDB backup into a new table; an AWS Glue job; or your own
Scan+BatchWriteItemprogram. From other databases → AWS DMS. - Backups: on-demand backups (DynamoDB or AWS Backup, kept until deleted) and point-in-time recovery (continuous, per-second restore points). Neither affects table performance, and every restore creates a new table.
- Deletion protection (
DeletionProtectionEnabled) blocksDeleteTablefor everyone until it is switched off. - Global tables (version 2019.11.21): multi-Region, multi-active replicas of one table, one replica per Region, 99.999 % availability SLA.
- DynamoDB Local: downloadable DynamoDB (JAR, Docker image, Maven dependency, or inside AWS CloudShell) for offline development and tests.
- Emptying a table:
Exam facts
- ⚠️ Changed: the PITR recovery period is configurable from 1 to 35 days (
RecoveryPeriodInDays, since January 2025; the price doesn't depend on it). Older material may say PITR always covers 35 days. Restore to any second betweenEarliestRestorableDateTimeandLatestRestorableDateTime(about 5 minutes ago). Deleting a PITR-enabled table keeps a free system backup for 35 days. Restored tables don't keep their tags. - ⚠️ Changed: global tables have two consistency modes. MREC (default): asynchronous replication, typically within a second; conflicts resolved by last writer wins; strongly consistent reads see only the local Region's latest write. MRSC (since June 2025): exactly 3 Regions (3 replicas, or 2 replicas + 1 witness), synchronous replication, RPO 0, strongly consistent reads return the latest version in any Region, conflicting concurrent writes fail with a retryable
ReplicatedWriteConflictException; no TTL, LSIs or transactions; built only from an empty table, and the mode can't be changed later. Older material may say global tables are always eventually consistent between Regions. - MREC replicas replicate through DynamoDB Streams (always on for them) and consume write capacity for replicated writes; capacity mode, write capacity, GSIs, TTL and warm throughput are synchronized, while PITR, deletion protection, tags and resource policies are set per replica.
- ⚠️ Changed: export to S3 (needs PITR; consumes no RCU; DynamoDB JSON or Amazon Ion; full or incremental with 15-minute to 24-hour windows; filtered exports since October 2026; the bucket may be in another account or Region) and import from S3 (CSV, DynamoDB JSON or Ion, optionally GZIP or ZSTD; always creates a new table; consumes no WCU; GSIs allowed, LSIs not) are the managed way to copy a table. Older material may say copy a table with AWS Backup, AWS Glue or a
Scan-and-write script. - ⚠️ Changed: tables have deletion protection (since March 2023). Older material may say only an IAM deny on
dynamodb:DeleteTableprevents an accidental delete. - DynamoDB Local: default port 8000; point tools at it with
--endpoint-url http://localhost:8000; any credentials and Region work; provisioned throughput is ignored (load tests there prove nothing about capacity), parallel scans, PITR and tagging aren't supported,TransactionConflictExceptionnever occurs, and table names are case-insensitive.
Developer angle
Calculated-suffix sharding for gate check-ins at a sold-out event:
import zlib
import boto3
SHARDS = 10
checkins = boto3.resource("dynamodb").Table("CheckIns") # key: pk (partition) + ticketId (sort)
def shard_key(event_id, ticket_id):
return f"{event_id}#{zlib.crc32(ticket_id.encode()) % SHARDS}" # same ticket, same shard
def check_in(event_id, ticket_id, gate):
checkins.put_item(Item={"pk": shard_key(event_id, ticket_id), "ticketId": ticket_id, "gate": gate})
- A reader that knows the ticket ID rebuilds
shard_key()and callsGetItem. Totals fan out:Queryeach of the N keys withSelect="COUNT", followLastEvaluatedKey, and add the counts — or let a stream consumer keep one total item up to date.
aws dynamodb update-table --table-name Orders --deletion-protection-enabled
aws dynamodb update-continuous-backups --table-name Orders \
--point-in-time-recovery-specification PointInTimeRecoveryEnabled=true,RecoveryPeriodInDays=14
aws dynamodb restore-table-to-point-in-time --source-table-name Orders \
--target-table-name Orders-restored --use-latest-restorable-time
aws dynamodb export-table-to-point-in-time \
--table-arn arn:aws:dynamodb:eu-west-1:111122223333:table/Orders \
--s3-bucket orders-exports-444455556666 --s3-bucket-owner 444455556666 \
--export-format DYNAMODB_JSON
aws dynamodb update-table --table-name Orders \
--replica-updates '[{"Create": {"RegionName": "us-east-1"}}]'
- Local tests:
docker run -p 8000:8000 amazon/dynamodb-local, then read the endpoint from configuration (an environment variable) so that tests hit DynamoDB Local and deployments hit the real service without code changes.
Traps
- Trap: "Delete every item with
Scan+DeleteItem." → Reality: drop and recreate the table; item by item is slow and pays for every read and write. - Trap: "A PITR restore rolls the existing table back." → Reality: it always creates a new table.
- Trap: "Import from S3 into the existing table." → Reality: import always creates a new table.
- Trap: "With a random suffix you can still
GetItemone logical item." → Reality: only a calculated suffix lets the reader derive the key. - Trap: "Global tables are strongly consistent everywhere by default." → Reality: MREC (the default) is eventually consistent with last writer wins; MRSC must be chosen at creation.
Practice questions
-
A ticketing app writes one item per gate check-in, with the event ID as the partition key and the check-in timestamp as the sort key. During a sold-out concert, writes are throttled with
TableWriteKeyRangeThroughputExceededalthough the table's provisioned WCU is far from used. A dashboard reads per-event totals every few seconds. Which design removes the throttling?- A. Create a global secondary index with the event ID as its partition key.
- B. Append a suffix computed from a hash of the ticket ID to the partition key, and add up the N shards for the dashboard.
- C. Put a DAX cluster in front of the table.
- D. Raise the table's provisioned WCU.
-
Every week, testers need a fresh copy of a 500 GB production table in a separate test account. The copy must not consume the production table's read capacity, and the team wants the LEAST operational overhead. Which TWO steps meet these requirements? (Select TWO.)
- A. Run a parallel
Scanfrom an EC2 instance and write the items into the test account withBatchWriteItem. - B. Enable DynamoDB Streams and a Lambda function that replays every change into the test account.
- C. Give the testers DAX access to the production table instead of a copy.
- D. With PITR enabled, export the table to an S3 bucket owned by the test account.
- E. In the test account, import the exported data from S3 into a new table.
- A. Run a parallel
Answers
- B. The reason says one key range is hot: sharding spreads the event's writes over N partition key values and therefore over more partitions, and the dashboard sums the N shards. A — the GSI has the same hot key and back-pressures the table; C accelerates reads only; D adds table capacity, but a single key value stays capped by the per-partition limit.
- D, E. Export to S3 reads from PITR data, consumes no RCU and can target another account's bucket; import from S3 builds a new table without consuming write capacity. A and B consume production capacity and add code to run; C produces no copy and exposes production data.
Quick decision rules
Keys, capacity and throttling (§1–§4)
- If you see "items of one customer or device in time order", think partition key = the entity ID, sort key = a timestamp or a composite string such as
ORDER#2026-10-12. - If you see a partition key with few values (status, date, country, one live event) under heavy writes, think hot partition → better key or write sharding.
- If you see a capacity calculation, think per second first, round each item up (1 KB writes, 4 KB reads), halve for eventual, double for transactional, round the result up.
- If you see unknown, spiky or brand-new traffic with LEAST operational overhead, think on-demand; steady, predictable traffic at the lowest cost, think provisioned + auto scaling (plus reserved capacity).
- If you see a known launch or sale far above the previous peak, think pre-warm with
WarmThroughput; a need to cap on-demand spend, thinkOnDemandThroughput. - If you see a table whose bill is mostly storage of rarely read data, think the Standard-IA table class.
- If you see throttling while the table has spare capacity, think hot partition or GSI back-pressure: read
ThrottlingReasons, then Contributor Insights in throttled-keys mode. - If you see an
Index…throttling reason, think raise the GSI's capacity or fix its key — not the table's. - If you see items missing after
BatchWriteItemorBatchGetItem, thinkUnprocessedItems/UnprocessedKeysresubmitted with exponential backoff.
Reads, indexes and writes (§5–§9)
- If you see "find one item by its key", think
GetItem, neverScan. - If you see a
FilterExpressionoffered as a way to save capacity, think it saves nothing: change the key condition or add an index. - If you see "fewer items than
Limit" or "only part of the results", thinkLimitcounts evaluated items and pages stop at 1 MB → loop onLastEvaluatedKey. - If you see a new query pattern on an existing table, think GSI; an alternate sort order with strongly consistent reads at design time, think LSI.
- If you see a strongly consistent read requested from a GSI, think
ValidationException— read the table or an LSI. - If you see lost updates from concurrent edits, think optimistic locking: version attribute + condition expression.
- If you see "insert only if it doesn't exist", think
attribute_not_exists(<partition key>); to see the existing item without another read, thinkReturnValuesOnConditionCheckFailure=ALL_OLD. - If you see approximate counters (views, likes), think atomic counter; for money or stock, think conditional update or transaction.
- If you see all-or-nothing changes across items or tables, think
TransactWriteItems(100 actions, 4 MB, double capacity) with aClientRequestToken; bulk loading, thinkBatchWriteItemorbatch_writer(). - If you see SQL-style access, think PartiQL with
dynamodb:PartiQL*actions, anddynamodb:FullTableScanto block scans.
Caching, streams, TTL, security and operations (§10–§14)
- If you see microsecond reads with minimal code changes, think DAX (inside a VPC, DAX client); for aggregates or multi-source caching, think ElastiCache.
- If you see stale
Queryresults behind DAX, think the query cache TTL (query-ttl-millis); writes refresh only the item cache. - If you see "react to item changes" or "email on every new item", think Streams + Lambda event source mapping,
NEW_IMAGEand aneventNamefilter. - If you see a poison record blocking a stream, think
BisectBatchOnFunctionError,MaximumRetryAttempts,ReportBatchItemFailuresand an on-failure destination. - If you see "delete old items automatically at no cost", think TTL on an epoch-seconds Number attribute; to archive them, a stream with
OLD_IMAGEand a Lambda filter on the DynamoDB service identity. - If you see per-user item access from a mobile app, think Cognito identity pool role +
dynamodb:LeadingKeys. - If you see cross-account table access without assuming a role, think a resource-based policy on the table.
- If you see a private path from a VPC without NAT, think gateway endpoint; from on-premises, think interface endpoint.
- If you see "empty the table quickly and cheaply", think delete and recreate it; "copy a table without using its read capacity", think export to S3 (PITR) + import from S3.
- If you see recovery from accidental writes, think a PITR restore into a new table.
- If you see multi-Region active-active with RPO 0, think an MRSC global table; for the lowest-latency local writes, think MREC (default).
- If you see local or offline tests, think DynamoDB Local on port 8000 with
--endpoint-url.
Changed vs older material
Learn the Current column; older practice material may still show the Older material may say value. The four DynamoDB rows already in notes/current-facts.md (switching, pricing, warm throughput, on-demand initial throughput) were re-checked on 2026-10-12; the other rows join it once you confirm them.
| Topic | Older material may say | Current (verified 2026-10-12) | Source |
|---|---|---|---|
| §1 · Partitions and capacity split | Partitions = ceil(max(RCU/3,000 + WCU/1,000, size/10 GB)), capacity divided equally and fixed | No published formula; partitions managed automatically; adaptive capacity (instant, free) and hot-item isolation; per partition 3,000 RCU / 1,000 WCU and 10 GB | AWS docs |
| §3 · Capacity mode switching | One capacity-mode change per table per day | Provisioned → on-demand up to 4 times per rolling 24 h (since August 2025); on-demand → provisioned any time | AWS docs |
| §3 · On-demand price and positioning | About 2.5× provisioned; only for unknown or spiky workloads | Price cut 50 % effective 2024-11-01 (global-table replicated writes −67 % on-demand, −33 % provisioned); default and recommended for most workloads; us-east-1 Standard $0.625 per million WRU, $0.125 per million RRU | What's New |
| §3 · On-demand throttling | Unlimited capacity, never throttles | Per-table quota 40,000 RRU / 40,000 WRU (adjustable); optional OnDemandThroughput cap (May 2024); more than 2× the previous peak within 30 min may throttle; partition limits apply |
AWS docs |
| §3 · Pre-warming | Provision high capacity temporarily, then switch to on-demand | Warm throughput visible for free on every table and GSI (2024-11-13); pre-warm with WarmThroughput (charged, cannot be decreased) in either mode |
AWS docs |
| §4 · Throttling diagnostics | Only ProvisionedThroughputExceededException, no detail |
ThrottlingReasons (reason + resource ARN) on ProvisionedThroughputExceededException, ThrottlingException, RequestLimitExceeded; 16 cause-specific metrics; Contributor Insights throttled-keys mode (August 2025) |
AWS docs |
§5 · AWS CLI --page-size default |
1,000 items for every command | Set per service (S3 1,000); each DynamoDB call bounded by 1 MB; --page-size changes only the number of calls |
AWS docs |
| §6 · GSI key schema | One partition-key attribute + one optional sort-key attribute; concatenate values into synthetic keys | Multi-attribute keys: up to 4 partition-key and up to 4 sort-key attributes | AWS docs |
| §6 · GSI throughput settings | Every GSI must be provisioned with its own RCU and WCU | GSIs inherit the table's capacity mode: own RCU/WCU only on provisioned tables (optional OnDemandThroughput cap on on-demand tables) |
AWS docs |
| §8 · Transaction size | 25 items per transaction | 100 actions on 100 distinct items, 4 MB aggregate | AWS docs |
| §10 · DAX cluster size | Up to 10 nodes per cluster | Developer Guide: up to 11 nodes (1 primary + up to 10 read replicas); ⚠️ unverified — the CreateCluster API reference still says ReplicationFactor ≤ 10 |
AWS docs |
| §11 · Stream ESM on-failure destinations | SQS queue or SNS topic only | SQS queue, SNS topic or S3 bucket (S3 also receives the batch payload) | AWS docs |
| §11 · KCL consumers of DynamoDB Streams | Kinesis Adapter with KCL 1.x and SDK for Java 1.x | Kinesis Adapter with KCL 3.5.x+ and SDK for Java 2.x; KCL 1.x end of support 2026-01-30 | AWS docs |
| §12 · TTL deletion delay | Within 48 hours of expiry | Typically within a few days of expiry; expired items stay readable until deleted | AWS docs |
| §12 · TTL delete cost on global tables | TTL deletes never cost anything | Free in the Region where the item expires; replicated deletes consume replicated write units in the other replica Regions | AWS docs |
| §13 · Cross-account access | Requires assuming a role in the table's account | Resource-based policies on tables, indexes and streams (March 2024), max 20 KB, with Block Public Access | AWS docs |
| §13 · Private connectivity | Gateway endpoints only | Gateway endpoints (free) or interface endpoints via PrivateLink (March 2024; Streams March 2025), reachable from on-premises | AWS docs |
| §13 · Full-access managed policy | Attach AmazonDynamoDBFullAccess |
Deprecated 2025-04-25; replaced by the narrower AmazonDynamoDBFullAccess_v2 |
Document history |
| §14 · PITR recovery period | Fixed 35 days | Configurable 1–35 days (RecoveryPeriodInDays, January 2025); same price whatever the period |
AWS docs |
| §14 · Global table consistency | Always eventually consistent between Regions (last writer wins) | MREC by default; MRSC option (June 2025): 3 Regions, RPO 0, strongly consistent reads in any Region, no TTL, LSIs or transactions | AWS docs |
| §14 · Protecting a table from deletion | Only an IAM deny on dynamodb:DeleteTable |
Deletion protection (DeletionProtectionEnabled, March 2023) |
What's New |
| §14 · Copying a table | AWS Backup, AWS Glue or a Scan + write script |
Also export to S3 (needs PITR, no RCU, filtered exports since October 2026) and import from S3 into a new table (no WCU) | AWS docs |
Re-checked and unchanged on 2026-10-12: new on-demand tables sustain up to 4,000 writes/s and 12,000 reads/s and instantly absorb up to double their previous peak (AWS docs).