Perché uno Scan di DynamoDB è lento e costoso
Uno Scan legge ogni item nella tabella e filtra solo dopo. È l'operazione a
cui ricorri per memoria muscolare da SQL, e quella che fa silenziosamente lievitare
la tua bolletta peggiorando la tua latenza rispetto al box RDS che hai lasciato.
Perché il mio Scan di DynamoDB è lento e costoso?
Uno Scan legge ogni item nella tabella prima che la FilterExpression venga
eseguita, quindi paghi per leggere l'intera tabella indipendentemente da quante
righe vengono restituite, e diventa più lento con la crescita della tabella. La
soluzione è quasi sempre una Query con chiave — modella il pattern di accesso
intorno a una chiave in modo che DynamoDB tocchi una sola partizione invece di
tutto.
- Uno
Scanlegge l'intera tabella, ogni volta. La dimensione, non il numero dei tuoi risultati, decide cosa paghi e quanto ci mette. - La
FilterExpressionè una bugia sul costo. Gira dopo che la lettura è contabilizzata, quindi restituire 12 item può fatturare la lettura di 12 milioni. - Uno
Scandiventa più lento man mano che cresci. UnaQuerycon chiave resta piatta — tocca una sola partizione per quanto grande diventi la tabella. - La soluzione è quasi sempre modellazione, non tuning. Se fai
Scanper rispondere a una domanda di routine, ti manca una chiave.
Cosa fa davvero uno Scan
Venendo da SQL, SELECT * FROM events WHERE type = 'checkout' sembra gratis — il
motore ha un indice, o non ce l'ha, ma in entrambi i casi ottieni righe in
risposta. In DynamoDB non c'è un query planner a deciderlo per te.
Uno Scan percorre l'intera tabella sequenzialmente, 1 MB alla volta, e passa
ogni pagina alla tua FilterExpression. Qualunque cosa il filtro rifiuti è
comunque letta, comunque contabilizzata e comunque sulla tua bolletta.
(AWS: Scanning tables)
Quella è la trappola. Il filtro sembra una clausola WHERE, ma cambia l'insieme
dei risultati, mai il costo. Uno Scan consuma la stessa capacità di lettura sia
che un filtro sia presente o no. (AWS: Scanning tables)
Conta le unità di lettura
DynamoDB contabilizza le letture in read capacity unit (RCU). Una RCU compra una singola lettura fortemente coerente di un item fino a 4 KB; le letture eventualmente coerenti costano la metà. Item più grandi arrotondano ai 4 KB successivi. (AWS: Read/write capacity mode)
Prendi una tabella di analytics, ProductEvents. Ogni riga è un evento tracciato:
PK = "TENANT#acme"
SK = "TS#2026-06-23T14:08:55Z#evt_9f3a"
attrs: eventType, sessionId, userId, payloadBytesDiciamo che contiene 2.000.000 di eventi, ognuno di ~1 KB, tutti sotto un solo tenant affollato. Vuoi i checkout di oggi. La mossa riflessa:
Scan ProductEvents
FilterExpression: eventType = "checkout"
Quel filtro potrebbe restituire 40 righe. Ma lo Scan ha letto tutti i 2.000.000
di item prima. A ~1 KB ciascuno (1 RCU per 4 KB, eventualmente coerente ≈ 0,5
RCU per 4 KB), hai contabilizzato all'incirca 250.000 RCU — e hai paginato
attraverso ~500 MB di dati — per restituire 40 item.
Ora modella l'access pattern come una chiave e fai invece Query:
Query ProductEvents
PK = "TENANT#acme"
AND SK begins_with "TS#2026-06-23"
Questo legge solo la fetta corrispondente di una partizione. Se quelle 40 righe di checkout più gli altri eventi della giornata arrivano a ~2 MB, paghi per ~2 MB di letture, non 500 MB. Stessa risposta, una minuscola frazione del costo — e la latenza resta piatta man mano che la tabella cresce.
Scan vs Query, contabilizzati
| Scan + filtro | Query con chiave | |
|---|---|---|
| Letture | Ogni item nella tabella | Una partizione, ristretta dalla SK |
| Capacità fatturata | Intera tabella, prima del filtro | Solo gli item nella tua fetta |
| Il nostro esempio | ~250.000 RCU (~500 MB) | qualche centinaio di RCU (~2 MB) |
| Latenza | Cresce con la dimensione della tabella | Piatta man mano che la tabella cresce |
| Numero di risultati | Non decide nulla sul costo | Corrisponde a ciò che paghi |
La lezione che la tabella codifica: su uno Scan, il numero dei tuoi risultati e
la tua bolletta non sono correlati. Su una Query, si seguono a vicenda.
Decidi prima di fare Scan
La maggior parte degli Scan accidentali nasce da una domanda: posso nominare la
partizione che mi serve? Se sì, è una Query. Se no, la soluzione è una chiave,
non un filtro più grande.
Ecco la decisione in forma di flusso.
Il percorso finisce quasi sempre a Query; cadi su Scan solo quando nessuna
chiave — presente o aggiungibile — si adatta all'access pattern.
Se il pattern è reale e ricorrente ma la tabella base non può dargli una chiave,
quello è il segnale per aggiungere una Global Secondary Index
così che la domanda diventi una Query. Modellare le tue chiavi attorno ai tuoi
access pattern fin dall'inizio è tutto il gioco — vedi
single-table design.
Scrivi la query con chiave, non un filtro
Quando hai davvero bisogno di una condizione oltre la chiave, costruiscila
deliberatamente invece di buttare tutto in una FilterExpression. Il
DynamoDB Expression Builder genera per te la
KeyConditionExpression e i placeholder degli attributi, così che la partition e
la sort key facciano il restringimento — prima che DynamoDB contabilizzi la
lettura, non dopo.
KeyConditionExpression: PK = :tenant AND begins_with(SK, :day)
Quando uno Scan va davvero bene
Uno Scan non è proibito — è solo il default sbagliato. È lo strumento giusto
quando intendi davvero "leggi tutto":
- Export una tantum o backfill eseguiti a mano.
- Tabelle di config / lookup minuscole dove l'intera tabella è di pochi KB.
- Job in background che paginano la tabella completa di proposito. Dividili tra
worker con
Segment/TotalSegments— un parallel scan — invece di un lungo crawl sequenziale. (AWS: Scanning tables)
E nota che PartiQL non ti salva: SELECT * FROM ProductEvents WHERE eventType = 'checkout' senza un predicato di chiave compila dritto in uno Scan.
È lo stesso footgun in abiti SQL. (Vedi Query vs Scan per
la scomposizione completa.)
Quando hai davvero bisogno di analytics tra item — un GROUP BY, un JOIN, un
aggregato che DynamoDB non può esprimere — la SQL Workbench di DynoTable li esegue
lato client su un insieme di risultati limitato, invece di martellare la tabella
con uno Scan completo.
Prossimi passi
Stima quanto costa l'uno o l'altro pattern con il calcolatore di capacità, leggi Query vs Scan per il confronto a livello di API, e scarica DynoTable per eseguirli contro le tue tabelle e guardare tu stesso la capacità consumata.