Denormalizzazione in DynamoDB
Venendo da SQL, la denormalizzazione suona come un peccato — dati duplicati, nessuna singola fonte di verità. In DynamoDB è proprio il punto. Non ci sono join, quindi copi i dati correlati sull'Item che ne ha bisogno e li rileggi in un colpo solo.
Cos'è la denormalizzazione in DynamoDB?
La denormalizzazione in DynamoDB significa copiare i dati correlati sull'Item che li legge, così una singola query restituisce tutto in un colpo solo. Poiché DynamoDB non ha join, fai il pre-join in fase di scrittura invece di cucire insieme le tabelle in fase di lettura. Lo scambio è l'obsolescenza — duplica solo valori che cambiano raramente.
- Niente join significa pre-join in fase di scrittura. Memorizza il valore correlato sull' Item che lo legge, così una query non ha mai bisogno di un secondo lookup.
- Due varianti. Incorpora dati annidati in un attributo complesso su un Item, oppure duplica un valore su molti Item.
- Il footgun è l'obsolescenza. Quando la sorgente cambia, ogni copia è sbagliata finché non propaghi l'aggiornamento. Duplica solo valori che cambiano raramente.
- Compri letture, non scritture. Scambi scritture più numerose (e più attente) per letture economiche a singola richiesta.
Perché non ci sono join su cui ripiegare
Un JOIN relazionale riassembla righe normalizzate in fase di lettura. DynamoDB non ha
join — una Query legge una item collection e restituisce esattamente ciò che è memorizzato
lì. Nulla cuce insieme due tabelle per te.
Quindi i dati devono essere già modellati per la lettura. Se una schermata ha bisogno di un post e del nome del suo autore, quel nome deve vivere da qualche parte che la lettura del post già tocca. Il paper Amazon Dynamo del 2007 ha reso esplicito questo scambio: rinunciare alle feature relazionali per ottenere letture prevedibili a singola cifra di millisecondi su larga scala.
Pattern 1 — incorpora con un attributo complesso
Gli attributi DynamoDB possono contenere map e list annidate, non solo scalari. Quindi una forma comune di denormalizzazione è infilare un oggetto figlio direttamente dentro il suo Item genitore invece di dargli un Item proprio.
Un post con i suoi tag e un piccolo snapshot dell'autore, tutto su un Item:
| PK | SK | author | tags |
|---|---|---|---|
| POST#9f3 | META | {id: U#12, name: "Mara Vance"} | ["dynamodb","aws"] |
Un solo GetItem restituisce il post, i tag e il blocco autore insieme. Nessuna
seconda lettura. Ottimo per dati che sono posseduti dal genitore e limitati in
dimensione — una manciata di tag, uno snapshot dell'autore.
Il limite da rispettare: un singolo Item DynamoDB raggiunge il massimo a 400 KB, nomi e valori degli attributi inclusi (Service Quotas). Incorpora una list illimitata (ogni commento su un post virale) e lo sforerai.
Pattern 2 — duplica un valore su più Item
Il caso del blog è quello da manuale. Elenchi i post e vuoi che ogni riga mostri il nome visualizzato dell'autore — ma non vuoi una seconda lettura per post per recuperarlo.
Così scrivi il nome dell'autore su ogni Item post quando il post viene creato:
| PK | SK | authorId | authorName | title |
|---|---|---|---|---|
| POST#9f3 | META | U#12 | "Mara Vance" | "Modeling 1:N" |
| POST#a71 | META | U#12 | "Mara Vance" | "Sparse GSIs" |
| POST#b04 | META | U#88 | "Lio Tan" | "Query vs Scan" |
Ora Query PK begins_with "POST#" (o un GSI sui post) renderizza l'intero elenco —
titolo e autore — senza lookup per riga. Il nome dell'autore è denormalizzato:
la copia canonica vive su USER#12, e ogni post porta la propria copia.
Lo scambio è proprio lì. Hai trasformato una lettura N+1 in una sola lettura, al costo di
tenere "Mara Vance" in N+1 posti.
Incorpora vs. duplica — quale
| Incorpora (attributo complesso) | Duplica (copia su più Item) | |
|---|---|---|
| Forma | figlio annidato nel genitore | stesso valore su molti Item |
| Ideale per | dati limitati, posseduti dal genitore | un valore condiviso che molti Item mostrano |
| Lettura | un GetItem | una Query |
| Costo aggiornamento | riscrivi il singolo Item genitore | propaga a ogni copia |
| Rischio dimensione | tetto Item da 400 KB | nessuno per Item |
Reggiungi a incorpora quando il figlio compare solo insieme al suo genitore. Reggiungi a duplica quando molti Item indipendenti devono mostrare lo stesso valore condiviso.
Il footgun: copie obsolete
Ecco la parte che morde. Mara si rinomina in "Mara V.". Aggiorni
USER#12. Ogni Item post dice ancora "Mara Vance" finché non vai a sistemarli.
Quindi aggiornare un valore duplicato è una scrittura fan-out, non una riga sola. Interroghi ogni Item interessato e riscrivi ognuno — idealmente protetto così tocchi solo le righe che ancora contengono il vecchio valore:
UPDATE POST#9f3
SET authorName = "Mara V."
WHERE authorName = "Mara Vance"
Puoi comporre quel SET condizionale su authorName nell'
Expression Builder e copiare l'
UpdateExpression e il ConditionExpression generati direttamente nel tuo codice.
Il fan-out stesso è una scrittura per Item: interroga i post dell'autore, poi emetti gli aggiornamenti. La sequenza:
Il costo del duplicare i dati: ogni modifica alla sorgente è una query più una scrittura per copia.
Ecco perché la regola è duplica solo valori che cambiano raramente. Un nome visualizzato, un tier di piano, un'etichetta di categoria — bene. Un contatore live o un campo modificato di frequente — no; il fan-out ti divorerà vivo.
Quando la normalizzazione vince ancora
Se un valore cambia spesso, o un Item viene letto con pattern davvero imprevedibili, tienilo normalizzato e accetta la lettura extra. La denormalizzazione è un' ottimizzazione per pattern di accesso noti, a prevalenza letture — non un default da applicare ovunque. Pre-join le letture che esegui davvero, e lascia stare il resto.
Per decidere dove vivono questi attributi duplicati, modella prima i pattern di accesso — vedi single-table design e, per il lato letture dello scambio, Query vs Scan.
Scarica DynoTable per ispezionare una tabella denormalizzata, individuare quali copie sono andate alla deriva ed eseguire l'aggiornamento fan-out sui tuoi dati.