Traitement des requêtes sans optimisation PREWHERE
① La requête inclut un filtre sur la colonne
town, qui fait partie de la clé primaire de la table et donc aussi de l’index primaire.
② Pour accélérer la requête, ClickHouse charge l’index primaire de la table en mémoire.
③ Il parcourt les entrées de l’index afin d’identifier quels granules de la colonne town peuvent contenir des lignes correspondant au prédicat.
④ Ces granules potentiellement pertinents sont chargés en mémoire, ainsi que les granules des autres colonnes nécessaires à la requête, alignés de façon positionnelle.
⑤ Les filtres restants sont ensuite appliqués lors de l’exécution de la requête.
Comme vous pouvez le voir, sans PREWHERE, toutes les colonnes potentiellement pertinentes sont chargées avant le filtrage, même si seules quelques lignes correspondent réellement.
Comment PREWHERE améliore l’efficacité des requêtes
① La requête inclut un filtre sur la colonne
town, qui fait partie de la clé primaire de la table — et donc aussi de l’index primaire.
② Comme dans l’exécution sans clause PREWHERE, pour accélérer la requête, ClickHouse charge l’index primaire en mémoire,
③ puis parcourt les entrées de l’index pour identifier quels granules de la colonne town peuvent contenir des lignes correspondant au prédicat.
Grâce à la clause PREWHERE, l’étape suivante est différente : au lieu de lire d’emblée toutes les colonnes pertinentes, ClickHouse filtre les données colonne par colonne et ne charge que ce qui est réellement nécessaire. Cela réduit drastiquement les E/S, en particulier pour les tables larges.
À chaque étape, il ne charge que les granules qui contiennent au moins une ligne ayant passé — c’est-à-dire correspondant à — au filtre précédent. Par conséquent, le nombre de granules à charger et à évaluer pour chaque filtre diminue de façon monotone :
Étape 1 : Filtrage par villeClickHouse commence le traitement PREWHERE en ① lisant les granules sélectionnés de la colonne
town et en vérifiant lesquels contiennent réellement des lignes correspondant à London.
Dans notre exemple, tous les granules sélectionnés correspondent, donc ② les granules correspondants, alignés positionnellement, de la colonne du filtre suivant — date — sont alors sélectionnés pour traitement :
Étape 2 : Filtrage par date
Ensuite, ClickHouse ① lit les granules sélectionnés de la colonne
date pour évaluer le filtre date > '2024-12-31'.
Dans ce cas, deux granules sur trois contiennent des lignes correspondantes, donc ② seuls leurs granules, alignés positionnellement, de la colonne du filtre suivant — price — sont sélectionnés pour la suite du traitement :
Étape 3 : Filtrage par prix
Enfin, ClickHouse ① lit les deux granules sélectionnés de la colonne
price pour évaluer le dernier filtre price > 10_000.
Un seul des deux granules contient des lignes correspondantes, donc ② seul son granule, aligné positionnellement, de la colonne du SELECT — street — doit être chargé pour la suite du traitement :
À l’étape finale, seul l’ensemble minimal de granules de colonnes, c’est-à-dire ceux qui contiennent des lignes correspondantes, est chargé. Cela se traduit par une utilisation mémoire plus faible, moins d’E/S disque et une exécution plus rapide des requêtes.
PREWHERE réduit les données lues, pas le nombre de lignes traitéesNotez que ClickHouse traite le même nombre de lignes dans les versions de la requête avec et sans PREWHERE. En revanche, lorsque les optimisations PREWHERE sont appliquées, il n’est pas nécessaire de charger toutes les valeurs de colonnes pour chaque ligne traitée.
L’optimisation PREWHERE est appliquée automatiquement
optimize_move_to_prewhere est activé (true par défaut), ClickHouse déplace automatiquement les conditions de filtre de WHERE vers PREWHERE, en donnant la priorité à celles qui réduisent le plus le volume de lecture.
L’idée est que les colonnes les plus petites sont plus rapides à parcourir et qu’au moment où les colonnes plus volumineuses sont traitées, la plupart des granules ont déjà été filtrés. Comme toutes les colonnes ont le même nombre de lignes, la taille d’une colonne est principalement déterminée par son type de données ; par exemple, une colonne UInt8 est généralement bien plus petite qu’une colonne String.
ClickHouse applique cette stratégie par défaut à partir de la version 23.2, en triant les colonnes de filtre PREWHERE pour un traitement en plusieurs étapes par ordre croissant de taille non compressée.
À partir de la version 23.11, des statistiques de colonnes facultatives peuvent encore améliorer ce mécanisme en choisissant l’ordre de traitement des filtres en fonction de la sélectivité réelle des données, et pas seulement de la taille des colonnes.
Comment mesurer l’impact de PREWHERE
optimize_move_to_prewhere setting est activé ou non.
Nous commençons par exécuter la requête avec le paramètre optimize_move_to_prewhere désactivé :
optimize_move_to_prewhere activé. (Notez que ce paramètre est facultatif, car il est activé par défaut) :
Points clés
- PREWHERE évite de lire des données de colonnes qui seront ensuite éliminées par le filtrage, ce qui permet d’économiser des E/S et de la mémoire.
- Il fonctionne automatiquement lorsque
optimize_move_to_prewhereest activé (par défaut). - L’ordre du filtrage est important : les petites colonnes très sélectives doivent être placées en premier.
- Utilisez
EXPLAINet les logs pour vérifier que PREWHERE est bien appliqué et en comprendre l’effet. - PREWHERE est particulièrement efficace sur les tables comportant de nombreuses colonnes et lors de scans volumineux avec des filtres sélectifs.