LSI vs GSI dans DynamoDB : choisir le bon index secondaire

Vous avez modélisé votre table DynamoDB autour d'une clé de partition, puis le jour où un nouveau cas d'usage apparaît — filtrer les commandes par statut, rechercher les utilisateurs par email — vous réalisez que la clé primaire ne suffit plus. C'est exactement là que les index secondaires entrent en jeu, et le choix entre un LSI et un GSI a des conséquences directes sur la cohérence des lectures, la capacité de la table et les contraintes de conception.

TL;DR — LSI vs GSI dans DynamoDB

CritèreLSI (Local Secondary Index)GSI (Global Secondary Index)
Clé de partitionIdentique à la table de baseN'importe quel attribut
Clé de triAttribut différent de la tableN'importe quel attribut (optionnel)
CréationUniquement à la création de la tableÀ tout moment
Cohérence des lecturesFortement cohérente possibleÉventuellement cohérente uniquement
CapacitéPartagée avec la table de baseCapacité indépendante
Limite par table5 LSI maximum20 GSI maximum (quota ajustable)
Taille par partitionLimitée à 10 Go par valeur de clé de partitionPas de limite de taille par partition

Comment fonctionnent les index secondaires dans DynamoDB

DynamoDB stocke les données sous forme de partitions physiques déterminées par la clé de partition. Quand vous interrogez sans index, vous êtes contraint de fournir la valeur exacte de la clé de partition — sans elle, vous faites un Scan complet, ce qui est coûteux à grande échelle.

Un index secondaire crée une vue alternative de vos données avec une clé différente. DynamoDB maintient cet index automatiquement : chaque écriture sur la table de base se propage vers les index associés. Ce n'est pas une copie manuelle — c'est une projection gérée par le moteur de stockage.

La distinction fondamentale entre LSI et GSI tient à leur rapport avec la clé de partition de la table de base. Un LSI reste local à une partition — il partage la même clé de partition et ne fait que changer la clé de tri. Un GSI est global — il peut définir une clé de partition entièrement différente, ce qui lui permet de traverser toutes les partitions de la table.

graph LR Table["Table de base
PK: userId / SK: createdAt"] LSI["LSI: OrdersByStatus
PK: userId / SK: status"] GSI["GSI: UsersByEmail
PK: email"] Table -->|"Même partition physique"| LSI Table -->|"Partitions indépendantes"| GSI style LSI fill:#d4edda,stroke:#28a745 style GSI fill:#cce5ff,stroke:#004085
  1. Table de base : stockée par userId (partition) + createdAt (tri).
  2. LSI : même partition userId, tri alternatif status — les données restent dans la même partition physique.
  3. GSI : nouvelle partition email — DynamoDB redistribue les données sur des partitions indépendantes, permettant des requêtes globales.
  4. Les deux index reçoivent les mises à jour de la table de base de façon asynchrone pour le GSI, synchrone pour le LSI.

LSI — Index secondaire local : contraintes et cas d'usage

Le LSI doit être déclaré à la création de la table. Impossible de l'ajouter après coup — c'est la contrainte la plus douloureuse en pratique. Si vous réalisez six mois après le lancement que vous en avez besoin, la seule option est de recréer la table et de migrer les données.

En échange de cette rigidité, le LSI offre des lectures fortement cohérentes. Quand vous interrogez un LSI avec ConsistentRead: true, vous lisez les données telles qu'elles existent au moment de la requête — sans délai de propagation. C'est le seul type d'index secondaire dans DynamoDB qui supporte cette garantie.

L'autre contrainte critique : la limite de 10 Go par valeur de clé de partition. Cette limite s'applique à l'ensemble des données de la table de base plus toutes les projections LSI pour une valeur de clé de partition donnée. Si un utilisateur accumule des gigaoctets de commandes, vous pouvez atteindre cette limite et les écritures commenceront à échouer avec une erreur ItemCollectionSizeLimitExceededException.

stateDiagram-v2 [*] --> EcritureItem : PutItem / UpdateItem EcritureItem --> VerificationTaille : DynamoDB vérifie la collection VerificationTaille --> EcritureOK : Taille < 10 Go VerificationTaille --> EcritureEchouee : Taille >= 10 Go EcritureOK --> PropagationLSI : Mise à jour LSI synchrone EcritureEchouee --> Erreur : ItemCollectionSizeLimitExceededException PropagationLSI --> [*] Erreur --> [*]
  1. L'application tente d'écrire un nouvel item dans la table Orders.
  2. DynamoDB vérifie la taille de la collection d'items pour cette valeur de clé de partition.
  3. Si la limite de 10 Go est dépassée, l'écriture échoue avec ItemCollectionSizeLimitExceededException.
  4. Sans LSI, cette limite ne s'applique pas — la partition peut croître librement.
Un LSI, c'est comme choisir l'agencement de votre appartement avant de poser les fondations. Une fois les murs coulés, vous ne pouvez plus déplacer les cloisons porteuses.

Créer une table avec un LSI

aws dynamodb create-table \
  --table-name Orders \
  --attribute-definitions \
      AttributeName=userId,AttributeType=S \
      AttributeName=createdAt,AttributeType=S \
      AttributeName=status,AttributeType=S \
  --key-schema \
      AttributeName=userId,KeyType=HASH \
      AttributeName=createdAt,KeyType=RANGE \
  --local-secondary-indexes \
      '[{
          "IndexName": "OrdersByStatus",
          "KeySchema": [
              {"AttributeName": "userId", "KeyType": "HASH"},
              {"AttributeName": "status", "KeyType": "RANGE"}
          ],
          "Projection": {"ProjectionType": "ALL"}
      }]' \
  --billing-mode PAY_PER_REQUEST \
  --region us-east-1

