← scratchings

DynamoDB Cheat Sheet (AWS CLI)

Reference for aws dynamodb — table inventory, CRUD, scans, queries, and a focused workflow for finding and deleting items by lockid.

Assumes AWS CLI v2 and credentials already configured (aws configure / SSO / env vars). For one-off scoping, append --region <r> and --profile <p> to any command.


0. Mental model — what you need to know before any command

DynamoDB is schemaless except for keys. Every table has: - A partition key (PK), required. - Optionally a sort key (SK). PK+SK together = primary key. - Zero or more secondary indexes (GSI / LSI), each with their own key schema.

This drives the three retrieval verbs: | Verb | What it does | Cost / latency | |---|---|---| | get-item | Fetch one item by full primary key | Cheapest, single-digit ms | | query | Fetch items sharing a PK (optionally narrowed by SK) — on the table or any index | Cheap, only scanned keys consume RCU | | scan | Read the entire table / index and filter in memory | Expensive — full-table read every time |

Rule of thumb: if your access pattern (e.g. "find by lockid") isn't covered by the primary key or a GSI, you're stuck with scan until someone adds an index.

All values in expressions are encoded as DynamoDB JSON: {"S": "..."}, {"N": "123"}, {"BOOL": true}, etc. The newer aws dynamodb execute-statement (PartiQL) sidesteps the type tags entirely — see §7.


1. Discovery & inventory

Command Purpose
aws dynamodb list-tables All tables in current region
aws dynamodb list-tables --max-items 50 --starting-token <t> Paginate
aws dynamodb describe-table --table-name T Schema, keys, indexes, item count (item count is stale — refreshed ~every 6h)
aws dynamodb describe-table --table-name T --query 'Table.KeySchema' Just the PK / SK
aws dynamodb describe-table --table-name T --query 'Table.GlobalSecondaryIndexes[].{Name:IndexName,Keys:KeySchema}' GSIs and their key schemas — check this first when chasing a non-key attribute like lockid
aws dynamodb describe-table --table-name T --query 'Table.AttributeDefinitions' Typed attributes referenced by keys/indexes
aws dynamodb describe-time-to-live --table-name T TTL attribute name + status

2. CRUD basics

Examples use a table Locks with PK lockid (S). Adjust the JSON to match your table.

Create / replace — put-item

aws dynamodb put-item \
  --table-name Locks \
  --item '{
    "lockid":  {"S": "abc-123"},
    "owner":   {"S": "alice"},
    "acquired":{"N": "1716240000"},
    "ttl":     {"N": "1716243600"}
  }'

Conditional put (don't clobber):

aws dynamodb put-item \
  --table-name Locks \
  --item '{"lockid":{"S":"abc-123"},"owner":{"S":"alice"}}' \
  --condition-expression "attribute_not_exists(lockid)"

Read — get-item

aws dynamodb get-item \
  --table-name Locks \
  --key '{"lockid":{"S":"abc-123"}}'

Strongly-consistent read (default is eventually consistent):

aws dynamodb get-item --table-name Locks \
  --key '{"lockid":{"S":"abc-123"}}' \
  --consistent-read

Project only specific attributes:

aws dynamodb get-item --table-name Locks \
  --key '{"lockid":{"S":"abc-123"}}' \
  --projection-expression "lockid, owner"

Update — update-item

aws dynamodb update-item \
  --table-name Locks \
  --key '{"lockid":{"S":"abc-123"}}' \
  --update-expression "SET #o = :o, acquired = :t" \
  --expression-attribute-names  '{"#o":"owner"}' \
  --expression-attribute-values '{":o":{"S":"bob"},":t":{"N":"1716240900"}}' \
  --return-values ALL_NEW

Increment a counter:

aws dynamodb update-item --table-name Locks \
  --key '{"lockid":{"S":"abc-123"}}' \
  --update-expression "ADD attempts :inc" \
  --expression-attribute-values '{":inc":{"N":"1"}}'

Remove an attribute:

aws dynamodb update-item --table-name Locks \
  --key '{"lockid":{"S":"abc-123"}}' \
  --update-expression "REMOVE owner"

Delete — delete-item

aws dynamodb delete-item \
  --table-name Locks \
  --key '{"lockid":{"S":"abc-123"}}'

Conditional delete (only if owned by alice — avoids deleting a re-acquired lock):

aws dynamodb delete-item --table-name Locks \
  --key '{"lockid":{"S":"abc-123"}}' \
  --condition-expression "#o = :o" \
  --expression-attribute-names  '{"#o":"owner"}' \
  --expression-attribute-values '{":o":{"S":"alice"}}' \
  --return-values ALL_OLD

--return-values ALL_OLD echoes the deleted item so you can confirm or pipe it onward.


3. Bulk reads & writes

Batch get — up to 100 items by key

aws dynamodb batch-get-item --request-items '{
  "Locks": {
    "Keys": [
      {"lockid":{"S":"abc-123"}},
      {"lockid":{"S":"abc-124"}}
    ]
  }
}'

