Amazon DynamoDB

DynamoDB provides key/value and document storage. Design the partition key, optional sort key and indexes around known access patterns; adding a table does not turn arbitrary joins/scans into efficient queries. Prerequisites: database selection, IAM and data-store selection.

Access patterns and consistency

GetItem addresses a primary key; Query selects a partition-key value and optionally sort-key conditions; Scan visits the dataset and can consume substantial capacity. Filter expressions do not make read capacity free. Avoid hot keys and use conditional writes for optimistic concurrency/idempotency. Pagination and retry/backoff are application responsibilities.

LSIs share the partition key and are defined at table creation; the 10-GB item-collection limit applies to tables with LSIs, not GSIs. GSIs use independent key schemas and have their own consistency/capacity behavior. Check consistency rules for table, index and chosen global-table configuration instead of applying one blanket guarantee.

Transactions support up to 100 distinct items and 4 MB, with documented same-account/Region restrictions. A thirty-item atomic change does not inherently require a Saga. Streams enable change-driven processing; consumers must tolerate retry/duplicate events and avoid equating a stream with indefinite archival retention.

Capacity and cache

On-demand bills request units; provisioned capacity bills configured read/write units and can use autoscaling. Item size and consistency affect units: a 5-KB standard write consumes five write units, and a strongly consistent 5-KB read consumes two read units. Transactions have different multipliers.

DAX is a read-through cache for supported DynamoDB access; it is not an independent database. A single node can be used for development, while production availability calls for a multi-node design. TTL expiry is asynchronous deletion, not a precise alarm clock. Do not invent a thirty-day DAX reservation product.

Backup and sandbox exercise

PITR supports a configurable one-to-thirty-five-day window. On-demand backups and longer retention are separate. Use recovery planning to include KMS, IAM and application cutover.

Create a small sandbox table with an orders-by-customer key design. Insert/query labelled orders, deliberately attempt a duplicate conditional write and verify the expected ConditionalCheckFailedException. Query an index using its documented consistency, then restore a backup to a new table and verify records. Observe consumed capacity and throttling metrics. Delete sandbox tables/index-related infrastructure/DAX and reviewed backups after testing.

Cost arithmetic

Compare workloads at identical item size, consistency and Region. 1,000 writes/day × 30 days is 30,000 writes, not a million. 1,000/second × 86,400 × 30 is 2.592 billion operations. Round each request’s units before multiplying rates; include storage, indexes, streams/backup, global replication and optional DAX. Provisioned daily usage is not interchangeable with purchased per-second capacity.

Core Concepts

Data Model

Table
  └── Item (row)
        ├── Attribute (column)
        ├── Partition Key (required, hash)
        └── Sort Key (optional, range)

Primary Key

Partition Key (PK) only:

UserID (PK) → hash-based initial physical placement → item

A partition-key-only table permits only one item per PK. With a composite key, same-PK items form a logical item collection; this is not a promise of permanent placement on one physical partition.

Partition Key + Sort Key (SK):

UserID (PK) → logical item collection, ordered by OrderID (SK)

Query selects one PK and can constrain/order its SK values efficiently. Physical partitions are service-managed: without an LSI, split-for-heat can divide an item collection by SK across partitions; with an LSI, that within-collection split is unavailable. Do not assume adaptive capacity immediately repairs a hot-key design. AWS partition splitting and LSI boundaries.

Example Table: Orders

UserID (PK)OrderID (SK)DateTotalStatus
user123order-0012024-01-1599.99shipped
user123order-0022024-02-20149.99pending
user456order-0012024-01-1029.99delivered

Query: Get all orders for user123:

aws dynamodb query \
  --table-name Orders \
  --key-condition-expression "UserID = :uid" \
  --expression-attribute-values '{":uid": {"S": "user123"}}'

Creating a Table

