Avanzato8 min di lettura

Il pattern Adjacency List di DynamoDB

Un grafo non è altro che nodi e archi, e il pattern adjacency list memorizza entrambi come semplici Item in una tabella. Ogni arco diventa una riga la cui chiave di partizione è il nodo sorgente e la cui sort key è il target. Interrogare una partizione elenca ogni vicino — il sostituto DynamoDB per un JOIN su una tabella di join.

Che cos'è il pattern adjacency list di DynamoDB?

Il pattern adjacency list modella un grafo come Item-arco in una singola tabella: ogni relazione (A segue B) è una riga con la sorgente sulla chiave di partizione e il target sulla sort key. Interrogare una partizione elenca ogni vicino, e un GSI invertito ribalta la relazione — niente join, niente scan, entrambe le direzioni in una sola query.

  • Gli archi sono Item. Modella ogni relazione (l'utente A segue l'utente B) come il proprio Item con chiave: sorgente sulla chiave di partizione, target sulla sort key.
  • Una direzione è gratis; l'altra ha bisogno di un GSI. La tabella base risponde a "chi segue A?" (chi A segue). Un indice invertito risponde a "chi sono i follower di A?".
  • Niente join, niente scan. Entrambe le direzioni sono una sola Query su una partizione nota — mai uno Scan sull'intera tabella.
  • È il primitivo molti-a-molti. Follow, membership, like, amicizie — ogni grafo in cui un'entità si connette a molte altre rientra in questa forma.

Inquadralo come pattern di accesso

Venendo da SQL, un grafo di follow è una tabella di join: follows(follower_id, followee_id). Per elencare i follower di qualcuno indicizzi una colonna; per elencare chi seguono indicizzi l'altra. DynamoDB non ha join, quindi progetti le chiavi per servire ogni lettura direttamente.

Scrivi prima le letture. Per un grafo di follow social:

  • Chi segue l'utente A? (la sua lista following)
  • Chi segue A? (la sua lista followers)
  • A segue B? (un singolo point lookup)

Le chiavi esistono solo per rispondere a quell'elenco. Azzeccale e ogni lettura è una sola Query o GetItem.

Modella gli archi come Item

Usa nomi di chiave generici così la tabella può contenere più di un tipo di entità, e codifica il tipo di nodo nel valore. Un arco di follow appare così:

PKSKcreatedAtedgeType
ACTOR#aliceTARGET#bob1718900000FOLLOWS
ACTOR#aliceTARGET#carol1718900100FOLLOWS
ACTOR#daveTARGET#bob1718900200FOLLOWS

PK = ACTOR#alice è la sorgente dell'arco; SK = TARGET#bob è chi lei segue. Una sola Query PK = "ACTOR#alice" restituisce ogni account che Alice segue in una singola lettura fatturata — la sua intera lista following, senza join.

Ogni arco viene scritto una volta, nella direzione "chi seguo". La direzione inversa ("chi mi segue") è la parte a cui la tabella base non può rispondere — ancora.

Attraversa l'altra direzione con un GSI

La tabella base ha chiave sorgente-prima, quindi non può rispondere a "chi segue Bob?" senza fare uno scan. Aggiungi un global secondary index che inverte le chiavi: proietta il target sulla chiave di partizione dell'indice e la sorgente sulla sort key dell'indice.

GSI1PKGSI1SK(base item)
TARGET#bobACTOR#aliceACTOR#alice → TARGET#bob
TARGET#bobACTOR#daveACTOR#dave→ TARGET#bob
TARGET#carolACTOR#aliceACTOR#alice → TARGET#carol

Ora Query GSI1 WHERE GSI1PK = "TARGET#bob" elenca tutti quelli che seguono Bob — alice e dave — in una sola lettura. Lo stesso Item-arco serve entrambe le direzioni: la tabella base è following, l'indice è followers. Scrivi ogni arco una volta e ottieni entrambe le query gratis.

Tabella base: PK = ACTOR#aliceSK: TARGET#bobSK: TARGET#carolQuery base chi segue alice
GSI1: GSI1PK = TARGET#bobGSI1SK: ACTOR#aliceGSI1SK: ACTOR#daveQuery GSI1 chi segue bob

Questo è esattamente il pattern che AWS documenta nella sua guida alle best-practice di DynamoDB per modellare relazioni molti-a-molti e dati a grafo — memorizza gli archi come Item, poi usa un GSI per invertire la relazione.

Controlla un singolo arco a basso costo

"Alice segue Bob?" non ha bisogno di nessuna delle due liste. Poiché l'arco ha chiave PK = ACTOR#alice, SK = TARGET#bob, è un GetItem diretto — la lettura più economica che DynamoDB offre, niente Query, niente indice.

Per scrivere il follow in modo idempotente ed evitare doppi conteggi, proteggi il PutItem con una condizione che l'arco non esista già:

attribute_not_exists(PK)

Puoi assemblare quella condizione — e i valori di chiave marshalled — con il DynamoDB expression builder invece di scrivere a mano il ConditionExpression e l'ExpressionAttributeValues.

Fallo in DynoTable

Quando navighi nella tabella, gli archi per un actor si impilano sotto una singola chiave di partizione come un'unica item collection, e passando alla vista GSI vedi la lista invertita dei follower — le due metà della relazione fianco a fianco.

DynoTable mostra gli Item-arco di follow di un actor sotto una singola partizione, con gli attributi chiave del GSI visibili come colonne.
DynoTable mostra gli Item-arco di follow di un actor sotto una singola partizione, con gli attributi chiave del GSI visibili come colonne.

Errori

La partizione "celebrità". Un utente con milioni di follower concentra ogni arco di follower sotto una partizione GSI1PK = TARGET#<star>. Le letture di quella collection sono paginate e possono scaldarsi. Per grafi a fan-out intenso, fai lo shard della hot key (es. TARGET#bob#0..N) o denormalizza i conteggi così non rileggi l'intera lista.

Memorizzare i conteggi sull'arco. Un conteggio di follower non è un arco — non derivarlo leggendo e contando l'intera partizione a ogni visualizzazione del profilo. Mantieni un attributo contatore sull'Item utente e aggiornalo transazionalmente con l'arco.

Dimenticare che la scrittura inversa non serve qui. Una classica variante adjacency-list scrive l'arco due volte con gli id scambiati. Con un GSI a chiave invertita lo scrivi una volta e lasci che l'indice materializzi l'inverso — meno scritture, nessuna deriva tra le due copie.

Prossimi passi

L'adjacency list è il building block delle relazioni del single-table design; l'indice invertente è un GSI, non un LSI, perché la chiave di partizione cambia. E ogni lettura qui è una Query o GetItem su una chiave nota — mai il footgun dello Scan.

Costruisci le espressioni di condizione e di chiave con il DynamoDB expression builder, e scarica DynoTable per modellare un grafo di follow sulla tua tabella e osservare entrambe le direzioni risolversi in una sola lettura.

Aggiornato