Intermédiaire7 min de lecture

Expressions de condition de clé DynamoDB

Une expression de condition de clé est la KeyConditionExpression que tu passes à un Query — la seule partie de la requête que DynamoDB utilise pour trouver les items. Tout le reste (filtres, projections) s'exécute après que la lecture est déjà mesurée.

Qu'est-ce qu'une expression de condition de clé dans DynamoDB ?

Une expression de condition de clé est la KeyConditionExpression d'un Query qui indique à DynamoDB quels items lire. La doit être une égalité (PK = :v) ; la prend un opérateur de plage — =, <, <=, >, >=, BETWEEN ou begins_with. Elle décide ce qui est lu et facturé, contrairement à un filtre.

  • La doit être une égalité. PK = :v et rien d'autre — pas de plages, pas de begins_with, pas de IN. DynamoDB la hache pour localiser une partition.
  • La prend un opérateur de plage. =, <, <=, >, >=, BETWEEN ou begins_with — c'est là que tu découpes une .
  • Ce n'est pas un filtre. Une condition de clé décide ce qui est lu et facturé ; une FilterExpression ne fait que réduire le résultat après que tu as payé la lecture.
  • Les clés de tri sont ordonnées par octets. Les opérateurs de plage comparent lexicographiquement, donc la façon dont tu formates la chaîne de clé de tri est ta puissance de requête.

Pourquoi la clé de partition est verrouillée à l'égalité

DynamoDB stocke les items en hachant la clé de partition vers une partition physique. Un hachage te donne un emplacement, pas une plage — donc il n'y a rien à scanner à travers.

C'est pourquoi PK > :v ou begins_with(PK, :v) sont rejetés d'emblée. Le moteur ne peut pas répondre à « toutes les partitions dont la clé commence par X » sans lire la table entière, ce qui est exactement le Scan qu'il est conçu pour éviter.

Quand tu viens du SQL, ça paraît à l'envers : WHERE id LIKE 'order%' est trivial dans Postgres. Dans DynamoDB, la clé de partition est une adresse, pas une colonne interrogeable.

La clé de tri est là où la puissance réside

Au sein d'une partition, les items sont stockés triés par la clé de tri. Cet ordre est ce que les opérateurs de plage exploitent — DynamoDB se positionne sur un point et lit vers l'avant.

OpérateurLitÀ utiliser pour
SK = :vUn item exactUn enfant précis par sa clé
SK < / <= / > / >= :vUne tranche ouverte« Tout après ce point »
SK BETWEEN :a AND :bUne plage fermée (inclusive)Une fenêtre bornée — une plage de dates
begins_with(SK, :p)Une tranche par préfixeUn type ou une hiérarchie sous la PK

Il n'y a pas de LIKE, pas de CONTAINS, pas de ENDS_WITH sur la clé. La correspondance de sous-chaîne et de suffixe n'est pas ordonnée par octets, donc elle forcerait une lecture complète — par conception, l'API ne te le permet pas. La correspondance de sous-chaîne existe via contains() dans une FilterExpression (où tu as déjà payé la lecture) ; la correspondance de suffixe n'est pas disponible côté serveur du tout — stocke une clé inversée ou filtre côté client. (AWS : Key condition expressions)

Un exemple concret : les messages d'une app de chat

Disons que tu construis un chat par canaux. Une table, partitionnée par canal, triée par heure du message. Schéma de clés d'origine :

  • Clé de partition ChannelRefCH#{channelId}
  • Clé de tri PostedAt — un timestamp ISO-8601, MSG#2026-06-23T14:05:00Z

Le préfixe MSG# garde les lignes de message triables et distinctes de tout autre type de ligne que tu pourrais co-localiser sous le même canal (config épinglée, appartenance).

Charger les derniers messages d'un canal. Juste la clé de partition, plus récents d'abord :

KeyConditionExpression      ChannelRef = :ch
ExpressionAttributeValues   { ":ch": "CH#general" }
ScanIndexForward            false

ScanIndexForward: false parcourt la collection triée à l'envers — le moyen bon marché d'obtenir « les plus récents d'abord » sans trier côté client.

Un jour précis avec begins_with. Parce que le timestamp est la clé de tri et qu'il est stocké comme texte, un préfixe de date est une tranche nette :

