Débutant7 min de lecture

Expressions de projection DynamoDB

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

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

Non. Une ProjectionExpression réduit la charge utile de la réponse, pas la capacité de lecture qui t'est facturée. DynamoDB lit l'item complet depuis le stockage, compte la sur sa taille sur disque, puis écarte les attributs que tu n'as pas nommés à la sortie. Pour vraiment réduire le coût de lecture, utilise plutôt un couvrant.

  • Elle réduit la charge utile, pas le coût de lecture. DynamoDB lit (et facture) l'item complet depuis le stockage, puis écarte 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 est autorisé à voir ; le reste ne quitte jamais la table.
  • Utilise des placeholders #name pour tout ce qui pourrait être réservé. Les noms d'attributs bruts dans l'expression entrent en collision avec les quelque 570 mots réservés de DynamoDB et font échouer la requête.
  • Pour de vraies économies de lecture, utilise plutôt un index couvrant. Un qui ne projette que les colonnes dont tu as besoin est lu à sa propre taille (plus petite).

Ce qu'elle économise réellement

Quand tu viens du SQL, tu supposerais que SELECT a, b scanne moins que SELECT *. Dans DynamoDB cette intuition est fausse. L'unité de capacité d'une lecture est calculée à partir de la taille de l'item sur disque, arrondie au 4 KB supérieur — avant que la projection ne 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, réelles toutes les deux mais toutes deux en aval de la lecture :

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

Ce qu'elle n'économise pas, c'est la RCU. C'est le piège : les gens se tournent vers 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 gères un annuaire d'utilisateurs. Chaque profil est un item, avec une clé qui te permet de récupérer une personne par pseudo :

PK = "PROFILE#ada"      (partition key)
SK = "PROFILE#ada"      (sort key — single-item collection)

L'item est volumineux. Il porte le visage public du compte plus une pile 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 application après 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 difficile à annuler 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 générateur d'expressions DynamoDB, qui émet la ProjectionExpression et la table ExpressionAttributeNames pour toi.

Ajoute ou retire des champs dans le préréglage ci-dessous pour regarder la ProjectionExpression changer — seuls les attributs listés reviennent :

Construis ta requête
Code généré
new QueryCommand({
  "TableName": "AuditLog",
  "KeyConditionExpression": "#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)",
  "ProjectionExpression": "#proj0, #proj1, #proj2",
  "ExpressionAttributeNames": {
    "#hashKey": "pk",
    "#rangeKey": "sk",
    "#proj0": "action",
    "#proj1": "actor",
    "#proj2": "createdAt"
  },
  "ExpressionAttributeValues": {
    ":hashKeyValue": {
      "S": "TENANT#acme"
    },
    ":rangeKeyValue": {
      "S": "EVENT#"
    }
  }
})

Échapper 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 est l'un d'eux, le nom brut dans l'expression est rejeté.

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

ProjectionExpression   displayName, status

status est réservé. Le correctif est un nom d'attribut d'expression — un placeholder préfixé par # mappé au vrai nom :

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

Le même mécanisme s'étend aux attributs imbriqués. Pour extraire 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 d'entre eux pourrait être réservé :

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

Une règle pratique : mets un placeholder sur tout. Tu n'as jamais à te rappeler lequel des quelque 570 mots réservés tu es en train de fouler, et l'expression se lit de la même façon dans les deux cas. Et si tu préfères savoir quels noms posent réellement problème, colle-les dans le vérificateur de mots réservés — il signale les collisions et émet la map d'alias ExpressionAttributeNames.

Quand un index couvrant bat une projection

Si tu as vraiment besoin de réduire le coût de lecture — pas juste la charge utile — le levier est un index secondaire global 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 avec un INCLUDE étroit est physiquement plus petit par item, donc un Query contre lui est compté à cette taille plus petite.

C'est un index couvrant : la requête est entièrement répondue depuis l'index, sans retour à la table de base. Utilise-le quand un mode de lecture fréquent n'a jamais besoin que de quelques attributs de gros items.

ProjectionExpressionGSI couvrant
Réduit la charge utileOuiOui
Réduit 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 à l'index
Idéal pourCacher des champs privés ; petits gainsLectures fréquentes de quelques champs d'items volumineux

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. Ça vaut le coup pour une lecture fréquente d'une fine tranche d'un item lourd ; pas pour rogner un GetItem ponctuel. Voir GSI vs LSI pour choisir le type d'index, et quand une lecture de GSI peut être obsolète avant d'en mettre un sur le chemin critique.

Pièges et étapes suivantes

  • Ne t'attends pas à une facture plus petite. Une projection seule ne change jamais la RCU. Si le chiffre n'a pas bougé, c'est le comportement documenté, pas un bug.
  • Mets un placeholder sur les mots réservés. Un name ou status nu dans l'expression fait échouer la requête — mappe-le avec #.
  • Inclus toujours les attributs de clé — ils ajoutent une charge utile négligeable et te permettent de paginer ou de récupérer à nouveau l'item.
  • Tourne-toi vers un index couvrant seulement quand un mode fréquent lit quelques champs de gros items ; pèse d'abord le coût en écriture/stockage.

Construis la ProjectionExpression et sa table de noms d'attributs dans le générateur d'expressions, et essaie DynoTable pour exécuter ces projections contre tes propres tables et regarder la réponse rétrécir.


  1. Guide du développeur AWS DynamoDB, Using projection expressions in DynamoDB — la capacité de lecture se calcule sur la taille de l'élément, avant toute application d'une ProjectionExpression. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html
  2. Guide du développeur AWS DynamoDB, Reserved Words in DynamoDB. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html
  3. Guide du développeur AWS DynamoDB, Attribute Projections (KEYS_ONLY / INCLUDE / ALL). https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html

Mis à jour