Skip to main content
Ce guide présente deux approches d’optimisation appliquées au jeu de données des taxis de NYC. Il réduit d’abord la quantité de données stockées et traitées en choisissant des types de colonnes plus précis. Il introduit ensuite une clé de tri permettant à ClickHouse d’ignorer des données lors de requêtes sélectives. Chaque modification est mesurée par rapport à la même référence. Consultez la vue d’ensemble de l’optimisation des requêtes pour découvrir le processus global suivi dans cet exemple.

Avant de commencer

Les exemples utilisent la table nyc_taxi.trips_small_inferred. Créez-la et chargez-la si ce n’est pas déjà fait :
Le fichier Parquet source fait environ 5,8 Go. Son chargement peut prendre plusieurs minutes, selon votre réseau et les ressources disponibles.
Le fichier Parquet source contient environ 329 millions de lignes. Les mesures de durée indiquées dans ce guide ont été effectuées sur un déploiement et varient selon les ressources de calcul disponibles. Comparez les variations relatives entre les étapes plutôt que de vous attendre à des durées identiques. Lorsque vous appliquez cette méthode à votre propre charge de travail, utilisez Diagnostiquer les requêtes lentes pour identifier un schéma de requête récurrent et choisir une exécution représentative avant de modifier la requête ou le schéma.

Vue d’ensemble du processus

L’exemple se déroule en trois étapes :
  1. Exécutez trois requêtes de charge indépendantes sur le schéma inféré afin d’établir une référence.
  2. Créez une table avec des types de colonnes plus précis, chargez les mêmes données, puis réexécutez les requêtes.
  3. Créez une autre table avec le même schéma optimisé et une clé de tri, puis réexécutez les requêtes.
Modifier le schéma et la clé de tri à des étapes distinctes permet de mieux distinguer leurs effets. Approches d’optimisation explique quand envisager ces modifications et comment les valider. Pour plus d’informations sur la collecte de mesures comparables, consultez Isoler les goulots d’étranglement des requêtes.

Définir la charge de travail de référence

Dans la même session cliente que celle utilisée pour exécuter la charge de travail, désactivez le cache du système de fichiers pour les données distantes, le cache de requêtes et le cache des conditions de requête :
Ces paramètres permettent de garantir la comparabilité des exécutions répétées lors des tests. Restaurez leurs valeurs précédentes une fois les mesures terminées.
Les trois requêtes indépendantes suivantes constituent la charge de travail de référence. Exécutez-les toutes les trois sur chaque table créée aux étapes suivantes. Exécutez chaque requête plusieurs fois dans des conditions comparables et consignez une durée représentative, telle que la médiane, ainsi que le nombre de lignes lues et le pic d’utilisation de la mémoire. Consultez Établir une référence reproductible pour connaître le processus de mesure complet, notamment comment récupérer ces valeurs depuis system.query_log.

Filtrer selon la vitesse calculée du trajet

Cette requête calcule la durée et la vitesse du trajet, puis détermine la répartition des distances pour les courses dont la vitesse dépasse 30 miles par heure :

Agréger les trajets sur une plage de dates

Cette requête calcule le nombre de trajets, la distance et le montant moyen des paiements pour le premier trimestre 2009 :

Filtrer par nombre de passagers

Cette requête calcule la durée moyenne des trajets avec un ou deux passagers :
Les mesures initiales étaient : Les trois requêtes ont lu environ 329 millions de lignes, soit un nombre proche du nombre de lignes de la table. Cela permet d’améliorer deux aspects de la charge de travail : réduire le coût de traitement des colonnes sélectionnées, puis, lorsque les filtres le permettent, réduire le nombre de lignes sélectionnées.

Optimiser le schéma

L’inférence de schéma est un moyen pratique pour commencer à explorer un jeu de données, mais les types inférés peuvent être plus généraux ou plus permissifs que ne l’exige la charge de travail. Examinez les données avant de modifier le schéma, plutôt que de supposer qu’un type inféré est inutile.

Évitez les colonnes Nullable inutiles

Une colonne Nullable stocke un masque de nullité en plus de ses valeurs. Conservez Nullable lorsque la distinction entre une valeur nulle et la valeur par défaut du type est pertinente, mais évitez-le pour les colonnes dont la présence d’une valeur est garantie. Comptez les valeurs nulles dans les colonnes utilisées par le schéma d’exemple :
Seules les colonnes ratecode_id, mta_tax et payment_type contiennent des valeurs nulles dans cet ensemble de données. Le schéma optimisé conserve Nullable pour ces colonnes et le supprime pour les autres.

Utiliser LowCardinality pour les valeurs répétées

LowCardinality utilise l’encodage par dictionnaire et peut réduire le stockage et le traitement des colonnes contenant de nombreuses valeurs répétées. Vérifiez le nombre de valeurs distinctes avant de l’appliquer :
Ces quatre colonnes contiennent nettement moins de valeurs distinctes que de lignes. Elles sont de bonnes candidates pour LowCardinality, mais il convient tout de même d’en mesurer l’effet sur la charge de travail. Environ 10 000 valeurs distinctes constituent un bon point de départ pour identifier les candidates, et non une limite fixe.

Choisissez des types de données plus précis

