Skip to main content
Cette section vise à illustrer, à travers des scénarios courants, comment utiliser différentes techniques d’optimisation et d’amélioration des performances, telles que l’analyseur, le profilage des requêtes ou éviter les colonnes Nullable, afin d’améliorer les performances de vos requêtes ClickHouse.

Comprendre les performances des requêtes

Le meilleur moment pour réfléchir à l’optimisation des performances, c’est lorsque vous concevez votre schéma de données, avant d’ingérer des données dans ClickHouse pour la première fois.  Mais soyons honnêtes : il est difficile de prévoir quelle croissance connaîtront vos données ou quels types de requêtes seront exécutés.  Si vous disposez déjà d’un déploiement avec quelques requêtes que vous souhaitez améliorer, la première étape consiste à comprendre comment elles se comportent et pourquoi certaines s’exécutent en quelques millisecondes alors que d’autres prennent plus de temps. ClickHouse propose un large éventail d’outils pour vous aider à comprendre comment votre requête est exécutée et quelles ressources elle consomme.  Dans cette section, nous allons examiner ces outils et voir comment les utiliser. 

Considérations générales

Pour comprendre les performances des requêtes, examinons ce qui se passe dans ClickHouse lorsqu’une requête est exécutée.  La partie suivante est volontairement simplifiée et prend quelques raccourcis ; l’objectif n’est pas de vous noyer dans les détails, mais de vous familiariser rapidement avec les concepts de base. Pour en savoir plus, vous pouvez consulter la page sur l’analyseur de requêtes À très haut niveau, lorsque ClickHouse exécute une requête, voici ce qui se passe : 
  • Analyse syntaxique et analyse de la requête
La requête est analysée, puis un plan d’exécution générique est créé. 
  • Optimisation de la requête
Le plan d’exécution de la requête est optimisé, les données inutiles sont écartées, et un pipeline de requête est construit à partir du plan de requête. 
  • Exécution du pipeline de requête
Les données sont lues et traitées en parallèle. C’est à cette étape que ClickHouse exécute réellement les opérations de requête telles que le filtrage, les agrégations et le tri. 
  • Traitement final
Les résultats sont fusionnés, triés et mis en forme avant d’être envoyés au client. En réalité, de nombreuses optimisations entrent en jeu, et nous les aborderons un peu plus en détail dans ce guide. Mais pour l’instant, ces grands principes suffisent à bien comprendre ce qui se passe en coulisses lorsque ClickHouse exécute une requête.  Avec cette vue d’ensemble, examinons les outils fournis par ClickHouse et la manière de les utiliser pour suivre les métriques qui influent sur les performances des requêtes. 

Jeu de données

Nous utiliserons un exemple concret pour illustrer notre approche des performances des requêtes.  Prenons le jeu de données NYC Taxi, qui contient des données sur les trajets en taxi à New York. Nous allons d’abord ingérer le jeu de données NYC Taxi sans aucune optimisation. Vous trouverez ci-dessous la commande permettant de créer la table et d’y insérer des données depuis un bucket S3. Notez que nous déduisons volontairement le schéma à partir des données, ce qui n’est pas optimisé.
Examinons le schéma de la table automatiquement inféré à partir des données.

Identifier les requêtes lentes

Journaux des requêtes

Par défaut, ClickHouse collecte et consigne des informations sur chaque requête exécutée dans les journaux des requêtes. Ces données sont stockées dans la table system.query_log Pour chaque requête exécutée, ClickHouse consigne des statistiques telles que le temps d’exécution de la requête, le nombre de lignes lues et l’utilisation des ressources, comme le CPU, l’utilisation de la mémoire ou les accès au cache du système de fichiers.  Le journal des requêtes est donc un bon point de départ pour analyser les requêtes lentes. Vous pouvez facilement repérer les requêtes dont l’exécution prend beaucoup de temps et afficher les informations d’utilisation des ressources pour chacune d’elles.  Trouvons les cinq requêtes les plus longues sur notre jeu de données des taxis de NYC.
Le champ query_duration_ms indique le temps d’exécution de cette requête précise. En examinant les résultats des journaux de requêtes, nous pouvons voir que la première requête met 2967 ms à s’exécuter, ce qui pourrait être optimisé.  Vous pouvez également chercher à savoir quelles requêtes sollicitent le plus le système en examinant celle qui consomme le plus de mémoire ou de CPU. 
Isolons les requêtes longues que nous avons identifiées et relançons-les plusieurs fois pour mieux comprendre le temps de réponse.  À ce stade, il est essentiel de désactiver le cache du système de fichiers en définissant le paramètre enable_filesystem_cache sur 0 afin d’améliorer la reproductibilité.
Résumons cela dans le tableau pour faciliter la lecture. Voyons d’un peu plus près ce que font ces requêtes. 
  • La requête 1 calcule la distribution des distances pour les trajets dont la vitesse moyenne dépasse 30 miles par heure.
  • La requête 2 calcule le nombre de trajets par semaine ainsi que leur coût moyen. 
  • La requête 3 calcule la durée moyenne de chaque trajet dans le jeu de données.
