DynamoDB Scan with the AWS CLI
aws dynamodb scan auto-paginates, which is convenient and means the one number you would use to judge the damage is wrong by default. See Query vs. Scan for when to avoid the operation entirely.
Code
aws dynamodb scan \
--table-name 'Music' \
--filter-expression '#filter0 >= :filterValue0' \
--expression-attribute-names '{"#filter0":"Year"}' \
--expression-attribute-values '{":filterValue0":{"N":"2010"}}'--return-consumed-capacity reports one page, not the scan
The fixture is 600 songs of roughly 3.9 KB each, of which 8 match. Add --return-consumed-capacity TOTAL to that command and the CLI prints:
{ "Count": 8, "ScannedCount": 600, "CU": 128.5 }The scan really cost 284.5 read units across three pages. Count and ScannedCount were summed across all three; ConsumedCapacity was taken from the first and the rest thrown away. That is not a bug so much as a declared rule — botocore's DynamoDB paginator config lists Count and ScannedCount as result keys and ConsumedCapacity as a non-aggregate key.
The tell is that the figure moves when the work does not. Same table, same 600 items read, one extra flag:
--page-size 50 -> { "Count": 8, "ScannedCount": 600, "CU": 24.0 }If you are sizing a table from a CLI scan, sum the pages yourself with --page-size plus --starting-token, or read capacity from CloudWatch.
--max-items does not stop the scan
--max-items 3 reads like a cheap sample. It is not:
--max-items 3 -> { "Count": 8, "ScannedCount": 600 }The CLI kept requesting pages until it had enough matches, which with a selective filter meant the entire table, then truncated the printed list. Its own resume token says so out loud:
{"ExclusiveStartKey": {"Artist": {"S": "Arturo Sandoval"},
"SongTitle": {"S": "Cubano Chant 0541"}}, "boto_truncate_amount": 3}boto_truncate_amount is a client-side counter. To bound what DynamoDB reads, use --page-size, which sets the API's Limit on each underlying request, and resume with --starting-token:
aws dynamodb scan \
--table-name 'Music' \
--page-size 500 \
--max-items 100 \
--starting-token "$NEXT_TOKEN"Measured 2026-07-28 against DynamoDB Local (amazon/dynamodb-local) with aws-cli/2.36.9. The JSON above is the CLI's own output, reshaped by --query for width.
Explanation
--filter-expressionruns after the read, so it shrinks the output and not the bill.#filter0aliasesYearthrough--expression-attribute-namesbecauseYearis a reserved word.--expression-attribute-valueswants the number quoted twice: shell quotes around the JSON, and the value as a JSON string. Dropping the inner quotes never reaches DynamoDB — the CLI rejects it locally withInvalid type for parameter ExpressionAttributeValues.:v.N, value: 2010, type: <class 'int'>, valid types: <class 'str'>.--page-sizeis the flag that changes the API call. It becomesLimiton each underlying request, capping items evaluated per page. The rest of the pagination family (--max-items,--starting-token) is the CLI managing its own output.- Parallel scan needs
--segment N --total-segments Mper worker, and each worker keeps its own--starting-token. It buys wall-clock time, not capacity.
Do it visually
The DynamoDB Expression Builder emits the filter and both JSON maps already escaped for the shell, which removes the quoting layer that makes CLI expressions fail before DynamoDB ever sees them.
To explore tables in a GUI, with filtered and paginated grids, download DynoTable instead of scanning blind from the terminal.
Related guides
- Query vs. Scan — when (rarely) a
scanis justified. - Why is my DynamoDB Scan slow and expensive? — the cost model and how to avoid it.
- DynamoDB ProvisionedThroughputExceededException — what a full-table scan does to a provisioned table's capacity.
- DynamoDB ThrottlingException — the other throttle, and how exponential backoff handles it.