Intermedio6 min di lettura

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:

PKSKauthortags
POST#9f3META{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:

PKSKauthorIdauthorNametitle
POST#9f3METAU#12"Mara Vance""Modeling 1:N"
POST#a71METAU#12"Mara Vance""Sparse GSIs"
POST#b04METAU#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)
Formafiglio annidato nel genitorestesso valore su molti Item
Ideale perdati limitati, posseduti dal genitoreun valore condiviso che molti Item mostrano
Letturaun GetItemuna Query
Costo aggiornamentoriscrivi il singolo Item genitorepropaga a ogni copia
Rischio dimensionetetto Item da 400 KBnessuno 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:

"DynamoDB"App"DynamoDB"App"Aggiorna nome USER"Interroga i post dell'autore""POST"Aggiorna ogni authorName"

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.

Aggiornato