A filter is not an index
A FilterExpression runs after the read: you pay for everything scanned.
Why
It is tempting to read this Scan as 'find the rock songs'. It is not. A Scan reads every item in the table, and the FilterExpression only drops some of them on the way out. The 1MB read limit and the bill are both applied before the filter.
Run the Scan, then the index Query for the identical result. The Scan's Scanned is the whole table; the index Query's Scanned equals what it returned. Same answer, very different cost.
You pay for Scanned, not Returned. A filter trims the result, never the bill. When you reach for a filter, reach for a key or an index instead.
These rows are real and already here. Running an operation against them downloads a DynamoDB-compatible engine, about 850 KB, and executes it in this tab.
Read the whole table, then drop everything that is not rock. Watch Scanned climb far past Returned.
Run an operation to see the raw engine response.