Intermedio7 min di lettura

Relazioni uno-a-molti in DynamoDB

Un piano di controllo SaaS ha quasi sempre una gerarchia di contenimento: un workspace possiede molti progetti. In SQL metteresti una chiave esterna workspace_id sulla tabella dei progetti e faresti un JOIN.

DynamoDB non ha join né chiavi esterne, quindi la relazione deve vivere nel key schema stesso. Fatto bene, "carica un workspace e ogni progetto al suo interno" diventa una singola Query invece di una lettura più una scansione di follow-up.

Come si modella una relazione uno-a-molti in DynamoDB?

Assegna al genitore e a tutti i suoi figli la stessa così da condividere un'unica , poi differenziali con la sort key. DynamoDB non ha join né chiavi esterne, quindi la relazione vive nel key schema stesso. Caricare un genitore più tutti i figli diventa così una singola Query invece di un join.

  • Modella le letture, non le entità. La relazione uno-a-molti esiste solo per servire "elenca i progetti di un workspace" — modella le chiavi attorno a quella query.
  • Codifica il genitore nella partition key del figlio. Dai al workspace e a tutti i suoi progetti lo stesso valore di partition key così da farli atterrare in un'unica item collection.
  • Allora la lettura dell'elenco è una sola Query. Il genitore più un numero arbitrario di figli tornano in una singola chiamata fatturata — niente join, niente secondo round trip.
  • Attenzione alla hot partition. Un singolo tenant enorme concentra tutto il suo traffico su una partizione; un workspace gigantesco potrebbe richiedere una chiave con sharding e una lettura fan-out.

Prima l'access pattern

Il modeling in DynamoDB è access-pattern-first, non entity-first — la stessa disciplina dietro il single-table design. Prima di scegliere una chiave, scrivi le letture che l'app emette davvero:

  • Ottieni le impostazioni di un workspace.
  • Elenca ogni progetto in un workspace, dal più recente.
  • Ottieni un progetto specifico per id.

La relazione "un workspace, molti progetti" conta solo per via della lettura #2. Se non avessi mai bisogno di elencare insieme i progetti di un workspace, non modelleresti affatto la relazione — memorizzeresti i progetti in modo indipendente.

Quindi la domanda non è mai "come rappresento uno-a-molti?" in astratto. È "quali query deve servire questa relazione?" Rispondi a questo, poi modella le chiavi attorno.

Perché una chiave esterna non aiuta qui

In DynamoDB ogni GetItem e Query punta a una partition key, e il servizio calcola l'hash di quella chiave per individuare la partizione che contiene l'item.

AWS lo dice direttamente nella documentazione Core Components: il valore della partition key è l'input di una funzione hash interna che decide dove risiedono i dati.

Quel posizionamento basato su hash è l'eredità del paper originale del 2007 Dynamo: Amazon's Highly Available Key-value Store, dove l'hashing consistente distribuisce le chiavi tra i nodi.

Un semplice attributo workspace_id su un item progetto è invisibile a quel meccanismo — DynamoDB non può "seguirlo".

Per recuperare item correlati in una sola richiesta, l'identità del genitore deve essere codificata nella partition key del progetto, così che tutti gli item di un workspace facciano hash sulla stessa partizione e una sola Query possa spazzarli via.

Esempio pratico: workspace e progetti

Usa un key schema generico e sovraccaricato. Chiama la partition key EntityRef e la sort key Detail. L'identità del workspace va in EntityRef per sia l'item workspace sia ogni progetto sotto di esso:

EntityRefDetailattributes
WS#acmeMETAdisplayName, region, seatLimit
WS#acmePROJ#2026-0007title, status, createdBy
WS#acmePROJ#2026-0042title, status, createdBy
WS#acmePROJ#2026-0118title, status, createdBy
WS#globexMETAdisplayName, region, seatLimit
WS#globexPROJ#2026-0009title, status, createdBy

Il workspace e tutti i suoi progetti condividono EntityRef = "WS#acme", quindi formano un'unica item collection che vive insieme su una partizione.

