Comment un GSI DynamoDB est stocké en interne
Un Global Secondary Index n'est pas un pointeur de retour vers ta table. C'est une table séparée, gérée en interne — ses propres partitions, son propre schéma de clés, sa propre capacité — que DynamoDB garde synchronisée en y copiant les écritures de façon asynchrone.
En venant de SQL, un index est un B-tree boulonné sur la même table physique, mis à jour dans la même transaction. Un GSI casse ces deux hypothèses, et presque chaque surprise de GSI remonte à ce seul fait.
Comment un GSI DynamoDB est-il stocké ?
Un GSI DynamoDB est stocké comme une table séparée, gérée en interne — avec ses propres partitions, son propre schéma de clés et sa propre capacité — et non comme un pointeur vers la table de base. DynamoDB copie chaque écriture dans l'index de façon asynchrone, en ne stockant que les clés du GSI, les clés de la table de base et les éventuels attributs projetés.
- Un GSI est sa propre table. Il a un espace de partitions totalement indépendant clé sur la clé de partition du GSI, pas celle de la table de base.
- Les écritures se répliquent de façon asynchrone. Ton écriture est committée sur la table de base d'abord, puis DynamoDB l'éclate vers chaque GSI sur un chemin en arrière-plan.
- Seuls les attributs projetés sont stockés. L'index contient les clés du GSI, les clés de base, plus les attributs que tu as projetés — rien d'autre.
- La clé du GSI n'a pas besoin d'être unique. Plusieurs items de base peuvent partager une même clé de partition/tri de GSI ; la clé primaire de base est le départage qui les garde distincts.
Commence par un item de base
Prenons un journal d'audit SaaS. Chaque action privilégiée dans un espace de
travail devient un événement immuable. La table de base, WorkspaceEvents, est clé
pour que tous les événements d'un espace de travail vivent dans une seule collection
d'items, ordonnés par le temps :
| EventPK | EventSK | actorId | verb | targetRef |
|---|---|---|---|---|
| WS#orbit-9 | TS#2026-06-23T14:02:11Z | USR#kp | ROLE_GRANTED | USR#mara |
EventPK = "WS#orbit-9" partitionne par espace de travail ; EventSK est un
timestamp ISO pour qu'un Query renvoie les événements d'un espace de travail en
ordre chronologique. Ça sert parfaitement « montre-moi la timeline de cet espace de
travail ».
Ça ne sert rien d'autre. Tu ne peux pas demander « qu'a fait USR#kp à travers
chaque espace de travail ? » — actorId n'est pas une clé, donc le seul moyen d'y
répondre sur la table de base est un Scan complet. C'est
l'access pattern qu'un GSI existe pour ajouter.
Ajoute un GSI et regarde une seconde table apparaître
Définis un GSI, ByActor, qui repartitionne les mêmes événements par qui les a
réalisés :
ByActor (GSI)
GSI1PK = actorId ("USR#kp")
GSI1SK = EventSK ("TS#2026-06-23T14:02:11Z")
DynamoDB maintient maintenant une seconde structure physique. Le même événement
logique est stocké deux fois — une fois dans la partition WS#orbit-9 de la
table de base, et encore dans la partition USR#kp du 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 |
Note ce qui a fait le voyage : les clés de la table de base (EventPK,
EventSK) sont stockées dans chaque item de GSI automatiquement. C'est ainsi qu'un
hit de GSI peut te ramener à l'item complet — et pourquoi un index
KEYS_ONLY coûte quand même du
stockage.
Ce qui vit réellement dans le GSI
L'index ne copie pas l'item entier. Chaque entrée de GSI contient exactement trois choses, et tu ne contrôles que la troisième :
| Stocké dans le GSI | D'où ça vient | Optionnel ? |
|---|---|---|
| Clé de partition + tri du GSI | Les attributs que tu as nommés comme clés de GSI | Non |
| Clé(s) de la table de base | Copiées depuis chaque item de base | Non |
| Attributs projetés | Ton choix de Projection | Oui |
Projection est KEYS_ONLY, INCLUDE (une liste nommée) ou ALL. Un Query sur
le GSI ne peut renvoyer que les attributs présents dans l'index.
Demande-en un qui n'est pas projeté et DynamoDB ne va pas le récupérer de façon transparente — tu n'obtiens rien pour ce champ. (doc AWS sur les GSI)
C'est le piège relationnel inversé : SQL ferait une jointure de retour vers le heap pour la colonne manquante. Un GSI ne le fait jamais. La projection est tout le contrat.
Comment une écriture atteint l'index
La réplication est la partie qui casse le plus durement l'intuition SQL. Une écriture de base et sa mise à jour d'index ne sont pas une seule opération atomique.
Quand tu fais PutItem, DynamoDB committe durablement sur la table de base,
acquitte ton écriture, puis propage le changement sur un chemin en arrière-plan
qui met à jour chaque GSI. L'acquittement n'attend pas l'index.
Voici l'ordre des événements pour notre écriture d'audit, de haut en bas :
L'appelant reçoit son 200 OK à l'étape trois, avant que les étapes quatre à six ne
finissent — donc un Query sur ByActor dans cet intervalle peut manquer un
événement tout neuf.
Cette asynchronie est intentionnelle, pas un défaut : c'est la lignée du papier Amazon Dynamo de 2007, qui a choisi la disponibilité plutôt que la cohérence synchrone. Les conséquences complètes vivent dans pourquoi un GSI est à cohérence à terme.
La clé du GSI n'est pas une clé unique
En SQL, un index secondaire non unique est le défaut et un unique est une contrainte que tu choisis. Un GSI est l'inverse : il n'a aucune garantie d'unicité, jamais.
Deux événements d'audit du même acteur à des timestamps qui collisionnent
partageraient le même GSI1PK et GSI1SK. DynamoDB stocke les deux — il les
désambiguïse en interne par la clé primaire de la table de base, qui est toujours
emportée avec.
Donc un Query de GSI pour un acteur à un instant peut légitimement renvoyer
plusieurs items. Si tu as supposé une-ligne-par-clé comme un index unique SQL te le
donnerait, c'est le piège.
Quand tu interroges l'index, le
constructeur d'expressions DynamoDB écrit la
KeyConditionExpression avec les noms et valeurs échappés correctement — par ex.
correspondant à un acteur depuis un seuil :
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é vit avec l'index, pas avec la table
Parce que le GSI est sa propre table, il a sa propre capacité de lecture et
d'écriture, facturée et throttlée séparément de la table de base. Une lecture sur
ByActor consomme les unités de lecture du GSI, jamais celles de la table.
Le couplage inverse est celui qui mord : chaque écriture de table de base écrit aussi l'index, et si le GSI ne peut pas l'absorber, il fait pression en retour sur l'écriture de base. Ce mécanisme a son propre guide — quand un GSI throttle les écritures de la table de base.
C'est aussi pourquoi la clé de partition d'un GSI importe autant que celle de la table de base. Une clé de GSI à faible cardinalité regroupe les écritures sur une seule partition d'index même quand les écritures de base sont parfaitement réparties — une partition chaude que tu as créée en re-clé.
Pièges et étapes suivantes
- Ne t'attends pas à récupérer les attributs non projetés. Un
Queryde GSI ne renvoie que ce que l'index stocke. Si tu as besoin de l'item complet, projette-le ou récupère-le depuis la table de base par les clés emportées. - Ne traite pas une clé de GSI comme unique. Prévois qu'un
Queryrenvoie plus d'un item par clé ; la clé primaire de base est la seule vraie identité. - Ne lis pas un GSI juste après l'écriture qui l'a alimenté. Le chemin async signifie que l'index peut ne pas montrer ton écriture encore — lis la table de base quand tu as besoin de lire tes propres écritures.
- Dimensionne la capacité du GSI délibérément. Elle est indépendante en lecture et une dépendance cachée en écriture.
Tout le jeu consiste à choisir des formes de clé qui servent tes patterns — le single-table design surcharge un GSI sur plusieurs d'entre eux ; GSI vs LSI couvre quand un index local convient à la place.
Construis et prévisualise ta KeyConditionExpression de GSI dans le
constructeur d'expressions DynamoDB, puis
essaie DynoTable pour inspecter les attributs projetés d'un index et
regarder les écritures se répliquer dans le GSI sur tes propres tables.