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

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:
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.
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.
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.
Ogni item è identificato univocamente da una chiave primaria, e DynamoDB supporta due tipi:
Nella pratica, le chiavi composite abilitano pattern “raggruppati” come “tutti gli ordini di un cliente, dal più recente.”
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.
Alcuni vincoli pratici:
Questi fondamenti preparano il terreno per gli access patterns, le scelte di indicizzazione e le caratteristiche di performance.
DynamoDB è spesso descritto sia come key-value store sia come document database. È accurato, ma aiuta capire cosa significa nella progettazione quotidiana.
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.
Gli item si mappano naturalmente a dati simili a JSON:
profile.name, profile.address).Questo è adatto quando un'entità viene letta di solito per intero—come un profilo utente, un carrello o un bundle di configurazione.
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.
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.
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.
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:
"GLOBAL")Con una sort key, gli item che condividono la stessa partition key sono memorizzati insieme e ordinati dalla sort key. Questo abilita query efficienti:
BETWEEN, begins_with)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.
PK = USER#<id> (semplice GetItem)PK = USER#<id>, SK begins_with ORDER# (o SK = CREATED_AT#...)PK = DEVICE#<id>, SK = TS#<timestamp> con BETWEEN per finestre temporaliSe 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.
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.
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.
La proiezione determina quali attributi DynamoDB memorizza nell'indice:
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.
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 è 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.
Il throughput provisioned si misura in:
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.
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.
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.
DynamoDB permette di bilanciare garanzie di lettura con latenza e costo, quindi è importante essere espliciti su cosa significa “corretto” per ogni operazione.
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.
La consistenza forte è utile per letture che determinano azioni irreversibili:
Per i contatori, l'approccio più sicuro è tipicamente un aggiornamento atomico (es. UpdateItem con ADD) così gli incrementi non vanno persi.
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.
I retry sono normali nei sistemi distribuiti. Rendi le scritture idempotenti così i retry non duplicano gli effetti:
ConditionExpression (es. “create solo se attribute_not_exists”)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.
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.
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.
Tipicamente vedrai:
ProvisionedThroughputExceededException) per un sottoinsieme di chiaviProgetta prima per la distribuzione, poi per la comodità della query:
TENANT#<id> invece di una costante condivisa).ORDER#<id>#<shard> per distribuire su N shard, poi interroga gli shard quando necessario.METRIC#2025-12-22T10) per evitare che tutte le scritture vadano all'ultimo item.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.
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.
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:
PK raggruppa una partizione logica (es. USER#123)SK ordina 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.
Per relazioni tipo grafo, una adjacency list funziona bene: memorizza gli archi come item.
PK = USER#123, SK = FOLLOWS#USER#456Per lookup inversi o veri molti-a-molti, aggiungi un item di arco invertito o proietta su un GSI, a seconda dei percorsi di lettura.
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.
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.
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.
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:
Questo mantiene la tabella primaria ottimizzata per letture/scritture a bassa latenza mentre il lavoro derivato viene spostato a consumer asincroni.
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:
Con queste garanzie in mente, Streams può trasformare DynamoDB in una solida spina dorsale per sistemi event-driven.
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.
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.
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.
Al minimo, allerta su:
Questi segnali solitamente rivelano problemi di hot-partition, capacità insufficiente o pattern di accesso inaspettati.
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.
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.
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.
DynamoDB cifra i dati a riposo di default usando chiavi AWS owned o una KMS gestita dal cliente. Se hai requisiti di compliance, verifica:
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.
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”.
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.
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.
La bolletta è modellata principalmente da:
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.
Un buon design delle chiavi riduce la necessità di operazioni costose. Se spesso usi Scan, stai pagando per leggere dati che poi scarti.
Preferisci:
Query per partition key (e opzionalmente condizioni sulla sort key)Se un access pattern è raro, considera servirlo tramite una tabella separata, un job ETL o un modello di lettura cache anziché un GSI permanente.
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.
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.
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.
DynamoDB è una scelta forte quando hai:
Cerca altrove se i tuoi requisiti principali includono:
Molti team usano DynamoDB per le letture/scritture “calde” e aggiungono:
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.
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.
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).
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:
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.
Gli indici secondari forniscono percorsi di query alternativi.
Gli indici aumentano il costo delle scritture perché le scritture vengono replicate anche nell'indice.
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.
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.
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.
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:
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:
Rendi i consumer (upsert per chiave, scritture condizionali o tracciamento degli ID eventi processati).