Amazon DynamoDB spiegato: costruire sistemi scalabili
Scopri cos'è Amazon DynamoDB, come funziona il suo modello NoSQL e pattern pratici per progettare sistemi scalabili a bassa latenza e microservizi.

Cos'è DynamoDB e perché i team lo usano
Amazon DynamoDB è un servizio database NoSQL completamente gestito di AWS, pensato per applicazioni che necessitano di letture e scritture con latenza costante e bassa a qualsiasi scala. “Completamente gestito” significa che AWS si occupa delle attività infrastrutturali—provisioning hardware, replica, patching e molte attività operative—così i team possono concentrarsi sulle funzionalità anziché sulla gestione dei server del database.
Alla base, DynamoDB memorizza i dati come item (righe) all'interno di tabelle, ma ogni item può avere attributi flessibili. Il modello dati si capisce meglio come una combinazione di:
- Key-value: recuperi un item rapidamente tramite la chiave primaria, come cercare un record per ID.
- Document: memorizzi attributi annidati (mappe e liste), simili a JSON, utile per campi correlati senza schemi rigidi.
I team scelgono DynamoDB quando vogliono performance prevedibili e operazioni più semplici per carichi che non si adattano bene alle join relazionali. È comunemente usato per microservizi (ogni servizio possiede i suoi dati), app serverless con traffico a raffica e sistemi event-driven che reagiscono ai cambiamenti dei dati.
Questo articolo illustra i mattoni fondamentali (tabelle, chiavi e indici), come modellare intorno agli access patterns (incluso il design a tabella singola), come funzionano le modalità di scaling e capacità, e pattern pratici per streammare i cambiamenti in un'architettura event-driven.
Concetti fondamentali: tabelle, item e chiavi primarie
DynamoDB si organizza attorno a pochi elementi fondamentali, ma i dettagli contano perché determinano come modellare i dati e quanto saranno veloci (e costose) le richieste.
Tabelle, item e attributi
Una tabella è il contenitore di primo livello. Ogni record in una tabella è un item (simile a una riga), e ogni item è un insieme di attributi (simili a colonne).
A differenza dei database relazionali, gli item nella stessa tabella non devono condividere gli stessi attributi. Un item potrebbe avere {status, total, customerId}, mentre un altro include {status, shipmentTracking}—DynamoDB non richiede uno schema fisso.
Chiavi primarie: semplice vs composita
Ogni item è identificato univocamente da una chiave primaria, e DynamoDB supporta due tipi:
- Chiave primaria semplice (solo partition key): un attributo identifica univocamente ogni item.
- Chiave primaria composita (partition key + sort key): più item possono condividere la stessa partition key, mentre la sort key li distingue e ne definisce l'ordine all'interno di quella partizione.
Nella pratica, le chiavi composite abilitano pattern “raggruppati” come “tutti gli ordini di un cliente, dal più recente.”
Query vs scan (a livello alto)
Una Query legge item per chiave primaria (o chiave di un indice). Mira a una specifica partition key e può filtrare per intervalli di sort key—questa è la strada efficiente e preferita.
Una Scan scorre l'intera tabella (o indice) e poi filtra. È facile iniziare con uno Scan, ma di solito è più lento e costoso su larga scala.
Limiti da tenere a mente
Alcuni vincoli pratici:
- Dimensione massima item: 400 KB.
- Tipi di attributo: scalari (string/number/binary/boolean/null), set, liste e mappe.
- Gli attributi chiave devono essere scalari (no liste/mappe come partition o sort key).
Questi fondamenti preparano il terreno per gli access patterns, le scelte di indicizzazione e le caratteristiche di performance.
Il modello dati di DynamoDB: Key-Value e Document
DynamoDB è spesso descritto sia come key-value store sia come document database. È accurato, ma aiuta capire cosa significa nella progettazione quotidiana.
Accesso key-value vs item in stile document
Alla base, recuperi i dati per chiave. Fornisci i valori della chiave primaria e DynamoDB restituisce un singolo item. Questo lookup per chiave è ciò che fornisce storage con latenza prevedibile per molti carichi.
Allo stesso tempo, un item può contenere attributi annidati (mappe e liste), per cui sembra un document database: puoi memorizzare payload strutturati senza definire uno schema rigido in anticipo.
Modellare strutture gerarchiche simili a JSON negli item
Gli item si mappano naturalmente a dati simili a JSON:
- Mappe rappresentano oggetti (es.
profile.name,profile.address). - Liste rappresentano array (es. azioni recenti, tag).
Questo è adatto quando un'entità viene letta di solito per intero—come un profilo utente, un carrello o un bundle di configurazione.
Quando denormalizzare (e perché è comune)
DynamoDB non supporta join lato server. Se l'app deve ottenere “un ordine più le sue righe più lo stato di spedizione” in un'unica lettura, spesso si denormalizza: copiare alcuni attributi in più item o inserire piccole sotto-strutture direttamente dentro un item.
Compromessi rispetto alla normalizzazione relazionale
La denormalizzazione aumenta la complessità delle scritture e può generare fan-out negli aggiornamenti. Il guadagno è ridurre i round trip e velocizzare le letture—spesso il percorso critico nei sistemi scalabili.
Partition Key e Sort Key: progettare per gli access patterns
Le query più veloci in DynamoDB sono quelle che esprimi come “dammi questa partition” (e opzionalmente “in questa partition dammi questo intervallo”). Per questo la scelta della chiave riguarda soprattutto come leggi i dati, non solo come li memorizzi.
Partition key: distribuzione e letture prevedibili
La partition key determina quale partizione fisica memorizza un item. DynamoDB effettua l'hash di questo valore per distribuire dati e traffico. Se molte richieste si concentrano su un piccolo insieme di valori di partition key, puoi creare partizioni “calde” e raggiungere i limiti di throughput anche se la tabella è per il resto inattiva.
Buone partition key:
- Hanno alta cardinalità (molti valori distinti)
- Corrispondono a un pattern di accesso frequente (così le letture sono dirette, non filtrate)
- Evitano valori che diventano “popolari” (es. una costante come
"GLOBAL")
Sort key: query per range e entità raggruppate
Con una sort key, gli item che condividono la stessa partition key sono memorizzati insieme e ordinati dalla sort key. Questo abilita query efficienti:
- Query per range (
BETWEEN,begins_with) - Letture ordinate per tempo (ultime prime con scansione inversa)
- Raggruppamento di entità (più tipi di item sotto la stessa partition key)
Un pattern comune è comporre la sort key, ad esempio TYPE#id o TS#2025-12-22T10:00:00Z, per supportare più forme di query senza tabelle aggiuntive.
Mappare access patterns comuni alle chiavi
- Get by ID:
PK = USER#<id>(sempliceGetItem) - List by user:
PK = USER#<id>,SK begins_with ORDER#(oSK = CREATED_AT#...) - Time-series ranges:
PK = DEVICE#<id>,SK = TS#<timestamp>conBETWEENper finestre temporali
Come la scelta delle chiavi influenza performance e scalabilità
Se la tua partition key si allinea con le query a più alto volume e si distribuisce uniformemente, ottieni letture e scritture a bassa latenza in modo consistente. Se non lo fa, compenserai con scan, filtri o indici aggiuntivi—ognuno con costi e un rischio maggiore di hot key.
Indici secondari: GSI e LSI spiegati
Gli indici secondari forniscono a DynamoDB percorsi di query alternativi oltre alla chiave primaria della tabella. Invece di rimodellare la tabella base ogni volta che compare un nuovo access pattern, puoi aggiungere un indice che re-keya gli stessi item per una query diversa.
GSI vs LSI: qual è la differenza?
Un Global Secondary Index (GSI) ha la propria partition key (e opzionale sort key) che può essere completamente diversa da quella della tabella. È “globale” perché copre tutte le partizioni della tabella e può essere aggiunto o rimosso in qualsiasi momento. Usa un GSI quando hai bisogno di un access pattern che non si adatta al design originale—per esempio, interrogare ordini per customerId quando la tabella è indicizzata per orderId.
Un Local Secondary Index (LSI) condivide la stessa partition key della tabella base ma usa una different sort key. Le LSI devono essere definite alla creazione della tabella. Sono utili quando vuoi più ordini di lettura all'interno dello stesso gruppo di entità (stessa partition key), come recuperare gli ordini di un cliente ordinati per createdAt vs status.
Proiezioni: cosa viene copiato nell'indice
La proiezione determina quali attributi DynamoDB memorizza nell'indice:
- KEYS_ONLY: il più economico in termini di storage, ma spesso richiede una lettura aggiuntiva dalla tabella base.
- INCLUDE: copia solo gli attributi che ritorni comunemente.
- ALL: più semplice, ma può aumentare storage e costo di scrittura.
Amplificazione delle scritture (il conto nascosto)
Ogni scrittura nella tabella base può innescare scritture in uno o più indici. Più GSI e proiezioni ampie aumentano i costi di scrittura e il consumo di capacità. Pianifica gli indici intorno ad access patterns stabili e mantieni gli attributi proiettati al minimo quando possibile.
Modalità di capacità e comportamento di scaling
Lo scaling inizia con una scelta: On-Demand o Provisioned. Entrambe possono raggiungere throughput molto elevato, ma si comportano diversamente con traffico variabile.
On-Demand vs Provisioned: come scegliere
On-Demand è la più semplice: paghi per richiesta e DynamoDB si adatta automaticamente al carico. È adatta per traffico imprevedibile, prodotti in fase iniziale e workload bursty dove non vuoi gestire target di capacità.
Provisioned è pianificazione della capacità: specifichi throughput di lettura e scrittura (o lo autoscalo) e ottieni prezzi più prevedibili con uso costante. È spesso più economica per workload stabili e per team che possono prevedere la domanda.
Capacità di lettura/scrittura in pratica
Il throughput provisioned si misura in:
- RCU (Read Capacity Units): circa una lettura fortemente consistente al secondo per fino a 4 KB (o due letture eventualmente consistenti).
- WCU (Write Capacity Units): circa una scrittura al secondo per fino a 1 KB.
La dimensione reale degli item e il pattern di accesso determinano il costo reale: item più grandi, consistenza forte e scan possono consumare capacità rapidamente.
Nozioni base sull'auto scaling (e limiti)
L'auto scaling aggiusta RCUs/WCUs provisioned in base a target di utilizzo. Aiuta con la crescita graduale e cicli prevedibili, ma non è istantaneo. Picchi improvvisi possono ancora essere throttle se la capacità non scala abbastanza velocemente, e non risolve una partition key calda che concentra il traffico su una singola partizione.
DAX: caching per carichi di lettura intensi
DynamoDB Accelerator (DAX) è una cache in-memory che può ridurre la latenza di lettura e scaricare letture ripetute (es. pagine prodotto popolari, lookup di sessioni, leaderboard). È utile quando molti client richiedono ripetutamente gli stessi item; non aiuta per pattern write-heavy e non sostituisce un'attenta progettazione delle chiavi.
Consistenza, transazioni e correttezza
DynamoDB permette di bilanciare garanzie di lettura con latenza e costo, quindi è importante essere espliciti su cosa significa “corretto” per ogni operazione.
Letture eventualmente consistenti vs fortemente consistenti
Di default, GetItem e Query usano letture eventualmente consistenti: potresti vedere temporaneamente un valore precedente subito dopo una scrittura. Questo va bene per feed, cataloghi di prodotto e altre viste principalmente in lettura.
Con letture fortemente consistenti (opzione per letture dalla tabella base in una singola regione), DynamoDB garantisce che vedi l'ultima scrittura riconosciuta. La consistenza forte costa più capacità di lettura e può aumentare la latenza tail, quindi riservala per letture davvero critiche.
Quando la consistenza forte è importante
La consistenza forte è utile per letture che determinano azioni irreversibili:
- Controllare l'inventario disponibile prima di confermare un ordine
- Leggere un flag di autorizzazione prima di concedere accesso
- Recuperare lo stato corrente di un workflow prima di eseguire il passo successivo
Per i contatori, l'approccio più sicuro è tipicamente un aggiornamento atomico (es. UpdateItem con ADD) così gli incrementi non vanno persi.
Letture/scritture transazionali
Le transazioni di DynamoDB (TransactWriteItems, TransactGetItems) forniscono semantica ACID su fino a 25 item. Sono utili quando devi aggiornare più item insieme—come scrivere un ordine e riservare inventario—o far rispettare invarianti che non tollerano stati intermedi.
Idempotenza per retry sicuri
I retry sono normali nei sistemi distribuiti. Rendi le scritture idempotenti così i retry non duplicano gli effetti:
- Usa un client request token (idempotency key) memorizzato con il risultato
- Applica unicità con
ConditionExpression(es. “create solo se attribute_not_exists”) - Preferisci update atomici al pattern read-modify-write
La correttezza in DynamoDB riguarda soprattutto scegliere il livello di consistenza giusto e progettare le operazioni in modo che i retry non danneggino i dati.
Partizioni, hot keys e picchi di traffico
DynamoDB distribuisce i dati della tabella su più partizioni fisiche. Ogni partizione ha throughput finito per letture e scritture, oltre a un limite di spazio. La tua partition key determina dove vive un item; se troppe richieste puntano allo stesso valore di partition key (o a un piccolo insieme), quella partizione diventa il collo di bottiglia.
Perché succedono hot partitions
Le hot partitions sono di solito causate da scelte di chiave che concentrano il traffico: una partition key “globale” come USER#1, TENANT#default o STATUS#OPEN, o pattern ordinati per tempo dove tutti scrivono su “now” sotto una sola partition key.
Sintomi di hot keys e traffico non uniforme
Tipicamente vedrai:
- Throttling (
ProvisionedThroughputExceededException) per un sottoinsieme di chiavi - Latenza elevata e scossoni per pochi pattern di accesso mentre altri restano veloci
- Metriche CloudWatch che mostrano consumo di capacità non uniforme e picchi improvvisi
Tecniche di mitigazione
Progetta prima per la distribuzione, poi per la comodità della query:
- Design della chiave: assicurati chiavi di partizione ad alta cardinalità (es.
TENANT#<id>invece di una costante condivisa). - Write sharding: aggiungi un piccolo suffisso/prefisso random o hash come
ORDER#<id>#<shard>per distribuire su N shard, poi interroga gli shard quando necessario. - Time buckets: bucket per ora/giorno (
METRIC#2025-12-22T10) per evitare che tutte le scritture vadano all'ultimo item.
Gestire workload bursty
Per picchi imprevedibili, On-Demand può assorbire le esplosioni (nei limiti del servizio). In modalità Provisioned, usa auto scaling e implementa lato client exponential backoff con jitter sui throttling per evitare retry sincronizzati che amplificano il picco.
Pattern di modellazione dati per sistemi scalabili
La modellazione dati in DynamoDB parte dagli access patterns, non dagli schemi ER. Progetti le chiavi in modo che le query necessarie diventino veloci Query, mentre tutto il resto viene evitato o gestito in modo asincrono.
Single-table design (e perché piace ai team)
Il “single-table design” significa memorizzare più tipi di entità (utenti, ordini, messaggi) in una sola tabella e usare convenzioni chiave coerenti per recuperare dati correlati con una singola Query. Questo riduce i round trip tra entità e mantiene la latenza prevedibile.
Un approccio comune usa chiavi composite:
PKraggruppa una partizione logica (es.USER#123)SKordina gli item all'interno di quel gruppo (es.PROFILE,ORDER#2025-12-01,MSG#000123)
Questo permette di ottenere “tutto per un utente” o “solo gli ordini per un utente” scegliendo un prefisso di sort key.
Relazioni: adjacency list e molti-a-molti
Per relazioni tipo grafo, una adjacency list funziona bene: memorizza gli archi come item.
PK = USER#123,SK = FOLLOWS#USER#456
Per lookup inversi o veri molti-a-molti, aggiungi un item di arco invertito o proietta su un GSI, a seconda dei percorsi di lettura.
Time-series: bucket + sort key + TTL
Per eventi e metriche, evita partizioni illimitate usando bucket:
PK = DEVICE#9#2025-12-22(device + giorno)SK = TS#1734825600(timestamp)
Usa TTL per scadere automaticamente i punti vecchi e mantieni aggregati (rollup orari/giornalieri) come item separati per dashboard veloci.
Se vuoi un ripasso più approfondito sulle convenzioni di chiave, vedi /blog/partition-key-and-sort-key-design.
Streams e architetture event-driven
DynamoDB Streams è il feed di change data capture (CDC) integrato. Se abilitato su una tabella, ogni insert, update o delete produce un record di stream a cui i consumer downstream possono reagire—senza fare polling della tabella.
Fondamenti di DynamoDB Streams
Un record di stream contiene le chiavi e (opzionalmente) l'immagine vecchia e/o nuova dell'item, a seconda del stream view type scelto (solo chiavi, new image, old image, both). I record sono raggruppati in shard, che si leggono in sequenza.
Costruire workflow event-driven
Un setup comune è DynamoDB Streams → AWS Lambda, dove ogni batch di record attiva una funzione. Altri consumer sono possibili (consumer custom o piping verso sistemi di analytics/log).
Workflow tipici includono:
- Materialized views: scrivere una tabella denormalizzata per le letture quando la tabella sorgente cambia.
- Cache invalidation: scadere o aggiornare elementi in Redis/ElastiCache dopo le scritture.
- Audit logs: appende eventi immutabili di cambiamento a una tabella di audit o a uno store esterno.
Questo mantiene la tabella primaria ottimizzata per letture/scritture a bassa latenza mentre il lavoro derivato viene spostato a consumer asincroni.
Ordinamento, retry e correttezza
Gli Streams forniscono processamento ordinato per shard (che tipicamente si correla con la partition key), ma non c'è ordinamento globale tra tutte le chiavi. La consegna è at-least-once, quindi possono verificarsi duplicati.
Per gestirlo in sicurezza:
- Rendi gli handler idempotenti (es. upsert per chiave primaria, scritture condizionali o memorizza gli ID evento processati).
- Aspettati retry e fallimenti parziali; usa DLQ/destinazioni di fallback dove possibile.
- Mantieni gli effetti collaterali (email, pagamenti) dietro deduplica o salvaguardie transazionali.
Con queste garanzie in mente, Streams può trasformare DynamoDB in una solida spina dorsale per sistemi event-driven.
Affidabilità, backup e osservabilità
DynamoDB è progettato per alta disponibilità distribuendo i dati su più Availability Zone in una regione. Per la maggior parte dei team, i benefici pratici in affidabilità arrivano da una chiara strategia di backup, comprendere le opzioni di replica e monitorare le metriche giuste.
Backup: on-demand vs point-in-time recovery
On-demand backups sono snapshot manuali (o automatizzati) che fai per un punto di ripristino noto—prima di una migrazione, dopo un rilascio o prima di un grande backfill. Sono ottimi per momenti “segnalibro”.
Point-in-time recovery (PITR) cattura continuamente i cambiamenti così puoi ripristinare la tabella a qualsiasi secondo entro la finestra di retention. PITR è la rete di sicurezza per delete accidentali, deploy errati o scritture malformate.
Replica e opzioni multi-region
Se hai bisogno di resilienza multi-region o letture a bassa latenza vicino agli utenti, Global Tables replicano i dati tra regioni selezionate. Semplificano il piano di failover, ma introducono ritardi di replica cross-region e considerazioni sulla risoluzione dei conflitti—mantieni chiari i pattern di scrittura e la proprietà degli item.
Monitoraggio essenziale
Al minimo, allerta su:
- Latenza (p95/p99) per letture e scritture
- Richieste throttle e errori di sistema
- Capacità consumata (e margine rispetto al provisioned)
Questi segnali solitamente rivelano problemi di hot-partition, capacità insufficiente o pattern di accesso inaspettati.
Playbook per incidenti
Per throttling, identifica prima il pattern di accesso che lo causa, poi mitiga temporaneamente passando a on-demand o aumentando la capacità provisioned, e considera lo sharding delle chiavi calde.
Per outage parziali o errori elevati, riduci il raggio d'azione: disabilita traffico non critico, riprova con backoff jitterato e degrada eleggibilmente (per esempio servendo letture cache) finché la tabella non si stabilizza.
Sicurezza e controllo degli accessi
La sicurezza in DynamoDB riguarda soprattutto chi può chiamare quali API, da dove e su quali chiavi. Poiché le tabelle possono contenere molti tipi di entità (e a volte più tenant), il controllo degli accessi va progettato insieme al modello dati.
Permessi IAM: least privilege per default
Inizia con policy IAM basate sull'identità che limitino le azioni (es. dynamodb:GetItem, Query, PutItem) al minimo e le scoprano su specifici ARN di tabella.
Per un controllo più fine, usa dynamodb:LeadingKeys per limitare l'accesso ai valori della partition key—utile quando un servizio o tenant deve leggere/scrivere solo nel proprio keyspace.
Crittografia: cosa verificare
DynamoDB cifra i dati a riposo di default usando chiavi AWS owned o una KMS gestita dal cliente. Se hai requisiti di compliance, verifica:
- La tabella è configurata con la KMS desiderata
- Il ruolo chiamante ha i permessi KMS richiesti (e niente in più)
Per la crittografia in transito, assicurati che i client usino HTTPS (gli SDK AWS lo fanno di default). Se termini TLS in un proxy, conferma che il salto tra proxy e DynamoDB sia comunque cifrato.
Controlli di rete: ridurre i percorsi di esfiltrazione
Usa un VPC Gateway Endpoint per DynamoDB così il traffico resta sulla rete AWS e puoi applicare policy dell'endpoint per limitare l'accesso. Abbina questo con controlli di egress (NACL, security group e routing) per evitare percorsi “qualsiasi possa raggiungere Internet pubblico”.
Design multi-tenant e pattern di isolamento
Per tabelle condivise, includi un identificatore tenant nella partition key (es. TENANT#<id>), poi applica isolamento tenant con condizioni IAM su dynamodb:LeadingKeys.
Se serve isolamento più forte, considera tabelle separate per tenant o per ambiente, e riserva il design a tabella condivisa per i casi in cui semplicità operativa e costi giustifichino un raggio d'azione più ampio.
Ottimizzazione dei costi per DynamoDB
DynamoDB è spesso “economico se sei preciso, costoso se sei vago.” I costi seguono i tuoi access patterns, quindi la miglior ottimizzazione parte dal rendere espliciti quei pattern.
Conosci i driver dei costi
La bolletta è modellata principalmente da:
- Letture e scritture (RCU/WCU in provisioned, unità di richiesta in on-demand)
- Storage (dati della tabella e dimensione item)
- Indici secondari (ogni GSI ha costi di scrittura e storage propri)
- Streams (letture sui record di stream e consumer downstream)
Una sorpresa comune: ogni scrittura a una tabella è anche una scrittura in ogni GSI interessato, quindi “solo un indice in più” può moltiplicare il costo delle scritture.
Progetta le chiavi per evitare sprechi
Un buon design delle chiavi riduce la necessità di operazioni costose. Se spesso usi Scan, stai pagando per leggere dati che poi scarti.
Preferisci:
Queryper partition key (e opzionalmente condizioni sulla sort key)- Proiezioni strette nei tuoi GSI (proietta solo gli attributi necessari)
Se un access pattern è raro, considera servirlo tramite una tabella separata, un job ETL o un modello di lettura cache anziché un GSI permanente.
Controlla lo storage con TTL e lifecycle
Usa TTL per cancellare automaticamente item a breve vita (sessioni, token temporanei, stato intermedio di workflow). Questo riduce lo storage e mantiene più piccoli gli indici nel tempo.
Per dati append-only (eventi, log), combina TTL con design della sort key che permettono di interrogare “solo i recenti”, così non tocchi la storia fredda di continuo.
Right-size della capacità ed evitare picchi accidentali
In modalità provisioned, imposta baseline conservative e scala con auto scaling basato su metriche reali. In on-demand, controlla pattern inefficienti (item grandi, client chatty) che aumentano le richieste.
Considera Scan come ultima risorsa—quando serve davvero processare l'intera tabella, schedulalo in fascia off-peak o eseguilo come batch controllato con paginazione e backoff.
Quando scegliere DynamoDB (e quando no)
DynamoDB brilla quando la tua applicazione può essere espressa come un insieme di access patterns ben definiti e hai bisogno di latenza costante e bassa a grande scala. Se puoi descrivere le tue letture e scritture in anticipo (per partition key, sort key e un piccolo numero di indici), spesso è uno dei modi più semplici per gestire uno store dati altamente disponibile.
Ideale per
DynamoDB è una scelta forte quando hai:
- Query prevedibili (recuperare profilo utente, elencare ordini di un utente per tempo, caricare una sessione per ID)
- Alto throughput di scrittura o traffico bursty che non vuoi gestire manualmente
- Bisogno di scalare orizzontalmente senza gestire server
- Progetti event-driven che usano Streams per attivare lavoro downstream
Quando considerare alternative
Cerca altrove se i tuoi requisiti principali includono:
- Join complesse tra molte entità o frequenti traversate di relazioni
- Query ad hoc e analisi che cambiano settimanalmente (group-by, filtri esplorativi)
- Ricerca testuale pesante e ranking di rilevanza senza un indice esterno
Approcci ibridi che funzionano bene
Molti team usano DynamoDB per le letture/scritture “calde” e aggiungono:
- S3 + Athena per analytics e reporting storico
- OpenSearch (o simili) per ricerca full-text e faceting
- Un layer di cache quando certe chiavi sono estremamente read-heavy
Nota su prototipazione: accorcia il percorso da modello ad app
Se stai validando access patterns e convenzioni single-table, la velocità è importante. Alcuni team prototipano il servizio e l'interfaccia in Koder.ai (una piattaforma vibe-coding che costruisce app web, server e mobile da chat) e poi iterano sul design delle chiavi DynamoDB man mano che emergono query reali. Anche se il backend di produzione differisce, i prototipi end-to-end rapidi aiutano a rivelare quali query dovrebbero essere Query e quali accidentalmente diventerebbero costosi Scan.
Domande frequenti
Cos'è DynamoDB e quando è una buona scelta?
DynamoDB è un database NoSQL completamente gestito su AWS progettato per letture/scritture con latenza costante e bassa su scala molto ampia. I team lo usano quando possono definire access patterns basati su chiavi (recuperare per ID, elencare per proprietario, query per intervalli temporali) e vogliono evitare di gestire l'infrastruttura del database.
È particolarmente comune in microservizi, applicazioni serverless e sistemi event-driven.
Cosa sono tabelle, item e attributi in DynamoDB?
Una tabella contiene item (simili a righe). Ogni item è un insieme flessibile di attributi (simili a colonne) e può includere dati annidati.
DynamoDB funziona bene quando una richiesta tipica ha bisogno della “entità completa”, perché gli item possono contenere mappe e liste (strutture simili a JSON).
Qual è la differenza tra una chiave primaria semplice e una chiave primaria composita?
Una chiave di partizione da sola identifica univocamente un item (chiave primaria semplice). Una chiave di partizione + chiave di ordinamento (chiave composita) permette a più item di condividere la stessa chiave di partizione pur rimanendo identificabili in modo univoco e ordinati dalla chiave di ordinamento.
Le chiavi composite abilitano pattern come:
- “Tutti gli ordini di un cliente”
- “Eventi per un dispositivo tra timestamp”
Quando dovrei usare Query e quando Scan?
Usa Query quando puoi specificare la chiave di partizione (e opzionalmente una condizione sulla chiave di ordinamento). È il percorso veloce e scalabile.
Usa Scan solo quando devi davvero leggere tutto: scansiona l'intera tabella o indice e filtra dopo, ed è di solito più lento e costoso.
Se scansionate spesso, è un segnale che la vostra progettazione di chiavi o indici va rivista.
Cosa sono GSI e LSI, e come scelgo?
Gli indici secondari forniscono percorsi di query alternativi.
- GSI (Global Secondary Index): può usare una chiave di partizione diversa (e opzionalmente una chiave di ordinamento) rispetto alla tabella base; può essere aggiunto successivamente.
- LSI (Local Secondary Index): condivide la stessa chiave di partizione della tabella base ma usa una chiave di ordinamento diversa; deve essere definito alla creazione della tabella.
Gli indici aumentano il costo delle scritture perché le scritture vengono replicate anche nell'indice.
Come scelgo tra On-Demand e Provisioned?
Scegli On-Demand se il traffico è imprevedibile, bursty, o non vuoi gestire la capacità. Paghi per richiesta.
Scegli Provisioned se l'utilizzo è stabile/prevedibile e vuoi costi più controllati. Abbinalo all'auto scaling, ma ricorda che potrebbe non reagire istantaneamente a picchi improvvisi.
Quali opzioni di consistenza offre DynamoDB e quando sono importanti?
Per impostazione predefinita le letture sono eventualmente consistenti, quindi potresti leggere un valore obsoleto subito dopo una scrittura.
Usa letture fortemente consistenti (quando disponibili) per controlli critici che devono essere aggiornati, come gate di autorizzazione o transizioni di stato in un workflow.
Per la correttezza sotto concorrenza, preferisci update atomici (es. UpdateItem con ADD) piuttosto che cicli di read-modify-write.
Quando dovrei usare le transazioni in DynamoDB?
Le transazioni (TransactWriteItems, TransactGetItems) offrono garanzie ACID su un massimo di 25 item.
Usale quando devi aggiornare più item insieme (es. creare un ordine e riservare inventario) o far rispettare invarianti che non possono tollerare stati intermedi.
Hanno costi e latenza maggiori, quindi riservale ai flussi che le richiedono davvero.
Cosa sono hot keys/partitions e come posso evitarle?
Le hot partitions si verificano quando troppe richieste mirano allo stesso valore di chiave di partizione (o a un piccolo insieme di valori), causando throttling anche se la tabella è poco usata nel complesso.
Mitigazioni comuni:
- Scegliere chiavi di partizione con alta cardinalità
- Applicare write sharding (suffisso/prefisso random/hash) per distribuire le scritture
- Usare time buckets per dati time-series
- Implementare exponential backoff con jitter sui retry
Come supportano le DynamoDB Streams le architetture event-driven?
Attiva DynamoDB Streams per ottenere un feed di cambiamenti su insert, update e delete. Un pattern comune è Streams → Lambda per attivare lavori downstream.
Garanzie importanti da progettare per:
- L'ordinamento è per shard (non globale)
- La consegna è at-least-once (possono verificarsi duplicati)
Rendi i consumer idempotenti (upsert per chiave, scritture condizionali o tracciamento degli ID eventi processati).