Come viene memorizzato internamente un GSI di DynamoDB
Un Global Secondary Index non è un puntatore di ritorno nella tua tabella. È una tabella separata, gestita internamente — le sue partizioni, il suo key schema, la sua capacità — che DynamoDB mantiene sincronizzata copiandovi le scritture in modo asincrono.
Venendo da SQL, un index è un B-tree imbullonato sulla stessa tabella fisica, aggiornato dentro la stessa transazione. Un GSI rompe entrambe quelle assunzioni, e quasi ogni sorpresa con un GSI risale a quell'unico fatto.
Come viene memorizzato un GSI di DynamoDB?
Un GSI di DynamoDB è memorizzato come una tabella separata e gestita internamente — con le proprie partizioni, il proprio key schema e la propria capacità — non come un puntatore nella tabella base. DynamoDB copia ogni scrittura nell'index in modo asincrono, memorizzando solo le chiavi del GSI, le chiavi della tabella base e gli eventuali attributi proiettati.
- Un GSI è una tabella a sé. Ha uno spazio di partizione completamente indipendente, con chiave la partition key del GSI, non quella della tabella base.
- Le scritture si replicano in modo asincrono. La tua scrittura viene committata prima sulla tabella base, poi DynamoDB la diffonde a ogni GSI su un percorso in background.
- Vengono memorizzati solo gli attributi proiettati. L'index contiene le chiavi del GSI, le chiavi base, più qualunque attributo tu abbia proiettato — nient'altro.
- La chiave del GSI non deve essere univoca. Più item base possono condividere una partition/sort key del GSI; la primary key della base è il discriminante che li mantiene distinti.
Inizia con un item base
Prendi un audit log SaaS. Ogni azione privilegiata in un workspace diventa un
evento immutabile. La tabella base, WorkspaceEvents, ha le chiavi impostate così
che tutti gli eventi di un workspace vivano in un'unica item collection, ordinati
per tempo:
| EventPK | EventSK | actorId | verb | targetRef |
|---|---|---|---|---|
| WS#orbit-9 | TS#2026-06-23T14:02:11Z | USR#kp | ROLE_GRANTED | USR#mara |
EventPK = "WS#orbit-9" partiziona per workspace; EventSK è un timestamp ISO
così che una Query restituisca gli eventi di un workspace in ordine cronologico.
Questo serve "mostrami la timeline di questo workspace" perfettamente.
Non serve nient'altro. Non puoi chiedere "cosa ha fatto USR#kp su ogni
workspace?" — actorId non è una chiave, quindi l'unico modo per rispondere sulla
tabella base è uno Scan completo. È l'access pattern che
un GSI esiste per aggiungere.
Aggiungi un GSI e guarda apparire una seconda tabella
Definisci un GSI, ByActor, che ri-partiziona gli stessi eventi per chi li ha
eseguiti:
ByActor (GSI)
GSI1PK = actorId ("USR#kp")
GSI1SK = EventSK ("TS#2026-06-23T14:02:11Z")
DynamoDB ora mantiene una seconda struttura fisica. Lo stesso evento logico è
memorizzato due volte — una nella partizione WS#orbit-9 della tabella base, e
di nuovo nella partizione USR#kp del GSI:
| GSI1PK | GSI1SK | EventPK | EventSK | verb |
|---|---|---|---|---|
| USR#kp | TS#2026-06-23T14:02:11Z | WS#orbit-9 | TS#2026-06-23T14:02:11Z | ROLE_GRANTED |
Nota cosa è venuto al seguito: le chiavi della tabella base (EventPK,
EventSK) sono memorizzate automaticamente in ogni item del GSI. È così che un hit
sul GSI può rimandarti all'item completo — ed è perché un index
KEYS_ONLY costa comunque storage.
Cosa vive davvero nel GSI
L'index non copia l'intero item. Ogni voce del GSI contiene esattamente tre cose, e tu controlli solo la terza:
| Memorizzato nel GSI | Da dove viene | Opzionale? |
|---|---|---|
| Partition + sort key GSI | Gli attributi che hai nominato come chiavi GSI | No |
| Chiave/i tabella base | Copiata da ogni item base | No |
| Attributi proiettati | La tua scelta di Projection | Sì |
Projection è KEYS_ONLY, INCLUDE (una lista nominata) o ALL. Una Query sul
GSI può restituire solo attributi che sono nell'index.
Chiedine uno che non è proiettato e DynamoDB non lo recupera in modo trasparente — non ottieni nulla per quel campo. (documentazione AWS sui GSI)
È la trappola relazionale al contrario: SQL farebbe la join di ritorno all'heap per la colonna mancante. Un GSI non lo fa mai. La proiezione è l'intero contratto.
Come una scrittura raggiunge l'index
La replica è la parte che rompe più duramente l'intuizione SQL. Una scrittura base e il suo aggiornamento dell'index non sono un'unica operazione atomica.
Quando fai PutItem, DynamoDB committa durevolmente sulla tabella base, conferma
la tua scrittura e poi propaga la modifica su un percorso in background che
aggiorna ogni GSI. La conferma non aspetta l'index.
Ecco l'ordine degli eventi per la nostra scrittura di audit, dall'alto al basso:
Il chiamante ottiene il suo 200 OK al terzo passo, prima che i passi da quattro a
sei finiscano — quindi una Query su ByActor nell'intervallo può perdere un
evento appena creato.
Quell'asincronia è per design, non un difetto: è la discendenza del paper Amazon Dynamo del 2007, che scelse la disponibilità rispetto alla coerenza sincrona. Le conseguenze complete vivono in perché un GSI è eventualmente coerente.
La chiave del GSI non è una chiave univoca
In SQL, un index secondario non univoco è il default e uno univoco è un vincolo a cui aderisci. Un GSI è l'opposto: non ha alcuna garanzia di univocità, mai.
Due eventi di audit dello stesso attore con timestamp che collidono
condividerebbero lo stesso GSI1PK e GSI1SK. DynamoDB li memorizza entrambi
— li disambigua internamente tramite la primary key della tabella base, che è
sempre portata al seguito.
Quindi una Query sul GSI per un attore in un istante può legittimamente
restituire diversi item. Se hai assunto una-riga-per-chiave come ti darebbe un
index univoco SQL, quello è il footgun.
Quando interroghi l'index, l'Expression Builder per
DynamoDB scrive la KeyConditionExpression
con nomi e valori correttamente escaped — es. corrispondendo a un attore da un
cutoff:
KeyConditionExpression: "#a = :actor AND #ts > :since"
ExpressionAttributeNames: { "#a": "actorId", "#ts": "EventSK" }
ExpressionAttributeValues: {
":actor": { "S": "USR#kp" },
":since": { "S": "TS#2026-06-01T00:00:00Z" }
}La capacità vive con l'index, non con la tabella
Poiché il GSI è una tabella a sé, ha la sua capacità di lettura e scrittura,
fatturata e sottoposta a throttling separatamente dalla tabella base. Una lettura
da ByActor consuma le unità di lettura del GSI, mai quelle della tabella.
L'accoppiamento inverso è quello che morde: ogni scrittura sulla tabella base scrive anche l'index, e se il GSI non riesce ad assorbirla, fa back-pressure sulla scrittura base. Quel meccanismo ha la sua guida — quando un GSI va in throttling sulle scritture della tabella base.
Questo è anche perché la partition key di un GSI conta tanto quanto quella della tabella base. Una chiave GSI a bassa cardinalità raggruppa le scritture su un'unica partizione dell'index anche quando le scritture base sono perfettamente distribuite — una hot partition che ti sei creato re-impostando le chiavi.
Insidie e prossimi passi
- Non aspettarti il ritorno di attributi non proiettati. Una
Querysul GSI restituisce solo ciò che l'index memorizza. Se ti serve l'item completo, proiettalo o recuperalo dalla tabella base tramite le chiavi portate al seguito. - Non trattare una chiave GSI come univoca. Pianifica che una
Queryrestituisca più di un item per chiave; la primary key della base è l'unica vera identità. - Non leggere un GSI subito dopo la scrittura che l'ha alimentato. Il percorso async significa che l'index potrebbe non mostrare ancora la tua scrittura — leggi la tabella base quando ti serve il read-your-own-writes.
- Dimensiona la capacità del GSI deliberatamente. È indipendente sulle letture e una dipendenza nascosta sulle scritture.
L'intero gioco è scegliere forme di chiave che servano i tuoi pattern — il single-table design sovraccarica un GSI su molti di essi; GSI vs LSI copre quando un index locale calza invece.
Costruisci e visualizza in anteprima la tua KeyConditionExpression del GSI
nell'Expression Builder per DynamoDB, poi
prova DynoTable per ispezionare gli attributi proiettati di un index e
guardare le scritture replicarsi nel GSI sulle tue tabelle.