Intermédiaire8 min de lecture

Projections d'index DynamoDB

Quand tu crées un secondary index, DynamoDB ne copie pas automatiquement tout l'item dedans. Tu choisis ce qui est copié — la projection de l'index. Choisis trop peu et tes queries paient une seconde lecture pour fetch le reste ; choisis tout et tu paies du stockage et du coût d'écriture extra à chaque update. C'est un tradeoff que tu settes une fois à la création de l'index et avec lequel tu vis.

(Ne confonds pas ça avec un projection expression, qui trim les attributs qu'une seule lecture renvoie. Cette page porte sur ce qu'un index stocke physiquement — vois projection expressions pour l'autre.)

Qu'est-ce qu'une projection d'index DynamoDB ?

Une projection est le set d'attributs que DynamoDB copie du table de base dans un secondary index. Tu choisis un des trois types : KEYS_ONLY (juste les clés), INCLUDE (clés plus une liste nommée d'attributs), ou ALL (tout l'item). Plus de projection veut dire moins de fetches du table de base mais plus de stockage et de coût d'écriture.

  • Une projection est le set d'attributs copiés dans un secondary index.
  • KEYS_ONLY — seulement les clés du table et de l'index. Le plus petit, le moins cher.
  • INCLUDE — les clés plus une liste nommée d'attributs extra que tu choisis.
  • ALL — chaque attribut de l'item. Le plus grand ; les queries n'ont jamais besoin du table de base.
  • Un attribut qui n'est pas projeté est simplement indisponible depuis un GSI — ton app doit émettre ses propres lectures du table de base. (Seul un LSI fetch les attributs non projetés pour toi, à coût de lecture extra.)
  • Plus de projection = plus de stockage + plus de coût d'écriture, puisque chaque écriture du table de base se propage à l'index.

Le problème : l'index qui te fait lire deux fois

Disons que tu gères un support desk avec un GSI qui te laisse lister les tickets ouverts par priorité. Tu projettes KEYS_ONLY pour le garder lean. La query revient vite — mais elle ne te donne que des IDs de ticket, et ton écran de queue a besoin du subject, assignee et age de chaque ticket.

Donc maintenant ton code fait une seconde ronde de lectures contre le table de base pour hydrater chaque résultat. Le « one query » que tu as conçu est vraiment une query plus N gets, et la latence et le coût que tu essayais d'économiser sont revenus. La projection était trop mince pour le modèle d'accès.

Ce que chaque type de projection copie

Item de base : clés + subject +assignee + age + bodyKEYS_ONLY : clés seulementINCLUDE : clés + subject,assignee, ageALL : chaque attribut
  • KEYS_ONLY stocke juste la clé du table de base et la clé d'index. Utilise-le quand la query a seulement besoin de savoir quels items matchent et que tu fetcheras les détails ailleurs — ou pas du tout.
  • INCLUDE stocke les clés plus une liste fixe d'attributs que tu nommes. Le sweet spot : projette exactement les fields dont ta query a besoin pour render, et rien de plus.
  • ALL copie l'item entier. Les queries sont fully self-served depuis l'index, au coût de dupliquer tout le stockage et write throughput de l'item dedans.

Pour la queue du support desk, INCLUDE avec subject, assignee et age est le bon appel — la queue render depuis l'index seul, sans second fetch et sans dupliquer le gros body du ticket dans l'index.

Le coût que tu trades

Chaque attribut que tu projettes est stocké une seconde fois et réécrit dans l'index chaque fois que l'item de base change. Donc une projection ALL généreuse sur un table fréquemment updaté multiplie à la fois stockage et write capacity. Projette ce que la query lit, pas « tout, juste au cas où ».

Une subtilité à connaître : avec un index sparse, la projection ne tient encore que les items qui portent la clé d'index — donc INCLUDE/ALL sur un index sparse reste petit parce que l'index lui-même est petit. Pèse le multiplicateur stockage et écriture pour ta projection avec le calculateur de pricing DynamoDB, et assemble les queries d'index elles-mêmes avec le DynamoDB expression builder.

Voir une projection dans DynoTable

DynoTable liste chacun des secondary indexes d'un table et te laisse query directement à travers un. Lance le même modèle d'accès contre le table de base et contre un GSI et compare les résultats — les attributs manquants du résultat d'index sont exactement ceux qu'il ne projette pas, donc l'effet d'une projection est visible sans relire la définition du table.

Choisir quel index DynamoDB une query traverse, dans le sélecteur d'index de DynoTable.
Choisir quel index DynamoDB une query traverse, dans le sélecteur d'index de DynoTable.

Pièges + étapes suivantes

  • Un attribut non projeté sur un GSI veut dire un fetch du table de base — conçois la projection autour de ce que la query render.
  • ALL est rarement free — ça duplique stockage et coût d'écriture ; default vers INCLUDE sauf si l'index a vraiment besoin de chaque field.
  • Les projections sont surtout fixes. Tu ne peux pas librement éditer la projection d'un GSI plus tard sans recreer l'index — choisis délibérément d'emblée.
  • Lié : GSI vs LSI et indexes sparse façonnent combien une projection stocke vraiment.

Envie de voir ce que chacun de tes indexes renvoie vraiment avant de les redesigner ? Télécharge DynoTable et query tes tables directement.

Coût d'hydration : KEYS_ONLY + N gets

Retour à l'exemple de queue du support desk : 50 tickets ouverts affichés avec subject, assignee et age.

ProjectionQuery d'indexLectures de suiviEsquisse RCU EC (items de base 2 KB)
KEYS_ONLY50 clés renvoyées50 × GetItem~50 RCU index + ~50 RCU base
INCLUDE subject, assignee, age50 lignes self-containednone~50 RCU index seulement
ALL50 copies fullnone~50 RCU index ; amp stockage + écriture plus haute

Les chiffres exacts dépendent des tailles d'attributs projetés — colle un ticket d'exemple dans le calculateur de taille d'item et multiplie par la profondeur de queue. Un INCLUDE qui ne liste que les fields UI bat souvent ALL quand l'attribut body est large et rarement montré dans la list view.

Comportement de fetch de projection LSI

Seuls les LSIs peuvent optionnellement fetch des attributs non projetés depuis le table de base pendant un query (avec un coût de lecture supplémentaire). Les GSIs ne le font jamais — les attributs manquants requièrent que ton application appelle GetItem sur le table de base. Cette différence pousse beaucoup de designs GSI vers des projections INCLUDE un peu plus larges d'emblée.

Changer les projections plus tard

Les projections GSI sont fixes à la création. Élargir KEYS_ONLY vers INCLUDE requiert de créer un nouvel index, backfiller, cutover le trafic, et supprimer l'ancien index — planifie les fields avant le launch. Les LSIs partagent la même limitation.

Quand tu évalues un nouveau modèle d'accès, query l'index candidat dans DynoTable et liste quels attributs apparaissent — les gaps mappent 1:1 aux entrées de projection manquantes.

Paire avec les indexes sparse

Un GSI sparse qui indexe seulement les tickets status = open stocke des projections pour les lignes ouvertes seulement. INCLUDE sur cet index reste bon marché même quand le table de base tient des millions de tickets fermés — l'index ne les a jamais copiés.

Combine avec les patterns d'index sparse quand le sous-ensemble filtré est petit par rapport au table.

Construis le modèle d'accès d'abord

Utilise le query builder pour prototyper la query GSI — key condition, projection expression et filter — avant d'altérer CloudFormation. Swap les types de projection dans la discussion de design en demandant quelles colonnes l'UI render ; tout le reste reste sur le table de base.

Mis à jour