Query vs Scan dans DynamoDB
Query lit une seule collection d'items par clé de partition (en restreignant
éventuellement la clé de tri) ; Scan lit la table entière et filtre ensuite.
Ils se ressemblent dans l'API mais ils facturent — et passent à l'échelle —
complètement différemment.
Quand dois-je utiliser Query plutôt que Scan dans DynamoDB ?
Utilise Query dès que tu peux nommer la partition dont tu as besoin — il lit une seule collection d'items et ne facture que les items correspondants. Recours à Scan uniquement pour des exports ponctuels ou de petites tables ; il lit chaque item et facture la table entière avant même qu'un FilterExpression s'exécute. Sur des données réelles, Query l'emporte.
- Query est ciblée : tu paies pour les items de la partition correspondante.
- Scan est exhaustif : tu paies pour lire chaque item, puis tu en jettes la
plupart avec un
FilterExpressionqui s'exécute après que la lecture a été facturée.
Sur une table d'une taille réelle, un Scan avec un filtre est le piège classique
du « pourquoi ma facture est-elle énorme et ma latence pire que RDS ».
Côte à côte
| Query | Scan | |
|---|---|---|
| Lectures | Une partition (par PK) | Chaque item de la table |
| Capacité facturée | Items correspondants dans la partition | Table entière, avant filtrage |
FilterExpression | Appliqué après la lecture — toujours facturé pour la lecture | Idem — le filtrage ne réduit jamais le coût |
| Latence | Constante quand la table grandit | Croît avec la taille de la table |
| Pagination | 1 MB/page → LastEvaluatedKey | 1 MB/page ; parallélisable |
| À utiliser pour | Modes d'accès connus | Exports ponctuels, petites tables de config |
Le piège clé : un FilterExpression s'exécute après que DynamoDB a facturé la
lecture, sur les deux opérations. Un Scan qui « renvoie 10 lignes » peut facturer
la lecture d'un million — le filtrage est un confort, jamais un contrôle de coût.
Ce qu'un Scan complet coûte réellement
Mettons des chiffres dessus. DynamoDB comptabilise les lectures par unités de 4 Ko :
une lecture coûte une
par 4 Ko, une lecture à
la moitié. Query et Scan
additionnent la taille de chaque item qu'ils touchent — pas de chaque item
qu'ils renvoient — et arrondissent au multiple de 4 Ko supérieur.
Prends une table d'un million d'items à 2 Ko par item en moyenne (~2 Go de données), et un mode d'accès qui a besoin de 10 de ces items :
| Items lus | Données comptabilisées | Unités de lecture (cohérence à terme) | |
|---|---|---|---|
Scan + FilterExpression | 1 000 000 | ~2 Go | ~262 000 |
Query sur une clé correspondante | 10 | 20 Ko | 3 |
Les mêmes 10 items, cinq ordres de grandeur d'écart — et le Scan facture ces
~262 000 à chaque exécution, que le filtre corresponde à dix items ou à aucun.
En facturation , ce sont des unités de requête qui
atterrissent directement sur la facture ; sur des tables
, un gros Scan entre en concurrence avec le trafic
de production pour le débit et peut le throttler jusqu'à déclencher des
ProvisionedThroughputExceededException.
Trois autres réalités de coût qui surprennent :
Select: COUNTn'est pas gratuit. Un Query ou un Scan de comptage consomme exactement la même capacité de lecture que la lecture des items — il ne les renvoie simplement pas.Limitplafonne les items évalués, pas les items correspondants. Combiné à un filtre, une page peut revenir vide tout en facturant quand même une pleine page de lectures.- Tu n'as jamais à deviner. Passe
ReturnConsumedCapacity: TOTALet chaque réponse indique la capacité qu'elle vient de consommer.
Vérifie ce que pèsent tes propres items avec le calculateur de taille d'item, puis convertis les unités de lecture en facture mensuelle avec le calculateur de prix.
Utilise Query
Query PK = "USER#42" AND SK begins_with "ORDER#"
Si tu te surprends à recourir à Scan pour répondre à un mode d'accès courant,
c'est un signal de modélisation : ajoute un
Global Secondary Index pour que le pattern devienne un
Query.
Le choix se résume à une seule question — peux-tu nommer la partition dont tu as besoin ?
Si la clé est connue, tu fais un Query ; sinon, ajoute un GSI pour en faire un, et
ne te rabats sur un Scan que lorsqu'aucune clé ne convient.
Quand Scan convient
Exports ponctuels, petites tables de config et jobs en arrière-plan qui parcourent
délibérément toute la table. Utilise Segment/TotalSegments pour répartir un
Scan sur plusieurs workers (un — voir
les scans parallèles dans DynamoDB) quand tu
dois vraiment tout lire, et pagine-le proprement avec LastEvaluatedKey
(la pagination). Si un Scan que tu exécutes déjà est le
problème, pourquoi Scan est lent et cher
déroule le triage.
Un SELECT * FROM table réflexe sur DynamoDB est le même anti-pattern en habit
PartiQL — il se compile en un Scan. Quand tu as vraiment besoin d'analytique
inter-items (un GROUP BY, un JOIN, un agrégat), le Workbench SQL de DynoTable
les exécute côté client sur un ensemble de résultats borné au lieu de marteler la
table.
Essaie DynoTable pour exécuter et inspecter ces requêtes sur tes propres tables — il affiche la capacité consommée de chaque opération qu'il exécute.