Interroger un LSI avec lecture fortement cohérente

aws dynamodb query \
  --table-name Orders \
  --index-name OrdersByStatus \
  --key-condition-expression 'userId = :uid AND #s = :status' \
  --expression-attribute-names '{"#s": "status"}' \
  --expression-attribute-values '{
      ":uid": {"S": "user-123"},
      ":status": {"S": "PENDING"}
  }' \
  --consistent-read \
  --region us-east-1

GSI — Index secondaire global : flexibilité et compromis

Le GSI peut être ajouté ou supprimé à tout moment sur une table existante. C'est l'index par défaut quand vous découvrez un nouveau pattern d'accès après le déploiement. En production, la création d'un GSI déclenche un backfill asynchrone — DynamoDB parcourt la table de base et peuple l'index progressivement. Pendant cette phase, l'index est visible mais peut retourner des résultats incomplets.

Les lectures sur un GSI sont toujours éventuellement cohérentes. Il n'existe pas d'option ConsistentRead pour un GSI — si vous passez ce paramètre à true sur une requête GSI, DynamoDB retourne une erreur de validation. C'est un point qui surprend souvent les équipes qui migrent depuis des bases relationnelles.

Le GSI dispose de sa propre capacité de lecture et d'écriture, indépendante de la table de base. En mode provisionné, vous devez dimensionner le GSI séparément. Si le GSI est sous-dimensionné par rapport au débit d'écriture de la table, les écritures sur la table peuvent être throttlées — même si la table elle-même a suffisamment de capacité. C'est un comportement contre-intuitif qui génère des alertes difficiles à diagnostiquer.

Le throttling d'un GSI se propage en amont vers la table de base. Un GSI sous-dimensionné peut bloquer des écritures sur une table qui, vue isolément, semble avoir largement assez de capacité.

Ajouter un GSI sur une table existante

La table Users existe déjà avec userId comme clé de partition. On ajoute un GSI pour permettre les recherches par email. La table est en mode PAY_PER_REQUEST, donc aucun ProvisionedThroughput n'est requis pour l'index.

aws dynamodb update-table \
  --table-name Users \
  --attribute-definitions \
      AttributeName=email,AttributeType=S \
  --global-secondary-index-updates \
      '[{
          "Create": {
              "IndexName": "UsersByEmail",
              "KeySchema": [
                  {"AttributeName": "email", "KeyType": "HASH"}
              ],
              "Projection": {"ProjectionType": "KEYS_ONLY"}
          }
      }]' \
  --region us-east-1

Interroger un GSI

aws dynamodb query \
  --table-name Users \
  --index-name UsersByEmail \
  --key-condition-expression 'email = :email' \
  --expression-attribute-values '{
      ":email": {"S": "alice@example.com"}
  }' \
  --region us-east-1

Vérifier le statut de backfill d'un GSI

Après la création, surveillez le statut de l'index avant d'envoyer du trafic de production dessus.

aws dynamodb describe-table \
  --table-name Users \
  --query 'Table.GlobalSecondaryIndexes[?IndexName==`UsersByEmail`].{Status:IndexStatus,Backfilling:Backfilling}' \
  --region us-east-1

L'index passe par les états CREATING puis ACTIVE. Tant que Backfilling est true, les résultats peuvent être incomplets.

Politique IAM pour les requêtes sur index secondaire

Les permissions DynamoDB pour les index secondaires sont distinctes des permissions sur la table de base. Une politique qui autorise dynamodb:Query sur la table ne couvre pas automatiquement les index — vous devez explicitement inclure l'ARN de l'index.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:Query"
      ],
      "Resource": [
        "arn:aws:dynamodb:us-east-1:123456789012:table/Users",
        "arn:aws:dynamodb:us-east-1:123456789012:table/Users/index/UsersByEmail"
      ]
    }
  ]
}

Diagnostic de production : le GSI throttlé invisible