Aucune de ces requêtes n’effectue de traitement particulièrement complexe, à l’exception de la première, qui calcule la durée du trajet à la volée à chaque exécution. Cependant, chacune d’elles met plus d’une seconde à s’exécuter, ce qui, dans le monde de ClickHouse, est très long. On peut aussi noter leur consommation mémoire : environ 400 Mo par requête, ce qui est assez élevé. De plus, chaque requête semble lire le même nombre de lignes (c.-à-d. 329.04 millions). Confirmons rapidement combien de lignes contient cette table.
La table contient 329,04 millions de lignes, donc chaque requête effectue un parcours complet de la table.

Instruction EXPLAIN

Maintenant que nous disposons de quelques requêtes à longue durée d’exécution, voyons comment elles sont exécutées. Pour cela, ClickHouse prend en charge la commande EXPLAIN. Il s’agit d’un outil très utile qui offre une vue détaillée de toutes les étapes d’exécution d’une requête, sans pour autant l’exécuter réellement. Bien qu’il puisse paraître intimidant pour un non-expert de ClickHouse, il reste un outil indispensable pour comprendre comment votre requête est exécutée. La documentation propose un guide détaillé sur l’instruction EXPLAIN et sur la manière de l’utiliser pour analyser l’exécution de vos requêtes. Plutôt que de répéter ce qui y est présenté, concentrons-nous sur quelques commandes qui nous aideront à identifier les goulots d’étranglement dans les performances d’exécution des requêtes. Explain indexes = 1 Commençons par EXPLAIN indexes = 1 pour inspecter le plan de requête. Le plan de requête est un arbre représentant la façon dont la requête sera exécutée. Vous pouvez y voir dans quel ordre les clauses de la requête seront exécutées. Le plan de requête renvoyé par l’instruction EXPLAIN se lit de bas en haut. Essayons d’utiliser la première de nos requêtes longues.
Le résultat est simple à interpréter. La requête commence par lire les données de la table nyc_taxi.trips_small_inferred. La clause WHERE est ensuite appliquée pour filtrer les lignes en fonction des valeurs calculées. Les données filtrées sont préparées pour l’agrégation, et les quantiles sont calculés. Enfin, le résultat est trié et affiché. Ici, nous pouvons constater qu’aucune clé primaire n’est utilisée, ce qui est logique puisque nous n’en avons défini aucune lors de la création de la table. Par conséquent, ClickHouse effectue un parcours complet de la table pour la requête. Expliquer le pipeline EXPLAIN Pipeline affiche la stratégie d’exécution concrète de la requête. Vous pouvez ainsi voir comment ClickHouse a réellement exécuté le plan de requête générique examiné précédemment.
Ici, nous pouvons noter le nombre de threads utilisés pour exécuter la requête : 59 threads, ce qui indique une parallélisation élevée. Cela accélère la requête, qui prendrait plus de temps à s’exécuter sur une machine moins puissante. Le nombre de threads s’exécutant en parallèle peut expliquer le volume important de mémoire utilisé par la requête. Idéalement, vous devriez analyser toutes vos requêtes lentes de la même façon afin d’identifier les plans de requête inutilement complexes et de comprendre le nombre de lignes lues par chaque requête ainsi que les ressources consommées.

Méthodologie

