Débutant6 min de lecture

Les projection expressions DynamoDB

Une projection expression est le SELECT col1, col2 de DynamoDB : une liste de noms d'attribut séparés par des virgules qui dit à GetItem, Query ou Scan de ne renvoyer que ces attributs au lieu de l'item entier.

Les projection expressions DynamoDB réduisent-elles le coût de lecture ?

Non. Une ProjectionExpression rogne le payload de la réponse, pas la capacité de lecture facturée. DynamoDB lit l'item complet depuis le stockage, mesure la sur sa taille sur disque, puis retire les attributs que tu n'as pas nommés à la sortie. Pour réellement réduire le coût de lecture, utilise plutôt un couvrant.

  • Elle rogne le payload, pas le coût de lecture. DynamoDB lit (et facture) l'item complet depuis le stockage, puis retire les attributs que tu n'as pas nommés à la sortie. ProjectionExpression est une optimisation réseau, pas une optimisation de capacité.
  • C'est ainsi que tu récupères un sous-ensemble public. Nomme les quelques attributs qu'un appelant a le droit de voir ; le reste ne quitte jamais la table.
  • Utilise des placeholders #name pour tout ce qui pourrait être réservé. Les noms d'attribut nus dans l'expression entrent en collision avec les ~570 mots réservés de DynamoDB et font échouer la requête.
  • Pour de vraies économies de lecture, utilise plutôt un covering index. Un GSI qui ne projette que les colonnes dont tu as besoin est lu à sa propre taille (plus petite).

Ce qu'elle économise réellement

Venant de SQL, tu supposerais que SELECT a, b scanne moins que SELECT *. Dans DynamoDB cette intuition est fausse. La capacity unit d'une lecture est calculée à partir de la taille de l'item sur le disque, arrondie au prochain 4 Ko — avant que la projection soit appliquée. AWS est explicite : une ProjectionExpression ne change pas la capacité de lecture qu'une requête consomme.1

Donc une projection t'économise deux choses, toutes deux réelles mais toutes deux en aval de la lecture :

  • Des octets sur le réseau. Un item de 6 Ko renvoyé comme deux petits attributs est une réponse minuscule. Sur un Query qui renvoie des centaines d'items, ça s'additionne vite.
  • Du travail côté client. Moins à désérialiser, moins à garder en mémoire, moins à fuiter par accident dans un log ou une réponse API.

Ce qu'elle ne pas économise, c'est la RCU. C'est l'arme à fragmentation : les gens sortent une projection pour réduire leur facture, ne voient aucun changement, et concluent que DynamoDB est cassé. Il ne l'est pas — tu as mesuré le mauvais levier.

Projeter un profil utilisateur public

Disons que tu exploites un annuaire d'utilisateurs. Chaque profil est un item, clé pour que tu puisses récupérer une personne par pseudo :

PK = "PROFILE#ada"      (clé de partition)
SK = "PROFILE#ada"      (clé de tri — collection à item unique)

L'item est gras. Il porte la face publique du compte plus un tas d'attributs privés et opérationnels :

{
  "PK": "PROFILE#ada",
  "SK": "PROFILE#ada",
  "displayName": "Ada L.",
  "avatarUrl": "https://cdn.example.com/u/ada.png",
  "bio": "Builds things.",
  "emailAddress": "ada@example.com",
  "passwordResetToken": "…",
  "billingCustomerId": "cus_…",
  "lastLoginIp": "…",
  "internalRiskScore": 0.02
}

Une carte de profil public a besoin de trois champs. Récupérer l'item entier signifie que emailAddress, lastLoginIp et internalRiskScore voyagent vers un contexte qui ne devrait jamais les voir. Ne nomme que le sous-ensemble public :

GetItem  PK = "PROFILE#ada"  SK = "PROFILE#ada"
ProjectionExpression: displayName, avatarUrl, bio

La réponse porte trois attributs. Les privés restent dans la table — pas filtrés par ton app après l'arrivée, mais jamais sérialisés dans la réponse du tout. C'est le gain de sécurité, et c'est celui qui est dur à défaire une fois qu'un secret a déjà franchi une frontière.

