Avanzato6 min di lettura

Key overloading in DynamoDB

Venendo da SQL, una colonna significa una cosa per sempre: orders.created_at è sempre una data, users.email è sempre un'email. Il key overloading butta via tutto questo. Dai alla chiave di partizione e alla sort key nomi generici — pk, sk — e lasci che ogni tipo di Item versi dentro un significato diverso. Una tabella, molte entità, una forma.

Cos'è il key overloading in DynamoDB?

Il key overloading consiste nel memorizzare molti tipi di entità in una sola tabella usando nomi di chiave generici come pk/sk, codificando il tipo nel valore (USER#u_3001, INVOICE#2026-0014). Il nome dell'attributo resta neutro, così utenti, fatture ed eventi condividono la stessa partizione; è il valore a portare il tipo, e un prefisso sulla sort key permette a una sola Query di estrarre ogni entità tramite begins_with.

  • Nomi di chiave generici, valori tipizzati. Chiama le tue chiavi pk/sk e metti il tipo di entità nel valore: pk = "TENANT#acme", sk = "USER#u_3001". Il nome è muto; il valore porta il tipo.
  • È ciò che fa funzionare il single-table design. Senza overloading, una tabella condivisa è solo un cassetto del disordine. Con esso, ogni entità sta in una partizione che puoi interrogare con Query.
  • begins_with è il guadagno. Un prefisso di tipo sulla sort key permette a una sola Query di estrarre un'intera entità, o una sua fetta, senza Scan e senza filtro.
  • Il costo: leggibilità. Un dump grezzo di pk/sk non ti dice nulla. Hai bisogno di un viewer che decodifichi i prefissi, altrimenti starai a strizzare gli occhi sulle stringhe.

Perché i nomi generici battono quelli reali

DynamoDB ha esattamente due attributi chiave per tabella, e una Query può puntare solo a una singola chiave di partizione. Quindi se chiami la tua chiave userId, solo gli Item utente possono vivere in quella tabella in modo pulito — tutto il resto deve fingere uno userId o spostarsi nella propria tabella.

L'overloading aggira tutto questo. Un nome neutro come pk non si impegna con nessuna entità, così un utente, una fattura e un evento di audit possono condividere lo stesso attributo chiave e la stessa tabella. Il valore, non il nome dell'attributo, dice cos'è l'Item.

Questa è la mossa che trasforma il single-table design da teoria in qualcosa che puoi davvero interrogare. La tabella condivisa è il contenitore; l'overloading è ciò che permette a entità distinte di coesistere al suo interno.

Un esempio multi-tenant

Diciamo che gestisci un prodotto SaaS di billing. Ogni tenant ha membri, fatture e un audit trail. Invece di tre tabelle, metti tutto in una e sovraccarica le chiavi:

pkskattributes
TENANT#acmeMETAname="Acme Inc", plan="team"
TENANT#acmeUSER#u_3001email, role="admin"
TENANT#acmeUSER#u_3002email, role="member"
TENANT#acmeINVOICE#2026-0014amount_cents, status="paid"
TENANT#acmeINVOICE#2026-0015amount_cents, status="open"
TENANT#acmeEVENT#2026-06-23T09:12Zactor="u_3001", action="invite"

Ogni riga condivide pk = "TENANT#acme", quindi formano un'unica item collection — tutte co-locate, tutte raggiungibili in una sola lettura di partizione.

Partizione: TENANT#acmesk: METAsk: USER#u_3001sk: INVOICE#2026-0015sk: EVENT#2026-06-23T09:12ZUna Query

Il prefisso della sort key fa il lavoro vero. Raggruppa le entità e le ordina.

Interroga la collection sovraccaricata

Poiché il tipo vive nel prefisso della sort key, begins_with affetta la partizione per entità senza fare scan di nulla:

Query pk = "TENANT#acme"  -- l'intero tenant, ogni tipo
Query pk = "TENANT#acme" AND begins_with(sk, "USER#")  -- solo i membri
Query pk = "TENANT#acme" AND begins_with(sk, "INVOICE#")  -- solo le fatture

Paghi solo per gli Item che la condizione abbina, non per l'intera partizione — l' opposto di uno Scan filtrato, dove paghi per leggere righe che poi butti via. AWS chiama questo una condition di chiave; gira sulle chiavi prima che qualsiasi dato lasci la partizione.

Se costruisci quella condizione begins_with a mano, azzecca i tag di tipo — un USERS# errante invece di USER# non restituisce nulla, silenziosamente. L' expression builder genera la KeyConditionExpression e la map ExpressionAttributeValues così i prefissi corrispondono a ciò che hai davvero scritto.

Sovraccarica anche l'indice

Lo stesso trucco si applica a un GSI. Dagli nomi di chiave generici — gsi1pk, gsi1sk — e lascia che ogni entità scriva ciò di cui ha bisogno. Un indice risponde poi a pattern che la tabella base non può.

pkskgsi1pkgsi1sk
TENANT#acmeINVOICE#2026-0015STATUS#open2026-06-30
TENANT#acmeINVOICE#2026-0014STATUS#paid2026-06-12
TENANT#betaINVOICE#2026-0099STATUS#open2026-06-25

Ora Query gsi1 WHERE gsi1pk = "STATUS#open" elenca ogni fattura aperta su tutti i tenant, ordinata per data di scadenza — una vista cross-partizione che le chiavi scoped per tenant della tabella base non potrebbero mai servire. Un'entità diversa può riutilizzare gsi1 con il proprio significato (diciamo gsi1pk = "ROLE#admin"), così un indice copre diverse letture. Ricorda solo che un GSI è eventualmente coerente — le sue scritture sono in ritardo rispetto alla tabella base.

Fallo in DynoTable

Le chiavi sovraccaricate grezze sono ostili da leggere: INVOICE#2026-0015 e EVENT#2026-06-23T09:12Z si confondono in un elenco piatto. Un viewer che raggruppa per partizione e fa emergere i prefissi trasforma il cassetto del disordine di nuovo in entità.

DynoTable mentre naviga la item collection di un tenant — Item META, USER, INVOICE ed EVENT raggruppati sotto una singola chiave di partizione sovraccaricata.
DynoTable mentre naviga la item collection di un tenant — Item META, USER, INVOICE ed EVENT raggruppati sotto una singola chiave di partizione sovraccaricata.

Errori

  • Scegli i delimitatori una volta e non cambiarli mai. # è la convenzione. Mischiare # e : tra le entità rompe begins_with in modi di cui nulla ti avverte.
  • Non sovraccaricare valori che richiedono aritmetica di range. Una sort key di INVOICE#2026-0015 si ordina lessicalmente, non numericamente — zero-padda gli id e usa date ISO-8601 così l'ordine delle stringhe corrisponde all'ordine che intendi.
  • Riserva il namespace dei prefissi. Due tipi di entità che iniziano entrambi con USER (diciamo USER# e USERGROUP#) collideranno sotto begins_with(sk, "USER"). Rendi i prefissi non ambigui dal primo carattere.
  • Pianifica la lettura prima delle chiavi. L'overloading serve pattern di accesso che hai enumerato. Se non conosci ancora le tue letture, vedi prima single-table design — le chiavi sono a valle delle query.

Mappa una partizione, poi scarica DynoTable per navigare le tue chiavi sovraccaricate e osservare una sola Query riportare indietro un intero tenant in una volta.

Aggiornato