Principiante7 min di lettura

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 Scan legge 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 Scan diventa più lento man mano che cresci. Una Query con chiave resta piatta — tocca una sola partizione per quanto grande diventi la tabella.
  • La soluzione è quasi sempre modellazione, non tuning. Se fai Scan per 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, payloadBytes

Diciamo 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 + filtroQuery con chiave
LettureOgni item nella tabellaUna partizione, ristretta dalla SK
Capacità fatturataIntera tabella, prima del filtroSolo gli item nella tua fetta
Il nostro esempio~250.000 RCU (~500 MB)qualche centinaio di RCU (~2 MB)
LatenzaCresce con la dimensione della tabellaPiatta man mano che la tabella cresce
Numero di risultatiNon decide nulla sul costoCorrisponde 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.

NoNoDevo leggere itemConosci la partition key?Query una partizioneUna GSI può darle una chiave?Aggiungi una GSI, poi QueryScan ultima risorsa

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.

Aggiornato