La sort key Detail li separa: META è il record del workspace, e ogni progetto porta un prefisso PROJ# con un id ordinato nel tempo e con zero-padding, così i progetti si ordinano naturalmente.

Visivamente, il genitore e i suoi figli si impilano dentro una partizione, ordinati per sort key:

Partizione: EntityRef = WS#acmeMETA impostazioni workspacePROJ#2026-0007PROJ#2026-0042PROJ#2026-0118

Una sola Query su EntityRef = "WS#acme" spazza l'intera pila — il genitore più ogni figlio — in una singola lettura.

Ora i tre access pattern collassano ciascuno in una sola chiamata:

  • Impostazioni del workspaceGetItem(EntityRef="WS#acme", Detail="META").
  • Elenca i progetti dal più recenteQuery(EntityRef="WS#acme") con Detail begins_with "PROJ#", eseguita in ordine decrescente (ScanIndexForward = false).
  • Un progettoGetItem(EntityRef="WS#acme", Detail="PROJ#2026-0042").

La seconda è tutto il punto: il genitore e un numero arbitrario di figli tornano in una sola Query fatturata, niente join e niente secondo round trip. È la mossa che non puoi fare con un attributo chiave esterna e uno Scan.

Scrivere a mano quella condizione begins_with è scomodo — la sintassi della key-condition e della projection-expression morde.

Il DynamoDB Expression Builder genera la KeyConditionExpression, le mappe di placeholder #name/:value e uno snippet SDK pronto all'uso, così non devi combattere con la grammatica:

KeyConditionExpression     "#er = :er AND begins_with(#d, :p)"
ExpressionAttributeNames   { "#er": "EntityRef", "#d": "Detail" }
ExpressionAttributeValues  { ":er": "WS#acme", ":p": "PROJ#" }

Ispeziona l'item collection in DynoTable

Il vantaggio di questo layout è visivo: ogni riga che condivide un EntityRef è il workspace più i suoi figli, l'uno accanto all'altro.

DynoTable li raggruppa così che tu veda la relazione uno-a-molti come un unico blocco contiguo invece di doverla indovinare tra tabelle separate.

L'item META del workspace e i suoi figli PROJ# raggruppati come un'unica item collection nella vista tabella di DynoTable.
L'item META del workspace e i suoi figli PROJ# raggruppati come un'unica item collection nella vista tabella di DynoTable.

Trappole e la forma alternativa

Alcune cose a cui fare attenzione:

  • Hot partition. Ogni item di un workspace vive su una sola partizione, quindi un singolo tenant molto grande o molto attivo concentra il traffico. Il comportamento di capacità adattiva descritto da AWS assorbe uno sbilanciamento moderato, ma un workspace con milioni di progetti potrebbe richiedere una chiave con sharding (es. WS#acme#01 … #10) e una lettura fan-out.
  • Dimensione dell'item collection. Con un local secondary index, l'item collection di una singola partizione è limitata a 10 GB; senza un LSI non c'è tale limite. Se stai valutando i tipi di indice qui, vedi GSI vs LSI.
  • Affidati a Query, mai a Scan. Tutto il design esiste così da poter fare Query su una partizione. Ripiegare su uno Scan filtrato per "trovare i progetti di un workspace" butta via il modello e legge l'intera tabella — la trappola trattata in Query vs Scan.

Se hai davvero bisogno di elencare i progetti tra workspace (diciamo, tutti i progetti con status = ACTIVE globalmente), la tabella base non può rispondere — la sua partition key è limitata al workspace.

Quello è un lavoro per un secondary index che ri-partiziona i progetti su un attributo diverso, non per rimodellare questa relazione.

Prossimi passi

Modella gli access pattern, codifica il genitore nella partition key del figlio, e la lettura uno-a-molti è una singola Query. Costruisci e convalida la key condition con il DynamoDB Expression Builder.

Poi scarica DynoTable per caricare questo schema, sfogliare dal vivo l'item collection workspace→progetti e confermare che ogni query faccia esattamente una lettura.

Aggiornato