Il peut être difficile d’identifier les requêtes problématiques sur un déploiement en production, car un grand nombre de requêtes s’exécutent probablement à tout moment sur votre déploiement ClickHouse.  Si vous savez quel utilisateur, quelle base de données ou quelles tables posent problème, vous pouvez utiliser les champs user, tables ou databases de system.query_logs pour affiner la recherche.  Une fois les requêtes à optimiser identifiées, vous pouvez commencer à travailler dessus. Une erreur fréquente à ce stade consiste à modifier plusieurs éléments en même temps, à mener des expériences ad hoc et, au final, à obtenir des résultats mitigés, mais surtout à ne pas bien comprendre ce qui a réellement accéléré la requête.  L’optimisation des requêtes exige de la méthode. Je ne parle pas de benchmarking avancé, mais le fait de disposer d’un processus simple pour comprendre comment vos modifications affectent les performances des requêtes peut faire une vraie différence.  Commencez par identifier vos requêtes lentes à partir du journal des requêtes, puis examinez les améliorations possibles de manière isolée. Lors du test de la requête, veillez à désactiver le cache du système de fichiers. 
ClickHouse s’appuie sur la mise en cache pour accélérer les performances des requêtes à différentes étapes. C’est bénéfique pour les performances des requêtes, mais pendant le dépannage, cela peut masquer d’éventuels goulots d’étranglement d’E/S ou un schéma de table inadéquat. C’est pourquoi je recommande de désactiver le cache du système de fichiers pendant les tests. Assurez-vous qu’il reste activé en production.
Une fois les optimisations potentielles identifiées, il est recommandé de les appliquer une par une afin de mieux mesurer leur impact sur les performances. Vous trouverez ci-dessous un schéma décrivant l’approche générale. Pour finir, méfiez-vous des valeurs aberrantes : il est assez courant qu’une requête s’exécute lentement, soit parce qu’un utilisateur a lancé une requête ad hoc coûteuse, soit parce que le système était fortement sollicité pour une autre raison. Vous pouvez regrouper par le champ normalized_query_hash pour identifier les requêtes coûteuses exécutées régulièrement. Ce sont probablement celles qu’il faut examiner.

Optimisation de base

Maintenant que nous disposons de notre environnement de test, nous pouvons commencer à optimiser. Le meilleur point de départ est d’examiner la manière dont les données sont stockées. Comme pour toute base de données, moins nous lisons de données, plus la requête s’exécutera rapidement.  Selon la manière dont vous avez ingéré vos données, vous avez peut-être exploité les fonctionnalités de ClickHouse pour déduire le schéma de la table à partir des données ingérées. Bien que cela soit très pratique pour se lancer, si vous souhaitez optimiser les performances de vos requêtes, vous devrez revoir le schéma des données afin de l’adapter au mieux à votre cas d’utilisation.

Nullable

Comme indiqué dans la documentation des bonnes pratiques, évitez autant que possible les colonnes Nullable. Il est tentant d’y recourir fréquemment, car elles rendent le mécanisme d’ingestion des données plus flexible, mais elles nuisent aux performances, puisqu’une colonne supplémentaire doit être traitée à chaque fois. L’exécution d’une requête SQL qui compte les lignes contenant une valeur NULL permet d’identifier facilement les colonnes de vos tables qui ont réellement besoin d’une valeur Nullable.
Nous n’avons que deux colonnes avec des valeurs nulles : mta_tax et payment_type. Les autres champs ne devraient pas utiliser de colonne Nullable.

Faible cardinalité

Une optimisation simple à appliquer aux chaînes de type String consiste à exploiter au mieux le type de données LowCardinality. Comme indiqué dans la documentation sur la faible cardinalité, ClickHouse applique un encodage par dictionnaire aux colonnes LowCardinality, ce qui améliore considérablement les performances des requêtes.  Une règle simple pour déterminer quelles colonnes se prêtent bien à LowCardinality est que toute colonne comptant moins de 10 000 valeurs uniques est une candidate idéale. Vous pouvez utiliser la requête SQL suivante pour trouver les colonnes ayant peu de valeurs uniques.
Étant donné leur faible cardinalité, ces quatre colonnes, ratecode_id, pickup_location_id, dropoff_location_id et vendor_id, sont de bonnes candidates pour le type de champ LowCardinality.

Optimiser le type de données

ClickHouse prend en charge un grand nombre de types de données. Veillez à choisir le plus petit type de données possible adapté à votre cas d’usage afin d’optimiser les performances et de réduire l’espace de stockage de vos données sur disque.  Pour les nombres, vous pouvez vérifier la valeur min/max dans votre jeu de données afin de voir si la valeur de précision actuelle correspond réellement à celui-ci. 
Pour les dates, choisissez une précision adaptée à votre jeu de données et aux requêtes que vous prévoyez d’exécuter.

Appliquer les optimisations

Créons une nouvelle table pour utiliser le schéma optimisé et ingérer de nouveau les données.
Nous exécutons à nouveau les requêtes sur la nouvelle table afin de vérifier les améliorations.  Nous constatons des améliorations, tant au niveau du temps d’exécution des requêtes que de l’utilisation de la mémoire. Grâce à l’optimisation du schéma de données, nous réduisons le volume total des données stockées, ce qui diminue la consommation mémoire et le temps de traitement.  Vérifions la taille des tables pour voir la différence. 
La nouvelle table est nettement plus petite que la précédente. Nous constatons une réduction d’environ 34 % de l’espace disque occupé par la table (7.38 GiB contre 4.89 GiB).

L’importance des clés primaires

