Does a shop read every order?
A DynamoDB query reads the items under one key, while a scan reads the whole table. The difference decides how fast and how cheap every read in your app will be.
An online shop holds a million orders. One customer asks to see hers. A query goes straight to her orders and reads nothing else. The counts the service sends back show the work it did.
By the end of this lesson
Note
I can say why a query is cheaper than a scan.
Key words
| Word | What it means |
|---|---|
| Partition key | the attribute that decides where an item is stored |
| Sort key | the attribute that puts items in order inside one partition key |
| Key condition | the test a query makes on the keys |
| Query | a read that goes to one partition key |
| Scan | a read that looks at every item in the table |
| Filter | a test applied after the items are read |
The cells students fill in are left blank in this preview.
Two keys give every item an address
Our orders table has two key attributes. The partition key is customer. It decides where the service stores an item, so all of one customer's orders sit together.
The sort key is order_id. It puts the orders of one customer in order. Together the two keys name exactly one item. Two orders may share a customer, but never both keys.
Worked example: how the orders are laid out
| Partition key: customer | Sort key: order_id | Other attributes |
|---|---|---|
| ana | 1 | tea, paid, total 4 |
| ana | 2 | milk, new, total 2 |
| ana | 3 | cake, paid, total 15 |
| ben | 7 | tea, paid, total 8 |
| cai | 4 | milk, new, total 6 |
| cai | 9 | tea, paid, total 3 |
The cells students fill in are left blank in this preview.
Query goes to one place, scan everywhere
A query names the partition key. It must, or the service refuses with a ValidationException and HTTP status 400. The service goes straight to that customer and reads only those orders.
A scan names nothing. It reads every item in the table, one after another. Both answers carry a Count, the items returned, and a ScannedCount, the items looked at.
Try it: a query and a scan
orders = [
{"customer": {"S": "ana"}, "order_id": {"N": "1"}, "status": {"S": "paid"}},
{"customer": {"S": "ana"}, "order_id": {"N": "2"}, "status": {"S": "new"}},
{"customer": {"S": "ana"}, "order_id": {"N": "3"}, "status": {"S": "paid"}},
{"customer": {"S": "ben"}, "order_id": {"N": "7"}, "status": {"S": "paid"}},
{"customer": {"S": "cai"}, "order_id": {"N": "4"}, "status": {"S": "new"}},
{"customer": {"S": "cai"}, "order_id": {"N": "9"}, "status": {"S": "paid"}},
]
def scan(items):
return {"Count": len(items), "ScannedCount": len(items)}
def query(items, customer):
found = []
for item in items:
if item["customer"]["S"] == customer:
found.append(item)
return {"Count": len(found), "ScannedCount": len(found)}
print("scan:", scan(orders))
print("query ana:", query(orders, "ana"))
# Change "ana" to "ben". How many items does the query look at now?
A filter trims the answer, not the work
A key condition can also test the sort key. For example, order_id of 2 or more. The service then reads only that part of the partition.
A filter tests any other attribute, such as status. It runs after the items are read, so it changes the Count but not the ScannedCount.
Worked example: ana's paid orders from 2
| Step | Items left | What happened |
|---|---|---|
| Partition key is ana | 3 | orders 1, 2 and 3 |
| Sort key 2 or more | 2 | orders 2 and 3, so ScannedCount is 2 |
| Filter: status is paid | 1 | order 3 only, so Count is 1 |
The cells students fill in are left blank in this preview.
A common mistake
Warning
Many people think a filter makes a read cheaper. It does not. The service reads the items first and drops some afterwards. Put what you can into the key condition, and use a filter only for what is left.
Try it: read the two counts
answer = {
"Count": 1,
"ScannedCount": 2,
"Items": [
{"customer": {"S": "ana"}, "order_id": {"N": "3"}, "status": {"S": "paid"}},
],
}
dropped = answer["ScannedCount"] - answer["Count"]
print("looked at:", answer["ScannedCount"])
print("returned:", answer["Count"])
print("dropped by the filter:", dropped)
# Change ScannedCount to 3. What does it say the filter dropped?