Batch write — up to 25 puts/deletes per call

aws dynamodb batch-write-item --request-items '{
  "Locks": [
    {"DeleteRequest":{"Key":{"lockid":{"S":"abc-123"}}}},
    {"DeleteRequest":{"Key":{"lockid":{"S":"abc-124"}}}},
    {"PutRequest":{"Item":{"lockid":{"S":"abc-125"},"owner":{"S":"alice"}}}}
  ]
}'

Check the response for UnprocessedItems and retry those — batch-write does not retry partial failures on its own.

Transactions — transact-write-items (up to 100 items, all-or-nothing)

aws dynamodb transact-write-items --transact-items '[
  {"Delete":{"TableName":"Locks","Key":{"lockid":{"S":"abc-123"}},
             "ConditionExpression":"#o = :o",
             "ExpressionAttributeNames":{"#o":"owner"},
             "ExpressionAttributeValues":{":o":{"S":"alice"}}}},
  {"Put":{"TableName":"AuditLog",
          "Item":{"event_id":{"S":"e-1"},"deleted_lock":{"S":"abc-123"}}}}
]'

4. Querying

Query by primary key

aws dynamodb query \
  --table-name Locks \
  --key-condition-expression "lockid = :id" \
  --expression-attribute-values '{":id":{"S":"abc-123"}}'

With a sort-key range (table has PK tenant, SK acquired):

aws dynamodb query --table-name Locks \
  --key-condition-expression "tenant = :t AND acquired BETWEEN :lo AND :hi" \
  --expression-attribute-values '{
    ":t":{"S":"acme"},":lo":{"N":"1716000000"},":hi":{"N":"1716240000"}
  }'

Query a GSI

aws dynamodb query --table-name Locks \
  --index-name owner-acquired-index \
  --key-condition-expression "#o = :o" \
  --expression-attribute-names  '{"#o":"owner"}' \
  --expression-attribute-values '{":o":{"S":"alice"}}'

Useful query flags

Flag Purpose
--filter-expression Server-side filter applied after the key match — saves bandwidth, not RCU
--projection-expression Pick attributes to return
--limit N Max items per page
--scan-index-forward false Descending sort-key order (newest first)
--consistent-read Strongly consistent (table only, not GSI)
--select COUNT Return only Count, no items
--starting-token <t> / --no-paginate Manual pagination in CLI v2

5. Scanning (last resort)

aws dynamodb scan --table-name Locks

With a filter (still reads the whole table — only the network response shrinks):

aws dynamodb scan --table-name Locks \
  --filter-expression "owner = :o" \
  --expression-attribute-values '{":o":{"S":"alice"}}'

Parallel scan (splits work across N workers, run each in a separate shell):

aws dynamodb scan --table-name Locks --total-segments 4 --segment 0
aws dynamodb scan --table-name Locks --total-segments 4 --segment 1
# ...etc