Symptôme observé : des erreurs ProvisionedThroughputExceededException sur des écritures dans la table Orders, alors que les métriques CloudWatch de la table montrent une consommation de WCU bien en dessous du seuil provisionné.

Diagnostic initial erroné : l'équipe augmente la capacité provisionnée de la table. Les erreurs persistent.

Cause réelle : la table Orders avait un GSI OrdersByStatus créé des mois plus tôt avec un ProvisionedThroughput de 5 WCU. Une campagne marketing avait multiplié le volume de commandes par dix. Le GSI était saturé, et DynamoDB throttlait les écritures sur la table de base en conséquence.

Vérification du débit consommé sur le GSI :

aws cloudwatch get-metric-statistics \
  --namespace AWS/DynamoDB \
  --metric-name WriteThrottleEvents \
  --dimensions \
      Name=TableName,Value=Orders \
      Name=GlobalSecondaryIndexName,Value=OrdersByStatus \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-15T06:00:00Z \
  --period 300 \
  --statistics Sum \
  --region us-east-1

Correction : augmenter la capacité du GSI indépendamment de la table, ou migrer la table en mode PAY_PER_REQUEST pour éliminer le dimensionnement manuel.

aws dynamodb update-table \
  --table-name Orders \
  --global-secondary-index-updates \
      '[{
          "Update": {
              "IndexName": "OrdersByStatus",
              "ProvisionedThroughput": {
                  "ReadCapacityUnits": 50,
                  "WriteCapacityUnits": 50
              }
          }
      }]' \
  --region us-east-1

Guide de décision : LSI ou GSI ?

graph TD Start(["Nouveau pattern d'accès"]) Q1{"Table déjà créée ?"} Q2{"Clé de partition différente ?"} Q3{"Cohérence forte requise ?"} Q4{"Données > 10 Go par partition ?"} GSI["Utiliser un GSI"] LSI["Utiliser un LSI"] Start --> Q1 Q1 -->|"Oui"| GSI Q1 -->|"Non"| Q2 Q2 -->|"Oui"| GSI Q2 -->|"Non"| Q3 Q3 -->|"Non"| Q4 Q3 -->|"Oui"| LSI Q4 -->|"Oui"| GSI Q4 -->|"Non"| LSI style GSI fill:#cce5ff,stroke:#004085 style LSI fill:#d4edda,stroke:#28a745
  1. Si vous avez besoin de lectures fortement cohérentes sur l'index, seul le LSI le permet.
  2. Si la table est déjà créée, le GSI est la seule option disponible.
  3. Si votre clé de partition doit changer pour le pattern d'accès, le GSI est obligatoire.
  4. Si la taille des données par partition peut dépasser 10 Go, évitez le LSI.
  5. Dans tous les autres cas, le GSI offre plus de flexibilité opérationnelle.

Projections d'index : ne projetez que ce dont vous avez besoin

Les deux types d'index supportent trois modes de projection : KEYS_ONLY (clés primaires uniquement), INCLUDE (clés + attributs spécifiés), et ALL (tous les attributs). Projeter ALL est tentant mais coûteux — chaque écriture sur la table de base duplique l'intégralité de l'item dans l'index. Pour un GSI avec une capacité indépendante, cela double les WCU consommées. Utilisez KEYS_ONLY ou INCLUDE quand le pattern d'accès est bien défini.

LSI vs GSI dans DynamoDB : conclusion et prochaines étapes

Le choix entre LSI et GSI se résume à deux questions : avez-vous besoin de cohérence forte, et pouvez-vous définir vos patterns d'accès avant de créer la table ? Si oui aux deux, le LSI est pertinent. Dans la majorité des cas de production, le GSI est l'outil par défaut — plus flexible, ajustable après coup, mais avec une cohérence éventuelle et un dimensionnement à surveiller activement.

Pour approfondir la modélisation des accès DynamoDB, consultez la documentation officielle sur les index secondaires et le guide bonnes pratiques pour les index DynamoDB.

Glossaire

TermeDéfinition
Clé de partitionAttribut utilisé par DynamoDB pour déterminer la partition physique de stockage d'un item.
Clé de triAttribut secondaire de la clé primaire, permettant d'ordonner les items dans une même partition.
ProjectionEnsemble d'attributs copiés depuis la table de base vers un index secondaire.
Cohérence forteGarantie que la lecture retourne la version la plus récente de l'item, reflétant toutes les écritures confirmées.
BackfillProcessus par lequel DynamoDB peuple un GSI nouvellement créé avec les données existantes de la table de base.

Related Posts

Commentaires

Posts les plus consultés de ce blog

Groupes IAM AWS : Pourquoi Attacher les Politiques aux Groupes Plutôt qu'aux Utilisateurs

NAT Gateway vs NAT Instance : Quelle solution choisir pour vos instances privées ?