Tu peux assembler et copier cette requête exacte — noms, placeholders, et l'appel SDK — dans le DynamoDB Expression Builder, qui émet la ProjectionExpression et la map ExpressionAttributeNames pour toi.

Échappe les mots réservés avec des placeholders #

Voici où une projection propre explose. DynamoDB réserve une longue liste de mots — name, status, comment, size, timestamp, et des centaines d'autres.2 Si un attribut que tu projettes en est un, le nom brut dans l'expression est rejeté.

Suppose que le profil ait aussi un attribut status ("active", "suspended"). Ceci échoue :

ProjectionExpression   displayName, status

status est réservé. Le correctif est un expression attribute name — un placeholder préfixé de # mappé au vrai nom :

ProjectionExpression       displayName, #s
ExpressionAttributeNames   { "#s": "status" }

Le même mécanisme atteint les attributs imbriqués. Pour tirer un seul champ d'une map, ou un élément d'une liste, utilise la syntaxe de chemin de document — et mets un placeholder sur chaque segment, puisque n'importe lequel pourrait être réservé :

ProjectionExpression       #addr.#city, tags[0]
ExpressionAttributeNames   { "#addr": "address", "#city": "city" }

Une règle pratique : mets un placeholder partout. Tu n'as jamais à te souvenir lequel des ~570 mots réservés tu piétines, et l'expression se lit pareil dans les deux cas.

Quand un covering index bat une projection

Si tu as véritablement besoin de réduire le coût de lecture — pas juste le payload — le levier est un Global Secondary Index qui ne projette que les attributs que tu lis. Un GSI est une copie séparée des données ; tu choisis KEYS_ONLY, INCLUDE ou ALL pour sa projection.3 Un index KEYS_ONLY ou un INCLUDE étroit est physiquement plus petit par item, donc un Query contre lui est mesuré à cette taille plus petite.

C'est un covering index : la requête est répondue entièrement depuis l'index, pas de retour vers la table de base. Utilise-le quand un motif de lecture chaud n'a jamais besoin que de quelques attributs de gros items.

ProjectionExpressionCovering GSI
Rogne le payloadOuiOui
Rogne le coût de lectureNonOui — lu à la taille de l'index
Stockage supplémentaireAucunUne seconde copie des champs projetés
Coût d'écriture supplémentaireAucunLes écritures se propagent vers l'index
Idéal pourCacher des champs privés ; petits gainsLectures chaudes de quelques champs depuis de gros items

Le compromis est honnête : l'index te coûte du stockage et de la capacité d'écriture pour économiser de la capacité de lecture. Vaut le coup pour une lecture fréquente d'une tranche fine d'un item lourd ; pas le coup pour rogner un GetItem ponctuel. Vois GSI vs LSI pour choisir le type d'index, et quand une lecture de GSI peut être périmée avant de le mettre sur le chemin chaud.

Pièges et étapes suivantes

  • N'attends pas une facture plus petite. Une projection seule ne change jamais la RCU. Si le nombre n'a pas bougé, c'est le comportement documenté, pas un bug.
  • Mets des placeholders sur les mots réservés. Un name ou status nu dans l'expression fait échouer la requête — #-mappe-le.
  • Projeter les attributs de clé est gratuit et souvent utile — DynamoDB les renvoie à bas coût, et ils te laissent paginer ou re-récupérer.
  • Sors un covering index seulement quand un motif chaud lit quelques champs de gros items ; pèse d'abord le coût d'écriture/stockage.

Construis la ProjectionExpression et sa map de noms d'attribut dans l'Expression Builder, et essaie DynoTable pour exécuter ces projections contre tes propres tables et regarder la réponse rétrécir.


  1. AWS DynamoDB Developer Guide, Working with Read and Write Operations — la capacité de lecture se base sur la taille de l'item avant l'application de toute ProjectionExpression. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/
  2. AWS DynamoDB Developer Guide, Reserved Words in DynamoDB. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html
  3. AWS DynamoDB Developer Guide, Attribute Projections (KEYS_ONLY / INCLUDE / ALL). https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html

Mis à jour