Orders have partition key customer and sort key placed, a date: each customer's orders sit together, in date order.
market-orders
| customer (partition) | placed (sort) | total |
|---|---|---|
| ada | 2026-10-01 | 45 |
| ada | 2026-10-15 | 310 |
| bo | 2026-10-03 | 95 |
Query
A query reads one partition key value, narrowed on the sort key with =, <, BETWEEN or begins_with:
aws dynamodb query --table-name market-orders --key-condition-expression "customer = :c AND begins_with(placed, :m)" --expression-attribute-values '{":c": {"S": "ada"}, ":m": {"S": "2026-10"}}'A filter drops items after they are read and paid for, and cannot use key attributes; Count against ScannedCount shows the difference:
aws dynamodb query --table-name market-orders --key-condition-expression "customer = :c" --filter-expression "#t > :min" --expression-attribute-names '{"#t": "total"}' --expression-attribute-values '{":c": {"S": "ada"}, ":min": {"N": "100"}}' --query "[Count,ScannedCount]" --output textResults come in sort-key order; aws dynamodb query --table-name market-orders --key-condition-expression "customer = :c" --expression-attribute-values '{":c": {"S": "ada"}}' --no-scan-index-forward --limit 1 is her latest order. A scan reads the whole table, whatever it keeps:
aws dynamodb scan --table-name market-monsters --select COUNTA second key: an index
A global secondary index keeps the items again under another key, here type then price. by-type.json holds [{"Create": {"IndexName": "by-type", "KeySchema": [{"AttributeName": "type", "KeyType": "HASH"}, {"AttributeName": "price", "KeyType": "RANGE"}], "Projection": {"ProjectionType": "ALL"}}}]:
aws dynamodb update-table --table-name market-monsters --attribute-definitions AttributeName=type,AttributeType=S AttributeName=price,AttributeType=N --global-secondary-index-updates file://by-type.jsonIt backfills first (Cannot read from backfilling global secondary index), then answers queries:
aws dynamodb query --table-name market-monsters --index-name by-type --key-condition-expression "#t = :t" --expression-attribute-names '{"#t": "type"}' --expression-attribute-values '{":t": {"S": "fire"}}'Capacity and expiry
A provisioned table serves its units each second and throttles the rest (ProvisionedThroughputExceededException; UnprocessedItems from a batch). On-demand does not. TTL deletes an item some time after the epoch-seconds time in an attribute, so carts clean themselves up: aws dynamodb update-time-to-live --table-name market-carts --time-to-live-specification Enabled=true,AttributeName=expires_at.