6. The lockid workflows ★

How you find/delete by lockid depends on what lockid is in your table. Run describe-table first (§1) and pick the matching path.

6a. lockid IS the partition key — single-item ops

Easy case. Use get-item / delete-item directly:

# Find
aws dynamodb get-item --table-name Locks \
  --key '{"lockid":{"S":"abc-123"}}'

# Delete
aws dynamodb delete-item --table-name Locks \
  --key '{"lockid":{"S":"abc-123"}}' \
  --return-values ALL_OLD

If lockid is the PK and there's a sort key, you may have multiple items per lockid. Use query to enumerate, then delete each by full key (see 6c).

6b. lockid is a GSI key — query the index, delete via the base table key

delete-item only accepts the base table's primary key, not the index key. So the flow is: query the GSI to discover base keys → loop → delete-item against the base table.

# 1. Query the index — project just the base PK (assume base PK is "id")
aws dynamodb query --table-name Locks \
  --index-name lockid-index \
  --key-condition-expression "lockid = :lid" \
  --expression-attribute-values '{":lid":{"S":"abc-123"}}' \
  --projection-expression "id" \
  --output json > /tmp/lockid-hits.json

# 2. Inspect first
jq '.Count, .Items' /tmp/lockid-hits.json

# 3. Delete each match
jq -r '.Items[].id.S' /tmp/lockid-hits.json | while read -r id; do
  aws dynamodb delete-item --table-name Locks \
    --key "{\"id\":{\"S\":\"$id\"}}" \
    --condition-expression "lockid = :lid" \
    --expression-attribute-values '{":lid":{"S":"abc-123"}}' \
    --return-values ALL_OLD
done

The --condition-expression on the delete guards against a race where the item's lockid changed between the query and the delete (GSIs are eventually consistent, so this matters).

6c. lockid is a non-key attribute — scan, then delete

Slowest path; use sparingly on large tables. Same general shape:

# 1. Scan with a filter (uses full-table RCU — be careful on big tables)
aws dynamodb scan --table-name Locks \
  --filter-expression "lockid = :lid" \
  --expression-attribute-values '{":lid":{"S":"abc-123"}}' \
  --projection-expression "id" \
  --output json > /tmp/lockid-hits.json

# 2. Sanity check
jq '.Count' /tmp/lockid-hits.json

# 3. Delete loop (single-item)
jq -r '.Items[].id.S' /tmp/lockid-hits.json | while read -r id; do
  aws dynamodb delete-item --table-name Locks \
    --key "{\"id\":{\"S\":\"$id\"}}"
done

If the table has a composite key (PK id + SK version), project both attributes and build the key JSON accordingly:

aws dynamodb scan --table-name Locks \
  --filter-expression "lockid = :lid" \
  --expression-attribute-values '{":lid":{"S":"abc-123"}}' \
  --projection-expression "id, version" \
  --output json \
| jq -r '.Items[] | "\(.id.S)\t\(.version.N)"' \
| while IFS=$'\t' read -r id version; do
    aws dynamodb delete-item --table-name Locks \
      --key "{\"id\":{\"S\":\"$id\"},\"version\":{\"N\":\"$version\"}}"
  done

6d. Faster bulk delete via batch-write-item (25 at a time)

For many matches, batch the deletes — far fewer API calls and lower latency.

# Build batches of 25 DeleteRequests and POST them
jq -c '.Items | _nwise(25) |
  {"Locks": [.[] | {"DeleteRequest":{"Key":{"id": .id}}}]}' \
  /tmp/lockid-hits.json \
| while read -r batch; do
    aws dynamodb batch-write-item --request-items "$batch"
    # NOTE: inspect UnprocessedItems and retry — batch-write doesn't retry for you
  done

_nwise(25) chunks into groups of ≤25, the max per batch-write-item call.

6e. Safety knobs to always consider


7. PartiQL — SQL-ish syntax, less JSON tagging

