Comprendre les performances des requêtes
Considérations générales
- Analyse syntaxique et analyse de la requête
- Optimisation de la requête
- Exécution du pipeline de requête
- Traitement final
Jeu de données
Identifier les requêtes lentes
Journaux des requêtes
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.
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.
enable_filesystem_cache sur 0 afin d’améliorer la reproductibilité.
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.
Instruction EXPLAIN
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.
Méthodologie
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
Nullable
mta_tax et payment_type. Les autres champs ne devraient pas utiliser de colonne Nullable.
Faible cardinalité
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
Appliquer les optimisations
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.
L’importance des clés primaires
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
- 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.
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.
| Requête 1 | |||
|---|---|---|---|
| Exécution 1 | Exécution 2 | Exécution 3 | |
| temps écoulé | 1.699 sec | 1.353 sec | 0.765 sec |
| lignes traitées | 329.04 million | 329.04 million | 329.04 million |
| mémoire maximale | 440.24 MiB | 337.12 MiB | 444.19 MiB |
| Requête 2 | |||
|---|---|---|---|
| Exécution 1 | Exécution 2 | Exécution 3 | |
| temps écoulé | 1.419 sec | 1.171 sec | 0.248 sec |
| lignes traitées | 329.04 million | 329.04 million | 41.46 million |
| mémoire maximale | 546.75 MiB | 531.09 MiB | 173.50 MiB |
| Requête 3 | |||
|---|---|---|---|
| Exécution 1 | Exécution 2 | Exécution 3 | |
| Temps écoulé | 1,414 s | 1,188 s | 0,431 s |
| Lignes traitées | 329,04 millions | 329,04 millions | 276,99 millions |
| Mémoire maximale | 451,53 MiB | 265,05 MiB | 197,38 MiB |