Comment un GSI DynamoDB est stocké en interne
Un n'est pas un pointeur 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 manière asynchrone.
En venant de SQL, un index est un arbre B boulonné à la même table physique, mis à jour à l'intérieur de la même transaction. Un GSI casse ces deux hypothèses, et presque chaque surprise liée à un 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 — ses propres partitions, son schéma de clés et sa capacité — et non comme un pointeur vers la table de base. DynamoDB copie chaque écriture dans l'index de manière asynchrone, en n'y stockant que les clés du GSI, les clés de la table de base et les éventuels attributs .
- Un GSI est sa propre table. Il a un espace de partitions entièrement indépendant, clé par la clé de partition du GSI, pas par celle de la table de base.
- Les écritures se répliquent de manière asynchrone. Ton écriture est d'abord validée sur la table de base, puis DynamoDB la diffuse vers chaque GSI par un chemin d'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 du GSI ; la de base est le départage qui les garde distincts.
Commence par un seul item de base
Prends 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é de sorte que tous les
événements d'un espace de travail vivent dans une seule , ordonnée 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 horodatage ISO afin qu'une
Query renvoie les événements d'un espace de travail dans l'ordre chronologique. Cela répond parfaitement à
« montre-moi la chronologie de cet espace de travail ».
Cela ne répond à rien d'autre. Tu ne peux pas demander « qu'a fait USR#kp à travers tous les
espaces 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 le mode d'accès 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 celui qui les a réalisés :
ByActor (GSI)
partition key = actorId ("USR#kp")
sort key = EventSK ("TS#2026-06-23T14:02:11Z")
DynamoDB maintient désormais 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 à nouveau dans
la partition USR#kp du GSI :
| actorId | EventSK | EventPK | verb |
|---|---|---|---|
| USR#kp | TS#2026-06-23T14:02:11Z | WS#orbit-9 | ROLE_GRANTED |
Note ce qui a suivi : les clés de la table de base (EventPK, EventSK) sont stockées
dans chaque item du GSI automatiquement. C'est ainsi qu'un résultat de GSI peut te renvoyer vers
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 nommés comme clés du 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. Une Query sur le
GSI ne peut renvoyer que des attributs présents dans l'index.
Demandes-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. (AWS : docs GSI)
C'est le piège relationnel inversé : SQL ferait une jointure vers le tas pour la colonne manquante. Un GSI ne le fait jamais. La est tout le contrat.
Comment une écriture atteint l'index
La réplication est la partie qui heurte le plus l'intuition SQL. Une écriture de base et sa mise à jour d'index ne sont pas une seule opération atomique.
Quand tu fais un PutItem, DynamoDB valide durablement sur la table de base, acquitte ton
écriture, puis propage le changement sur un chemin d'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 se terminent —
donc une Query sur ByActor dans l'intervalle peut manquer un événement tout neuf.
Cette asynchronie est voulue, pas un défaut : c'est l'héritage du papier Amazon Dynamo de 2007, qui a choisi la disponibilité plutôt que la cohérence synchrone. Les conséquences complètes se trouvent 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 cas par défaut et un index 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 horodatages qui coïncident partageraient la
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 transportée avec.
Donc une Query de GSI pour un acteur à un instant donné peut légitimement renvoyer plusieurs
items. Si tu supposais une-ligne-par-clé comme un index unique SQL te le donnerait,
c'est le piège.
Quand tu interroges l'index, le
Générateur d'expressions DynamoDB écrit la
KeyConditionExpression avec les noms et valeurs échappés correctement — par ex. en faisant correspondre
un acteur depuis une date de coupure :
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 limité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 la table de base écrit aussi l'index, et si le GSI ne peut pas absorber ça, il fait contre-pression sur l'écriture de base. Ce mécanisme a son propre guide — quand un GSI limite les écritures de la table de base.
C'est aussi pourquoi la clé de partition d'un GSI compte autant que celle de la table de base. Une clé de GSI à faible cardinalité agglutine 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é.
Amplification d'écriture des GSI (facturée)
Chaque écriture sur la table de base qui se projette dans un GSI coûte WCU de
base + WCU d'index en on-demand dans us-east-1. Un item de 1 Ko avec une
projection ALL facture typiquement ~2 WCU au total — un pour la ligne de la
table, un pour la copie dans l'index. KEYS_ONLY réduit l'écriture d'index ;
ALL double le stockage et l'amplification d'écriture. Modélise la taille des
items et la projection dans le
calculateur de tarifs.
Pièges et étapes suivantes
- N'attends pas le retour d'attributs non projetés. Une
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 transportées. - Ne traite pas une clé de GSI comme unique. Prévois qu'une
Queryrenvoie plus d'un item par clé ; la clé primaire de base est la seule identité réelle. - Ne lis pas un GSI juste après l'écriture qui l'a alimenté. Le chemin asynchrone signifie que l'index peut ne pas encore montrer ton écriture — lis la table de base quand tu as besoin de relire tes propres écritures.
- Dimensionne la capacité du GSI délibérément. Elle est indépendante sur les lectures et une dépendance cachée sur les écritures.
Tout l'enjeu est de choisir des formes de clés qui servent tes modes d'accès — le single-table design surcharge un seul GSI sur beaucoup d'entre eux ; GSI vs LSI couvre les cas où un index local convient mieux.
Construis et prévisualise ta KeyConditionExpression de GSI dans le
Générateur 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.