aws dynamodb create-table \
  --table-name Orders \
  --attribute-definitions '[
    {"AttributeName": "UserID", "AttributeType": "S"},
    {"AttributeName": "OrderID", "AttributeType": "S"}
  ]' \
  --key-schema '[
    {"AttributeName": "UserID", "KeyType": "HASH"},
    {"AttributeName": "OrderID", "KeyType": "RANGE"}
  ]' \
  --billing-mode PAY_PER_REQUEST \
  --table-class STANDARD

Reading and Writing

PutItem (insert/replace)

aws dynamodb put-item \
  --table-name Orders \
  --item '{
    "UserID": {"S": "user123"},
    "OrderID": {"S": "order-001"},
    "Date": {"S": "2024-01-15"},
    "Total": {"N": "99.99"},
    "Status": {"S": "shipped"}
  }'

GetItem (read by PK + SK)

aws dynamodb get-item \
  --table-name Orders \
  --key '{"UserID": {"S": "user123"}, "OrderID": {"S": "order-001"}}'

Query (range of items by SK within a partition)

aws dynamodb query \
  --table-name Orders \
  --key-condition-expression "UserID = :uid AND OrderID BETWEEN :start AND :end" \
  --expression-attribute-values '{
    ":uid": {"S": "user123"},
    ":start": {"S": "order-001"},
    ":end": {"S": "order-999"}
  }'

Scan (full table, expensive)

aws dynamodb scan \
  --table-name Orders \
  --filter-expression "#status = :status" \
  --expression-attribute-names '{"#status":"Status"}' \
  --expression-attribute-values '{":status": {"S": "shipped"}}'

Warning: Scan reads every item in the table. Use FilterExpression to reduce response size, but you still pay for all the reads. Use scans only when deliberately budgeted/controlled; filtering does not remove the read cost.

Batch Operations

# BatchGetItem (up to 100 items)
aws dynamodb batch-get-item \
  --request-items '{
    "Orders": {
      "Keys": [
        {"UserID": {"S": "user123"}, "OrderID": {"S": "order-001"}},
        {"UserID": {"S": "user456"}, "OrderID": {"S": "order-001"}}
      ]
    }
  }'
 
# BatchWriteItem (up to 25 items, 16MB)
aws dynamodb batch-write-item \
  --request-items '{
    "Orders": [
      {"PutRequest": {"Item": {"UserID": {"S": "user789"}, "OrderID": {"S": "order-001"}}}}
    ]
  }'

Index comparison, streams and TTL

DimensionGSILSI
Partition keyCan differ from tableSame as table
Sort keyIndependent optional keyDifferent sort key for same partition
CapacitySeparate provisioned capacity where applicableShares table capacity
CreationSupported after table creationAt table creation
ConsistencyEventually consistent readsSupported eventual/strong reads
Item-collection limitNot the LSI ten-GB constraintTen GB per partition-key item collection

Use attribute-name aliases for reserved names such as Status. Index projections trade storage/write cost against read needs. A low-cardinality GSI partition such as one common status can be hot; model required distribution before creating it. Provisioned index capacity examples do not directly apply to an on-demand table.

Streams expose configured item-change records, not indefinite business history. Choose keys-only/new/old/both image configuration from consumer needs; checkpoint, retry and deduplicate downstream effects. TTL identifies an epoch-time attribute and asynchronously expires eligible items; reads must account for expired-but-not-yet-deleted data where the application requires exact validity.

Partition and cache investigation

Write down each access pattern as a key condition, expected item count/size and consistency requirement. Observe consumed capacity and throttling for the base table and indexes separately. A filter only reduces returned results after reading; projection and transaction behavior have their own billing rules.

DAX read-through caching can help supported repeated eventually consistent reads, but does not make every access faster or replace DynamoDB durability. Check cache miss/staleness behavior and bypass requirements for strongly consistent reads. Handle UnprocessedKeys/UnprocessedItems with bounded backoff; one successful batch API response need not mean every requested item was processed.

Before cleanup, verify backup retention and downstream stream dependencies. Delete only the sandbox table/cache and reviewed recovery artifacts, not every table sharing a naming prefix.

References

Across the wiki