Les clés primaires dans ClickHouse fonctionnent différemment de celles de la plupart des systèmes de base de données traditionnels. Dans ces systèmes, les clés primaires garantissent l’unicité et l’intégrité des données. Toute tentative d’insérer des valeurs de clé primaire en doublon est rejetée, et un index basé sur un B-tree ou un hash est généralement créé pour permettre une recherche rapide.  Dans ClickHouse, l’objectif de la clé primaire est différent : elle ne garantit pas l’unicité et ne contribue pas à l’intégrité des données. Elle est conçue à la place pour optimiser les performances des requêtes. La clé primaire définit l’ordre dans lequel les données sont stockées sur disque et est implémentée sous la forme d’un index clairsemé qui stocke des pointeurs vers la première ligne de chaque granule.
Dans ClickHouse, les granules sont les plus petites unités de données lues lors de l’exécution d’une requête. Elles contiennent jusqu’à un nombre fixe de lignes, déterminé par index_granularity, avec une valeur par défaut de 8192 lignes. Les granules sont stockées de manière contiguë et triées selon la clé primaire. 
Choisir un bon ensemble de clés primaires est important pour les performances, et il est d’ailleurs courant de stocker les mêmes données dans différentes tables et d’utiliser différents ensembles de clés primaires pour accélérer un ensemble spécifique de requêtes.  D’autres options prises en charge par ClickHouse, telles que les projections ou les vues matérialisées, vous permettent d’utiliser un ensemble différent de clés primaires sur les mêmes données. La deuxième partie de cette série d’articles de blog abordera ce sujet plus en détail. 

Choisir les clés primaires

Choisir le bon ensemble de clés primaires est un sujet complexe, et il peut être nécessaire de faire des compromis et des essais pour trouver la meilleure combinaison.  Pour l’instant, nous allons suivre ces quelques bonnes pratiques : 
  • Utilisez les champs employés comme filtres dans la plupart des requêtes
  • Choisissez d’abord les colonnes ayant la cardinalité la plus faible 
  • Envisagez d’inclure une composante temporelle dans votre clé primaire, car le filtrage par date/heure sur un jeu de données horodaté est très courant. 
Dans notre cas, nous allons tester les clés primaires suivantes : passenger_count, pickup_datetime et dropoff_datetime La cardinalité de passenger_count est faible (24 valeurs uniques) et ce champ est utilisé dans nos requêtes lentes. Nous ajoutons également des champs d’horodatage (pickup_datetime et dropoff_datetime), car ils sont souvent utilisés pour le filtrage. Créez une nouvelle table avec ces clés primaires, puis réingérez les données.
Nous réexécutons ensuite nos requêtes. Nous regroupons les résultats des trois expériences pour observer les améliorations du temps d’exécution, du nombre de lignes traitées et de la consommation de mémoire. 
Requête 1
Exécution 1Exécution 2Exécution 3
temps écoulé1.699 sec1.353 sec0.765 sec
lignes traitées329.04 million329.04 million329.04 million
mémoire maximale440.24 MiB337.12 MiB444.19 MiB
Requête 2
Exécution 1Exécution 2Exécution 3
temps écoulé1.419 sec1.171 sec0.248 sec
lignes traitées329.04 million329.04 million41.46 million
mémoire maximale546.75 MiB531.09 MiB173.50 MiB
Requête 3
Exécution 1Exécution 2Exécution 3
Temps écoulé1,414 s1,188 s0,431 s
Lignes traitées329,04 millions329,04 millions276,99 millions
Mémoire maximale451,53 MiB265,05 MiB197,38 MiB
On constate une amélioration significative, tant en temps d’exécution qu’en mémoire utilisée.  C’est la requête 2 qui bénéficie le plus de la clé primaire. Examinons en quoi le plan d’exécution généré diffère du précédent.
Grâce à la clé primaire, seul un sous-ensemble des granules de la table a été sélectionné. À lui seul, cela améliore considérablement les performances des requêtes, puisque ClickHouse doit traiter beaucoup moins de données.

Étapes suivantes

Nous espérons que ce guide vous a aidé à bien comprendre comment analyser les requêtes lentes avec ClickHouse et comment les accélérer. Pour approfondir ce sujet, vous pouvez consulter la documentation sur l’analyseur de requêtes et le profilage afin de mieux comprendre comment ClickHouse exécute votre requête. À mesure que vous vous familiariserez avec les spécificités de ClickHouse, nous vous recommandons également de lire la documentation sur les clés de partitionnement et les index de saut de données pour découvrir des techniques plus avancées permettant d’accélérer vos requêtes.
Dernière modification le 23 juillet 2026