PartiQL (execute-statement) is convenient for ad-hoc CLI queries because you can use plain SQL literals instead of {"S":"..."}.

# Find by lockid (works for PK, GSI key, or non-key attribute — DynamoDB picks the cheapest plan)
aws dynamodb execute-statement \
  --statement "SELECT id, lockid, owner FROM Locks WHERE lockid = 'abc-123'"

# Delete by PK
aws dynamodb execute-statement \
  --statement "DELETE FROM Locks WHERE id = 'abc-123'"

# Update
aws dynamodb execute-statement \
  --statement "UPDATE Locks SET owner = 'bob' WHERE id = 'abc-123'"

Caveats: - DELETE / UPDATE only work on a full primary key — same constraint as the low-level API. - If your WHERE clause doesn't match a PK or index, PartiQL silently does a full scan. Run EXPLAIN (in the console) or check costs before unleashing on a large table. - Use execute-transaction for transactional PartiQL. - Pagination uses NextToken like other commands.


8. TTL — let DynamoDB delete for you

If items have a TTL attribute, you may not need to delete by lockid at all — set the TTL and DynamoDB removes them within ~48h.

Command Purpose
aws dynamodb describe-time-to-live --table-name T Is TTL enabled? On what attribute?
aws dynamodb update-time-to-live --table-name T --time-to-live-specification "Enabled=true,AttributeName=ttl" Enable TTL
(per-item) --update-expression "SET ttl = :t" with epoch seconds Mark item for expiry

TTL is asynchronous, low priority, and unconditional once triggered. It does not consume write capacity. Don't use it for "delete in 5 minutes" SLAs.


9. Streams, exports, and other adjacents

Command Purpose
aws dynamodb describe-table --table-name T --query 'Table.StreamSpecification' Is a stream enabled?
aws dynamodb update-table --table-name T --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES Enable a stream
aws dynamodb list-streams --table-name T Stream ARNs
aws dynamodb describe-continuous-backups --table-name T PITR status
aws dynamodb update-continuous-backups --table-name T --point-in-time-recovery-specification PointInTimeRecoveryEnabled=true Enable PITR
aws dynamodb create-backup --table-name T --backup-name name On-demand backup
aws dynamodb restore-table-from-backup --target-table-name T2 --backup-arn ... Restore
aws dynamodb export-table-to-point-in-time --table-arn ... --s3-bucket b --export-format DYNAMODB_JSON Snapshot to S3 (requires PITR)

10. Common workflows

Inspect a table's lockid coverage

T=Locks
aws dynamodb describe-table --table-name "$T" \
  --query '{PK:Table.KeySchema, GSIs:Table.GlobalSecondaryIndexes[].{Name:IndexName,Keys:KeySchema}}' \
  --output yaml

Look for lockid in the PK or any GSI's key schema. If absent → §6c (scan).

Count items matching a lockid before acting

aws dynamodb query --table-name Locks \
  --index-name lockid-index \
  --key-condition-expression "lockid = :lid" \
  --expression-attribute-values '{":lid":{"S":"abc-123"}}' \
  --select COUNT

Swap query for scan (with --filter-expression) when lockid isn't indexed.

Find-and-delete by lockid, single pipeline

LID=abc-123
T=Locks
IDX=lockid-index
PK=id

aws dynamodb query --table-name "$T" --index-name "$IDX" \
  --key-condition-expression "lockid = :lid" \
  --expression-attribute-values "{\":lid\":{\"S\":\"$LID\"}}" \
  --projection-expression "$PK" --output json \
| jq -r ".Items[].$PK.S" \
| xargs -I{} -n1 aws dynamodb delete-item --table-name "$T" \
    --key "{\"$PK\":{\"S\":\"{}\"}}" --return-values ALL_OLD

Restore a single item from a backup

There's no "restore one item." Either restore the whole backup to a side table (restore-table-from-backup) and copy the row across, or pull the item from a PITR S3 export and re-put-item.


11. Tips & gotchas