KeyConditionExpression  ChannelRef = :ch AND begins_with(PostedAt, :day)
:ch    "CH#general"
:day   "MSG#2026-06-23"

Ça lit chaque message du 2026-06-23 et rien d'autre — DynamoDB se positionne sur le préfixe et s'arrête quand il en sort. Ça ne marche que parce que le préfixe est un vrai ancrage à gauche d'une chaîne triée par octets.

Une fenêtre précise avec BETWEEN. Pour « les messages pendant l'heure de 14:00 », une plage inclusive bat un préfixe :

KeyConditionExpression  ChannelRef = :ch AND PostedAt BETWEEN :lo AND :hi
:ch    "CH#general"
:lo    "MSG#2026-06-23T14:00:00Z"
:hi    "MSG#2026-06-23T14:59:59Z"

BETWEEN est inclusif sur les deux bornes, alors choisis tes points d'extrémité délibérément — un décalage d'un ici laisse tomber ou double silencieusement un message de bord.

Tu peux assembler et copier n'importe laquelle de ces expressions, avec la map ExpressionAttributeValues remplie pour toi, dans l' Expression Builder DynamoDB — pratique pour obtenir la syntaxe begins_with et BETWEEN correcte du premier coup.

Ce builder est préréglé sur une requête pk = … AND begins_with(sk, …) — change l'opérateur pour voir la KeyConditionExpression se mettre à jour :

Construis ta requête
Code généré
new QueryCommand({
  "TableName": "AuditLog",
  "KeyConditionExpression": "#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)",
  "ExpressionAttributeNames": {
    "#hashKey": "pk",
    "#rangeKey": "sk"
  },
  "ExpressionAttributeValues": {
    ":hashKeyValue": {
      "S": "TENANT#acme"
    },
    ":rangeKeyValue": {
      "S": "EVENT#2026-06"
    }
  }
})

Vois-le dans DynoTable

Exécute la même condition de clé contre une vraie partition de canal. Dès l'instant où tu définis un filtre sur la clé de partition, DynoTable émet un Query — donc tu charges seulement cette tranche, pas la collection entière.

Le piège : confondre une condition de clé avec un filtre

L'erreur coûteuse est de recourir à FilterExpression pour faire le travail d'une clé. Un filtre ne peut même pas référencer PostedAt — c'est la clé de tri, et DynamoDB rejette un filtre sur un attribut-clé avec une ValidationException. Alors le contournement est de dupliquer la date dans un attribut ordinaire, non-clé (MessageDate) et de filtrer là-dessus :

KeyConditionExpression   ChannelRef = :ch
FilterExpression         begins_with(MessageDate, :day)

Ça paraît équivalent à la condition de clé begins_with ci-dessus et renvoie les mêmes lignes — mais ça lit d'abord la partition entière du canal, puis jette tout ce qui est en dehors du jour. Tu es facturé pour la lecture complète.

Les filtres ne réduisent jamais le coût de lecture. Ils s'exécutent après que DynamoDB a mesuré les items, le même piège qu'un Scan filtré. Si un prédicat peut aller dans la condition de clé, c'est là qu'il appartient.

Le correctif est en amont : si un pattern d'accès ne peut pas s'exprimer comme une égalité de PK plus une plage de clé de tri, c'est un signal de modélisation. Soit tu remodèles la clé de tri, soit tu ajoutes un index avec des clés pour le pattern — voir GSI vs LSI et single-table design pour savoir comment disposer les clés.

Pièges et étapes suivantes

  • La clé de partition est toujours =. Jamais de plages. Si tu as besoin d'une plage à travers des partitions, tu as dépassé un seul Query.
  • Une seule condition de clé de tri par requête. Tu ne peux pas AND deux prédicats de clé de tri ; choisis BETWEEN ou begins_with, pas les deux.
  • Les mots réservés ont besoin d'alias. Une clé nommée Timestamp ou Name doit utiliser ExpressionAttributeNames (#ts), sinon la requête échoue. (AWS : reserved words)
  • BETWEEN est inclusif. Les deux points d'extrémité sont matchés — conçois tes bornes en conséquence.

Rédige tes conditions de clé dans l' Expression Builder, puis essaie DynoTable pour les exécuter contre tes propres tables et voir exactement quelle tranche chaque condition de clé renvoie.

Mis à jour