Intermédiaire9 min de lecture

Les stratégies de filtrage DynamoDB

« Filtrer » dans DynamoDB désigne quatre choses différentes portant le même mot. Trois réduisent les données avant qu'elles soient lues et facturées ; une — celle qui s'appelle Filter — les réduit après. Savoir laquelle est laquelle constitue l'essentiel de la compétence.

Comment fonctionne le filtrage dans DynamoDB ?

DynamoDB a quatre façons de filtrer, et une seule s'exécute après que tu es facturé. La choisit une partition, la clé de tri réduit une tranche, et un index clairsemé filtre par présence d'attribut — les trois réduisent ton coût de lecture avant le métrage. Une FilterExpression s'exécute après la lecture, donc elle réduit la réponse mais jamais la facture.

  • La est le filtre le moins cher : elle choisit la partition, donc tu ne touches jamais le reste de la table.
  • La filtre au sein d'une partition avec begins_with, between, <, > — toujours avant la facturation, toujours bon marché.
  • L' filtre par absence : un item n'apparaît dans l'index que s'il possède l'attribut indexé, donc l'index est l'ensemble filtré.
  • La FilterExpression est le piège : elle s'exécute après que DynamoDB métre la lecture, donc elle réduit la taille de ta réponse mais jamais ta facture.

Mettre en place l'exemple

Un catalogue de produits. Une table, clé de partition PK, clé de tri SK :

PK = "DEPT#kitchen"   SK = "PROD#00194"

Chaque produit porte aussi price, inStock (un booléen) et clearanceAt (un horodatage unix, présent uniquement sur les items marqués pour déstockage). Les items d'un rayon partagent une partition, triés par id de produit.

Nous voulons quatre motifs d'accès. Chacun correspond à une stratégie de filtrage différente — et le mauvais choix sur l'un d'eux est un Scan que tu paieras pour toujours.

Filtrer par clé de partition

« Donne-moi tous les produits du rayon kitchen. » La clé de partition y répond directement :

Query  PK = "DEPT#kitchen"

DynamoDB lit exactement une partition. Rien d'autre dans la table n'est touché ni facturé. C'est le seul filtre gratuit au sens qui compte — c'est la différence entre Query et Scan.

En venant de SQL, cela paraît à l'envers : il n'y a pas de WHERE department = 'kitchen' qui balaie un index, tu nommes simplement la partition. Si tu ne peux pas la nommer, c'est un problème de modélisation, pas un problème de requête.

Filtrer par clé de tri

« Donne-moi les produits du rayon kitchen à partir de PROD#00100. » La clé de tri réduit à l'intérieur de la partition, et elle le fait avant que la lecture soit métrée :

Query  PK = "DEPT#kitchen"  AND  SK between "PROD#00100" AND "PROD#00200"

Les conditions de clé de tri sont limitées à dessein : =, <, <=, >, >=, between et begins_with. Pas de OR, pas de prédicat arbitraire.

Cette contrainte est ce qui garde la lecture ciblée — DynamoDB parcourt une tranche contiguë, pas toute la partition.

Le levier ici est comment tu encodes la clé de tri. Si ton motif est « par tranche de prix », une clé de tri PROD#<id> n'aidera pas — tu encoderais le prix dans la clé.

C'est une décision de stratégie de clé de tri, prise au moment de la conception, pas au moment de la requête.

Filtrer par index clairsemé

« Donne-moi tout ce qui est actuellement en déstockage. » La plupart des produits ne le sont pas, donc tu ne veux pas lire le catalogue pour trouver les rares qui le sont.

Un index clairsemé résout cela par l'absence. Un ne contient un item que si cet item possède les deux attributs de clé de l'index.

Définis un drapeau constant clearance = "CLEARANCE" comme clé de partition du GSI — écrit uniquement sur les items en déstockage — avec clearanceAt comme clé de tri, et l'index ne contient rien d'autre.

AWS le précise : un index secondaire global ne contient que les items qui ont les attributs de clé de l'index, donc les items dépourvus de l'attribut de clé ne sont simplement pas propagés (AWS — Tirer parti des index clairsemés).

OuiNonTable de base tous les produitsclearanceAt présent ?Répliqué vers ClearanceIndexAbsent de l'indexQuery l'index = items endéstockage seuls

Maintenant la requête ne lit que les items en déstockage, facturés seulement pour eux :

Query  ON ClearanceIndex   GSI_PK = "CLEARANCE"   (sorted by clearanceAt)

Le filtre s'est produit quand tu as écrit les données — en choisissant de définir ou non clearanceAt. L'index est l'ensemble filtré. Voir GSI vs LSI pour savoir quel type d'index convient.

