Query vs Scan in DynamoDB
Query legge una singola collezione di Item per partition key (restringendo facoltativamente la
sort key); Scan legge l'intera tabella e filtra in seguito. Sembrano
simili nell'API ma fatturano — e scalano — in modo completamente diverso.
Quando dovrei usare Query vs Scan in DynamoDB?
Usa Query ogni volta che puoi indicare la partizione di cui hai bisogno — legge una sola e fattura solo gli Item corrispondenti. Ricorri a Scan solo per export una tantum o tabelle piccole; legge ogni Item e fattura l'intera tabella prima che venga eseguita qualsiasi FilterExpression. Con dati reali, Query vince.
- Query è mirata: paghi per gli Item nella partizione corrispondente.
- Scan è esaustiva: paghi per leggere ogni Item, poi ne scarti la maggior parte con una
FilterExpressionche gira dopo che la lettura è stata conteggiata.
Su una tabella di dimensioni reali, uno Scan con un filtro è il classico trabocchetto del "perché la mia
bolletta è enorme e la mia latenza è peggio di RDS".
Affiancati
| Query | Scan | |
|---|---|---|
| Letture | Una partizione (per PK) | Ogni Item della tabella |
| Capacità fatturata | Item corrispondenti nella partizione | Intera tabella, prima del filtro |
FilterExpression | Applicata dopo la lettura — comunque fatturata per la lettura | Idem — il filtro non riduce mai il costo |
| Latenza | Costante al crescere della tabella | Cresce con la dimensione della tabella |
| Paginazione | 1 MB/pagina → LastEvaluatedKey | 1 MB/pagina; parallelizzabile |
| Usala per | Pattern di accesso noti | Export una tantum, piccole tabelle di config |
La trappola chiave: una FilterExpression gira dopo che DynamoDB ha conteggiato la lettura, su
entrambe le operazioni. Uno Scan che "restituisce 10 righe" può fatturare la lettura di un milione —
il filtro è una comodità, mai un controllo dei costi.
Quanto costa davvero uno Scan completo
Mettiamoci dei numeri. DynamoDB contabilizza le letture in unità da 4 KB: una
lettura costa una
ogni 4 KB, una lettura
la metà. Query e Scan
sommano la dimensione di ogni Item che toccano — non di ogni Item che
restituiscono — e arrotondano per eccesso ai successivi 4 KB.
Prendi una tabella da 1 milione di Item con una media di 2 KB per Item (~2 GB di dati) e un pattern di accesso che ha bisogno di 10 di quegli Item:
| Item letti | Dati contabilizzati | Unità di lettura (eventualmente coerenti) | |
|---|---|---|---|
Scan + FilterExpression | 1.000.000 | ~2 GB | ~262.000 |
Query su una chiave corrispondente | 10 | 20 KB | 3 |
Gli stessi 10 Item, cinque ordini di grandezza di distanza — e lo Scan fattura
quelle ~262.000 a ogni esecuzione, che il filtro trovi dieci Item o nessuno. Con
la fatturazione sono request unit che finiscono dritte
in bolletta; sulle tabelle un grande Scan compete
con il traffico di produzione per il throughput e può strozzarlo fino a una
ProvisionedThroughputExceededException.
Altri tre fatti sui costi che sorprendono:
Select: COUNTnon è gratis. Una Query o uno Scan di conteggio consuma esattamente la stessa capacità di lettura della lettura degli Item — semplicemente non li restituisce.Limitlimita gli Item valutati, non gli Item corrispondenti. Combinato con un filtro, una pagina può tornare vuota fatturando comunque una pagina intera di letture.- Non devi mai tirare a indovinare. Passa
ReturnConsumedCapacity: TOTALe ogni risposta riporta la capacità appena consumata.
Verifica quanto pesano i tuoi Item con il calcolatore della dimensione degli Item, poi trasforma le unità di lettura in una bolletta mensile con il calcolatore di prezzi.
Usa Query
Query PK = "USER#42" AND SK begins_with "ORDER#"
Se ti ritrovi a ricorrere a Scan per rispondere a un pattern di accesso comune, è
un segnale di modellazione: aggiungi un Global Secondary Index così il
pattern diventa una Query.
La scelta si riduce a una domanda — sai indicare la partizione che ti serve?
Se conosci la chiave fai una Query; altrimenti aggiungi un GSI per renderla tale, e
ricorri allo Scan solo quando nessuna chiave è adatta.
Quando Scan va bene
Export una tantum, piccole tabelle di config e job in background che paginano
deliberatamente l'intera tabella. Usa Segment/TotalSegments per suddividere
uno Scan tra più worker (uno — vedi
gli Scan paralleli in DynamoDB) quando devi
davvero leggere tutto, e paginalo correttamente con LastEvaluatedKey
(guida alla paginazione). Se il problema è uno Scan che
esegui già, perché Scan è lento e costoso
ti guida nel triage.
Un riflessivo SELECT * FROM table su DynamoDB è lo stesso anti-pattern travestito
da PartiQL — si compila in uno Scan. Quando hai davvero bisogno di analisi tra più Item
(un GROUP BY, un JOIN, un aggregato), il Workbench SQL di DynoTable li esegue
lato client su un insieme di risultati limitato invece di martellare la tabella.
Prova DynoTable per eseguire e ispezionare queste query sulle tue tabelle — mostra la capacità consumata di ogni operazione che esegue.