Utilisez le type le plus restrictif permettant de préserver la plage et la précision requises. Par exemple, examinez les valeurs minimales et maximales des colonnes numériques avant de remplacer un Int64 ou un Float64 inféré :
Les deux colonnes d’entiers peuvent être stockées dans UInt8, même si passenger_count atteint sa valeur maximale de 255. L’exemple utilise également Float32 pour trip_distance et Decimal32 pour les valeurs monétaires. Toutes les valeurs de cet ensemble de données se situent dans les plages cibles, et l’exemple accepte la précision réduite des nombres à virgule flottante ainsi qu’une précision monétaire au centime, car sa charge de travail compare des résultats agrégés. Conservez les types source plus larges lorsque les valeurs source exactes sont nécessaires. L’exemple remplace les colonnes DateTime64 déduites par DateTime dans le même fuseau horaire UTC, car les requêtes de l’exemple ne nécessitent pas de précision à la fraction de seconde. Ces choix sont spécifiques à cet ensemble de données. Vérifiez les exigences de plage, de précision et de nullabilité des données de production avant d’appliquer les mêmes modifications.

Appliquer les modifications du schéma

Créez une table sans clé de tri afin que cette étape mesure indépendamment les modifications du schéma :
Dans chaque requête de charge, remplacez nyc_taxi.trips_small_inferred par nyc_taxi.trips_small_no_pk, puis réexécutez les trois requêtes. L’exemple d’origine a donné les résultats représentatifs suivants : Les requêtes lisent toujours le même nombre de lignes, mais le schéma optimisé réduit la quantité de données représentée par ces lignes. La durée des requêtes et le pic de mémoire s’améliorent donc sans modifier la sélection des données. Comparez la taille sur disque des deux tables :
Pour ce jeu de données, le schéma optimisé réduit le stockage compressé d’environ 34 %, le faisant passer de 7,38 Gio à 4,89 Gio.

Optimiser la clé de tri

Dans la famille MergeTree, la clé de tri détermine la façon dont les lignes sont organisées sur le disque. ClickHouse crée un index primaire creux sur cet ordre afin d’ignorer les granules qui ne peuvent pas répondre aux filtres d’une requête. Contrairement à une clé primaire dans de nombreuses bases de données transactionnelles, elle ne garantit pas l’unicité. La clé de tri doit refléter les filtres utilisés dans les requêtes importantes et récurrentes. L’ordre des colonnes est important : une clé est plus efficace lorsque la requête filtre sur un préfixe utile. Les colonnes à faible cardinalité constituent parfois de bonnes premières colonnes lorsqu’elles sont fréquemment utilisées comme filtres, et une composante temporelle est souvent utile pour les charges de travail basées sur le temps. Pour des conseils détaillés sur le choix de la clé, consultez Choisir une clé primaire. Pour cet exemple, utilisez (passenger_count, pickup_datetime, dropoff_datetime). passenger_count possède peu de valeurs distinctes et apparaît dans le filtre sur le nombre de passagers, tandis que pickup_datetime apparaît dans l’agrégation sur une plage de dates. Bien que pickup_datetime ne soit pas la première colonne, ClickHouse peut toujours utiliser les valeurs des colonnes suivantes de la clé pour exclure des données lorsque la première colonne n’est soumise à aucune contrainte. Le filtrage sur un préfixe utile de la clé de tri permet généralement un élagage plus efficace.

Appliquer la modification de la clé de tri

Créez une table en utilisant le même schéma optimisé que lors de l’étape précédente. Modifiez uniquement la clé de tri :
Dans chaque requête de charge, remplacez le nom de la table par nyc_taxi.trips_small_pk, puis réexécutez les trois requêtes.

Comparez les résultats

Le guide original a relevé les mesures suivantes pour les trois étapes : L’optimisation du schéma réduit l’espace de stockage et rend le traitement des valeurs sélectionnées moins coûteux. La clé de tri apporte l’amélioration supplémentaire la plus importante pour l’agrégation sur une plage de dates, car ClickHouse peut ignorer les granules en dehors de cette plage. Le filtre sur le nombre de passagers lit également moins de lignes, car il s’applique à la première colonne de la clé. Le filtre sur la vitesse calculée lit toujours l’intégralité de la table, car il est dérivé de pickup_datetime, dropoff_datetime et trip_distance, plutôt que d’un préfixe utile de la clé de tri. Examinez l’agrégation sur une plage de dates avec EXPLAIN indexes = 1 :
Dans ClickHouse 25.9 et les versions ultérieures, ces paramètres garantissent que EXPLAIN indique les index utilisés ainsi que les parties et les granules qu’ils permettent d’éliminer.
L’index primaire sélectionne 5 061 granules sur 40 167. Grâce à cette réduction, l’agrégation sur la plage de dates traite 41,46 millions de lignes au lieu des 329,04 millions initiales.

Appliquez la méthode à votre charge de travail

Suivez la même démarche pour votre propre charge de travail :
  1. Relevez la durée de référence, le nombre de lignes et d’octets lus, ainsi que le pic de mémoire.
  2. Vérifiez si les colonnes sélectionnées utilisent des types inutilement larges ou permissifs.
  3. Appliquez les modifications de schéma et mesurez leurs effets sans modifier la disposition des données.
  4. Testez une clé de tri fondée sur les filtres utilisés par les requêtes importantes et récurrentes.
  5. Comparez les données sélectionnées avec EXPLAIN indexes = 1, puis réexécutez les requêtes de référence dans des conditions comparables.
Ne supposez pas que les types ou la clé de tri de cet exemple conviendront à un autre jeu de données. Appuyez-vous sur les valeurs observées et les filtres de requête pour prendre ces décisions.

Étapes suivantes

Revenez aux Approches d’optimisation pour évaluer les projections, les vues matérialisées, les index d’exclusion de données ou le précalcul, lorsque les modifications du schéma et de la clé de tri ne permettent pas de résoudre le goulot d’étranglement identifié.
Dernière modification le 28 août 2026