16. Cheat sheet
DynamoDB · 16. Cheat sheetEverything worth having to hand, in one place.
The six facts
Section titled “The six facts”1. THE PARTITION KEY decides the partition, the machine, the throughput ceiling and the hot-key risk. It is permanent.
2. QUERY reads ONE partition by exact PK, with an optional SORT KEY range. SCAN reads everything and belongs in batch jobs only.
3. 3,000 RCU / 1,000 WCU / 10 GB per PARTITION — whatever the table's capacity is.
4. Writes round up to 1 KB, reads to 4 KB. Eventually consistent reads cost HALF. Transactions cost DOUBLE.
5. A GSI is a SECOND COPY of your data. Every table write writes every matching index — and a throttled index backpressures the table.
6. ALWAYS paginate on LastEvaluatedKey. The 1 MB cut is silent.Limits
Section titled “Limits” item size 400 KB query/scan page 1 MB (filters apply AFTER) partition key value 2048 bytes sort key value 1024 bytes item collection unlimited — 10 GB if an LSI exists BatchGetItem 100 items BatchWriteItem 25 items TransactWrite/GetItems 100 items, 4 MB GSIs per table 20 (soft) LSIs per table 5, at creation only per partition 3,000 RCU / 1,000 WCU / 10 GB stream retention 24 hours PITR window 35 daysCapacity arithmetic
Section titled “Capacity arithmetic” WRITE ceil(bytes / 1 KB) WCU READ ceil(bytes / 4 KB) RCU strongly consistent half that, eventually consistent TXN 2× the above
1 KB item, 1,000 writes/s → 1,000 WCU 3 KB item, 1,000 reads/s → 1,000 RCU strong 500 RCU eventual 0.2 KB item → still 1 WCUKey design
Section titled “Key design” GOOD PK userId, deviceId, tenant#entity — high cardinality, even access BAD PK status, country, "ORDERS", today's date
SORT KEY ORDER#2026-03-11#A917 ISO dates · # separators · zero-padded numbers · most general segment first
HOT KEY FIX PK = "PRODUCT#123#" + shard(0..N) fan out reads, or derive the shard from something the reader knowsExpressions
Section titled “Expressions” KEY CONDITION PK = :pk AND SK BETWEEN :a AND :b PK = :pk AND begins_with(SK, :prefix)
CONDITION attribute_not_exists(PK) attribute_exists(PK) version = :v balance >= :amt attribute_type(x, :type) contains(tags, :t) size(list) < :n
UPDATE SET a = :v, b = if_not_exists(b, :d) SET list = list_append(list, :items) REMOVE oldAttr ADD counter :one ← atomic DELETE tagSet :values
NAMES #s = "status" ← alias reserved words VALUES :v = {"S": "..."}aws dynamodb create-table --table-name AppData \ --attribute-definitions AttributeName=PK,AttributeType=S \ AttributeName=SK,AttributeType=S \ --key-schema AttributeName=PK,KeyType=HASH \ AttributeName=SK,KeyType=RANGE \ --billing-mode PAY_PER_REQUEST
aws dynamodb get-item --table-name AppData \ --key '{"PK":{"S":"CUSTOMER#42"},"SK":{"S":"PROFILE"}}' \ --return-consumed-capacity TOTAL
aws dynamodb query --table-name AppData \ --key-condition-expression 'PK = :pk AND begins_with(SK, :p)' \ --expression-attribute-values '{":pk":{"S":"CUSTOMER#42"},":p":{"S":"ORDER#"}}' \ --no-scan-index-forward
aws dynamodb update-item --table-name AppData \ --key '{"PK":{"S":"CUSTOMER#42"},"SK":{"S":"PROFILE"}}' \ --update-expression 'SET #n = :n ADD logins :one' \ --expression-attribute-names '{"#n":"name"}' \ --expression-attribute-values '{":n":{"S":"Alice"},":one":{"N":"1"}}' \ --condition-expression 'attribute_exists(PK)'
aws dynamodb describe-table --table-name AppDataaws dynamodb update-time-to-live --table-name AppData \ --time-to-live-specification "Enabled=true, AttributeName=expiresAt"aws dynamodb update-continuous-backups --table-name AppData \ --point-in-time-recovery-specification PointInTimeRecoveryEnabled=trueaws dynamodb update-table --table-name AppData --deletion-protection-enabledPagination, correctly
Section titled “Pagination, correctly”kwargs = {"KeyConditionExpression": Key("PK").eq(pk)}while True: resp = table.query(**kwargs) yield from resp["Items"] if "LastEvaluatedKey" not in resp: break kwargs["ExclusiveStartKey"] = resp["LastEvaluatedKey"]Idempotent write
Section titled “Idempotent write”table.put_item( Item={"PK": f"IDEMPOTENCY#{rid}", "SK": "LOCK", "expiresAt": int(time.time()) + 86400}, ConditionExpression="attribute_not_exists(PK)")Uniqueness on a non-key attribute
Section titled “Uniqueness on a non-key attribute”client.transact_write_items(TransactItems=[ {"Put": {"TableName": T, "Item": {"PK": {"S": "EMAIL#a@b.c"}, "SK": {"S": "UNIQUE"}}, "ConditionExpression": "attribute_not_exists(PK)"}}, {"Put": {"TableName": T, "Item": user_item}},])Consistency, gathered
Section titled “Consistency, gathered” table read, default eventually consistent table read, ConsistentRead strongly consistent GSI read ALWAYS eventual LSI read can be strong TransactGetItems serializable snapshot global tables, other region eventual, LAST-WRITER-WINSStreams
Section titled “Streams” view types KEYS_ONLY · NEW_IMAGE · OLD_IMAGE · NEW_AND_OLD_IMAGES events INSERT · MODIFY · REMOVE (incl. TTL) ordering per ITEM only delivery AT LEAST ONCE → consumers idempotent Lambda a failed batch BLOCKS the shard. set BisectBatchOnFunctionError, retry limits and an on-failure destination.SDK configuration
Section titled “SDK configuration”Config(retries={"max_attempts": 5, "mode": "adaptive"}, connect_timeout=1, read_timeout=2, max_pool_connections=50)# create the client ONCE, outside the handlerMetrics and alarms
Section titled “Metrics and alarms” ThrottledRequests / Read|WriteThrottleEvents > 0 ConsumedCapacity ÷ Provisioned > 80% SuccessfulRequestLatency p99 SystemErrors (5xx), UserErrors (4xx) ScannedCount vs ReturnedItemCount ← efficiency per-INDEX consumed capacity ← separately! Contributor Insights ← names hot keysErrors, decoded
Section titled “Errors, decoded” ProvisionedThroughputExceededException → table capacity, hot partition, or GSI. check per-index metrics and Contributor Insights.
ConditionalCheckFailedException → your condition was false. expected outcome. still billed.
TransactionCanceledException → READ CancellationReasons. ConditionalCheckFailed = final. TransactionConflict = retryable.
ValidationException: reserved keyword → alias the attribute name with #
ValidationException: item size exceeded 400 KB → an unbounded attribute. split into items or S3.
ItemCollectionSizeLimitExceededException → an LSI item collection passed 10 GB.
ResourceNotFoundException on a GSI query → index still backfilling, or IAM missing /index/* on the resource ARNRecipes
Section titled “Recipes”# newest N items for one parenttable.query(KeyConditionExpression=Key("PK").eq("USER#42") & Key("SK").begins_with("DOC#"), ScanIndexForward=False, Limit=20)
# a time windowtable.query(KeyConditionExpression=Key("PK").eq("DEVICE#7") & Key("SK").between("READING#2026-09-01", "READING#2026-09-30"))
# optimistic locktable.update_item(Key=key, UpdateExpression="SET #s = :new, version = :next", ConditionExpression="version = :cur", ExpressionAttributeNames={"#s": "status"}, ExpressionAttributeValues={":new": "done", ":next": v+1, ":cur": v})
# atomic counter, no readtable.update_item(Key=key, UpdateExpression="ADD views :one", ExpressionAttributeValues={":one": 1})
# decrement with an invariant, returning the resulttable.update_item(Key=key, UpdateExpression="ADD stock :neg", ConditionExpression="stock >= :one", ExpressionAttributeValues={":neg": -1, ":one": 1}, ReturnValues="ALL_NEW")
# sparse work queue: join the indextable.update_item(Key=key, UpdateExpression="SET GSI1PK = :q, GSI1SK = :t", ExpressionAttributeValues={":q": "REVIEW#pending", ":t": now_iso()})# …and leave ittable.update_item(Key=key, UpdateExpression="REMOVE GSI1PK, GSI1SK")
# TTL: expire in 30 days (SECONDS, as a number){"expiresAt": int(time.time()) + 30*86400}
# batch write with the retry loopreq = {"AppData": [{"PutRequest": {"Item": i}} for i in batch]}while req: resp = client.batch_write_item(RequestItems=req) req = resp.get("UnprocessedItems") or NoneCost review checklist
Section titled “Cost review checklist” □ eventually consistent reads where allowed (−50%) □ TTL instead of DeleteItem (free) □ drop unused GSIs □ KEYS_ONLY / INCLUDE projections □ batch small items together □ shorten attribute names □ export to S3 for analytics □ provisioned + reserved for steady baselinesPre-launch checklist
Section titled “Pre-launch checklist” □ access patterns written down beside the table □ no Scan on any request path □ partition keys: high cardinality, even access □ pagination helper used everywhere □ writes idempotent; retries adaptive □ PITR + deletion protection on □ table name from configuration □ alarms: throttles, latency, per-index capacity □ a restore practised and timedRead alongside: Cassandra for the same ideas self-hosted, PostgreSQL for what you gave up, Redis for the layer in front, and Kafka for where your streams should land.