Filtrer avec FilterExpression

« Donne-moi les produits du rayon kitchen qui sont en stock. » inStock n'est pas un attribut de clé, donc tu te tournes vers une FilterExpression :

Query  PK = "DEPT#kitchen"
Filter inStock = true

Voici le piège. DynamoDB lit chaque item de la partition kitchen, métre la capacité pour tous, et ensuite écarte ceux qui sont en rupture de stock.

La règle officielle : une expression de filtre est « appliquée après qu'une Query se termine, mais avant que les résultats soient renvoyés », et « une Query consomme la même quantité de capacité de lecture, qu'une expression de filtre soit présente ou non » — tu as déjà payé pour la lecture complète (AWS — Expressions de filtre pour Query).

Donc si kitchen compte 10 000 produits et que 12 sont en stock, tu paies pour lire 10 000. La réponse est petite ; la facture ne l'est pas. FilterExpression réduit la charge utile qui traverse le fil, jamais la lecture.

Il y a une seconde arête, plus tranchante : la pagination est métrée avant le filtrage. Une page fait 1 Mo d'items lus, pas 1 Mo de correspondances.

Un filtre peut renvoyer une page vide avec un LastEvaluatedKey défini — DynamoDB a lu un mégaoctet complet, n'a rien fait correspondre, t'a rendu un tableau vide. Tu continues de paginer, et tu as payé pour chaque page vide.

Construis l'expression — noms, valeurs et le bon échappement de mot réservé — avec le Générateur d'expressions DynamoDB pour que les espaces réservés #inStock/:val soient corrects du premier coup.

Le générateur ci-dessous est préréglé sur un Scan avec une FilterExpression — l'exact anti-motif ci-dessus. Remarque que le filtre s'exécute sur toute la table, pas sur une tranche de clé :

Construis ta requête
Code généré
new ScanCommand({
  "TableName": "AuditLog",
  "FilterExpression": "#filter0 = :filterValue0",
  "ExpressionAttributeNames": {
    "#filter0": "action"
  },
  "ExpressionAttributeValues": {
    ":filterValue0": {
      "S": "delete"
    }
  }
})

Comparer les quatre

Quand ça filtreRéduit le coût de lecture ?Puissance du prédicatCoût de mise en place
Clé de partitionAvant la lectureOui — une partitionÉgalité uniquementGratuit (c'est la clé)
Clé de triAvant la lectureOui — une tranchePlage / begins_withConception de la clé de tri
Index clairseméAvant la lectureOui — index seulementPrésence d'un attributGSI supplémentaire + coût d'écriture
FilterExpressionAprès la lectureNonPresque n'importe quelle conditionAucun

Lis le tableau de haut en bas : la puissance du prédicat monte, le contrôle du coût descend. FilterExpression peut tout exprimer précisément parce qu'elle s'exécute sur des items déjà lus — c'est pour la même raison qu'elle ne peut pas t'économiser d'argent.

Vois-le dans DynoTable

Quand tu exécutes une Query avec un filtre, l'écart entre les items lus et les items renvoyés est toute l'histoire. DynoTable affiche les items balayés à côté des items renvoyés à mesure qu'une lecture filtrée arrive en flux — ainsi un filtre qui lit discrètement toute la partition est visible, et non caché dans ta facture mensuelle.

Pour de véritables questions inter-items qu'un filtre ne peut pas résoudre — « prix moyen par rayon », « produits en stock joints à leurs avis » — le SQL Workbench de DynoTable exécute GROUP BY, JOIN et des agrégats côté client sur un ensemble de résultats borné, au lieu de compiler vers un Scan sur toute la table.

Pièges et étapes suivantes

  • N'utilise pas FilterExpression comme chemin d'accès principal. Si un motif est courant, modélise-le dans une clé ou un index clairsemé. Un filtre sert au dernier petit bout d'affinage, pas à l'essentiel.
  • Surveille les pages vides. Une requête filtrée peut paginer longtemps en ne renvoyant rien. Honore LastEvaluatedKey ; ne suppose pas qu'une page vide signifie « terminé ».
  • Un index clairsemé n'est pas gratuit. Il coûte de la capacité d'écriture et du stockage pour chaque item qui y atterrit — bon marché quand l'attribut est rare, moins quand il ne l'est pas.

Estime ce qu'une lecture filtrée coûtera réellement avec le calculateur de tarification, et essaie DynoTable pour observer la capacité consommée face aux lignes renvoyées sur tes propres tables.

Mis à jour