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.
ProjectionExpressionest 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
#namepour 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
Queryqui 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.
ProjectionExpression | Covering GSI | |
|---|---|---|
| Rogne le payload | Oui | Oui |
| Rogne le coût de lecture | Non | Oui — lu à la taille de l'index |
| Stockage supplémentaire | Aucun | Une seconde copie des champs projetés |
| Coût d'écriture supplémentaire | Aucun | Les écritures se propagent vers l'index |
| Idéal pour | Cacher des champs privés ; petits gains | Lectures 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
nameoustatusnu 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.
- 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/ ↩ - AWS DynamoDB Developer Guide, Reserved Words in DynamoDB. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html ↩
- AWS DynamoDB Developer Guide, Attribute Projections (
KEYS_ONLY/INCLUDE/ALL). https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html ↩