Analyse des performances des requĂŞtes dans ClickHouse. Rapport Yandex

Que faire si votre requête de base de données ne fonctionne pas assez rapidement? Comment savoir si une requête utilise de manière optimale les ressources informatiques ou peut-elle être accélérée? Lors de la dernière conférence HighLoad ++ à Moscou, j'ai parlé de l'introspection des performances des requêtes - et de ce que le SGBD ClickHouse fournit, et des fonctionnalités du système d'exploitation que tout le monde devrait connaître.



Chaque fois que je fais une demande, je m'inquiète non seulement du résultat, mais aussi de ce que fait cette demande. Par exemple, cela fonctionne pendant une seconde. Est-ce beaucoup ou peu? Je pense toujours: pourquoi pas une demi-seconde? Ensuite, j'optimise quelque chose, je l'accélère et cela fonctionne pendant 10 ms. Je suis généralement satisfait. Mais quand même, dans ce cas, j'essaie de faire une expression faciale mécontente et je demande: "Pourquoi pas 5 ms?" Comment puis-je savoir combien de temps est consacré au traitement de la demande? Peut-il être accéléré en principe?

En règle générale, la vitesse de traitement des demandes est une simple arithmétique. Nous avons écrit le code - probablement de manière optimale - et nous avons un appareil dans le système. Les appareils ont des spécifications. Par exemple, la vitesse de lecture à partir du cache L1. Ou le nombre de lectures aléatoires qu'un SSD peut effectuer. Nous le savons tous. Nous devons prendre ces caractéristiques, additionner, soustraire, multiplier, diviser et vérifier la réponse. Mais c'est dans le cas idéal, cela n'arrive presque jamais. Presque. En fait, cela se produit parfois dans ClickHouse.

Considérez les faits triviaux sur les appareils et les ressources de nos serveurs.



Processeur, mémoire, disque, réseau. J'ai spécialement organisé ces ressources de cette manière, en commençant par la plus simple et la plus pratique pour la révision et l'optimisation, et en terminant par la plus gênante et la plus complexe. Par exemple, j'exécute une requête et constate que mon programme semble reposer sur le CPU. Qu'est-ce que cela signifie? Que vais-je trouver, il y a une sorte de boucle interne, une fonction qui est le plus souvent exécutée, réécrit le code, recompile et une fois - mon programme s'exécute plus rapidement.

Si vous dépensez trop de RAM, alors tout est un peu plus compliqué. Vous devez repenser la structure des données, presser quelques bits. En tout cas, je redémarre mon programme, et il dépense moins de RAM. Certes, cela se fait souvent au détriment du processeur.

Si tout dépend des disques, cela est également plus difficile, car je peux changer la structure des données sur le disque, mais je dois convertir ces données plus tard. Si je fais une nouvelle version, les gens devront faire une sorte de migration de données. Il s'avère que le disque est déjà beaucoup plus compliqué, et il vaut mieux y penser à l'avance.

Et le réseau ... Je n'aime vraiment pas le réseau, car il est souvent très difficile de savoir ce qui s'y passe, surtout s'il s'agit d'un réseau entre continents, entre centres de données. Quelque chose ralentit là-bas, et ce n'est même pas votre réseau, pas votre serveur, et vous ne pouvez rien faire. La seule chose à laquelle vous pouvez penser à l'avance est de savoir comment les données seront transmises et comment minimiser l'interaction sur le réseau.

Il arrive que pas une seule ressource du système ne soit utilisée et que le programme n'attende que quelque chose. En fait, c'est un cas très courant, car notre système est distribué, et il peut y avoir de nombreux processus et flux différents, et certains en attendent un autre, et tout cela doit être connecté d'une manière ou d'une autre pour bien le prendre en compte.



La chose la plus simple est d'examiner l'utilisation des ressources, à une certaine valeur numérique. Par exemple, vous démarrez un top, et il écrit: le processeur est à 100%. Ou exécutez iostat, et il écrit: les disques sont à 100%. Certes, ce n'est souvent pas suffisant. Une personne verra que le programme repose sur des disques. Que peut-on faire? Vous pouvez simplement noter cela et vous reposer, décider que tout, rien ne peut être optimisé. Mais en fait, chacun des appareils en lui-même est assez compliqué. Le processeur dispose d'un tas de dispositifs informatiques pour différents types d'opérations. Les disques peuvent avoir une matrice RAID. S'il y a un SSD, il y a à l'intérieur son propre processeur, son propre contrôleur, ce qui ne permet pas de savoir quoi. Et une valeur - 50% ou 100% - ne suffit pas. La règle de base: si vous voyez que certaines ressources sont utilisées à 100%, n'abandonnez pas. Souvent, vous pouvez toujours améliorer quelque chose. Mais cela arrive et vice versa. Disons que vous voyez que le recyclage est à 50%, mais rien ne peut être fait.

Examinons cela de plus près.



La ressource la plus simple et la plus pratique est le processeur. Vous regardez en haut, ça dit que le processeur est à 100%. Mais il ne faut pas oublier que ce n'est pas un processeur à 100%. Le programme supérieur ne sait pas ce que le processeur y fait. Elle regarde du point de vue du planificateur OS. Autrement dit, maintenant une sorte de thread de programme s'exécute sur le processeur. Le processeur fait quelque chose, puis 100% sera affiché s'il est moyenné dans le temps. Dans le même temps, le processeur fait quelque chose et son efficacité n'est pas claire. Il peut exécuter un nombre différent d'instructions par cycle. S'il y a peu d'instructions, le processeur lui-même peut attendre quelque chose, par exemple, le chargement de données depuis la mémoire. Dans le même temps, la même chose sera affichée en haut - 100%. Nous attendons que le processeur suive nos instructions. Et ce qu'il fait à l'intérieur n'est pas clair.

Enfin, il y a juste un rake quand vous pensez que votre programme repose sur le processeur. C'est vrai, mais pour une raison quelconque, le processeur a une fréquence plus basse. Il peut y avoir plusieurs raisons: surchauffe, limitation de puissance. Pour une raison quelconque, dans le centre de données, il y a une limitation de puissance ou l'économie d'énergie peut simplement être activée. Ensuite, le processeur passera constamment d'une fréquence plus élevée à une fréquence plus basse, mais si votre charge est instable, cela ne suffira pas et en moyenne, le code s'exécutera plus lentement. Voir le turbostat pour la fréquence actuelle du processeur. Vérifiez la surchauffe dans le dmesg. Si quelque chose comme ça se produisait, il dirait: «Surchauffe. Fréquence en baisse. "

Si vous êtes intéressé par le nombre de cache manquants, combien d'instructions sont exécutées par cycle, utilisez perf record. Enregistrez un échantillon du programme. De plus, il sera possible de le consulter en utilisant perf stat ou perf report.



Et vice versa. Disons que vous regardez en haut et que le processeur est recyclé à moins de 50%. Supposons que vous ayez 32 cœurs de processeur virtuel dans votre système et 16 cœurs physiques. Sur les processeurs Intel, c'est parce que l'hyper-threading est double. Mais cela ne signifie pas que des cœurs supplémentaires sont inutiles. Tout dépend de la charge. Supposons que vous ayez des opérations d'algèbre linéaire bien optimisées ou que vous ayez des hachages pour extraire des bitcoins. Ensuite, le code sera clair, de nombreuses instructions seront exécutées par cycle, il n'y aura pas de raté de cache, de mauvaises prédictions de branche aussi. Et l'hyper-threading n'aide pas. Cela aide lorsque vous avez un noyau en attente de quelque chose, tandis que l'autre peut exécuter simultanément des instructions à partir d'un autre thread.

ClickHouse a les deux situations. Par exemple, lorsque nous effectuons une agrégation de données (GROUP BY) ou un filtrage par ensemble (sous-requête IN), nous aurons une table de hachage. Si la table de hachage ne tient pas dans le cache du processeur, des échecs de cache se produiront. Cela peut difficilement être évité. Dans ce cas, l'hyper-threading nous aidera.

Par défaut, ClickHouse utilise uniquement des cœurs de processeur physiques, à l'exclusion de l'hyper-threading. Si vous savez que votre demande peut bénéficier de l'hyper-threading, il suffit de doubler le nombre de threads: SET max threads = 32, et votre demande sera plus rapide.

Il arrive que le processeur soit parfaitement utilisé, mais vous regardez le graphique et voyez, par exemple, 10%. Et votre horaire, par exemple, est de cinq minutes dans le pire des cas. Même s'il s'agit d'une seconde, il existe toujours une sorte de valeur moyenne. En fait, vous avez constamment eu des requêtes, elles sont exécutées rapidement, en 100 ms toutes les secondes, et c'est normal. Parce que ClickHouse essaie d'exécuter la demande le plus rapidement possible. Il n'essaie pas du tout d'utiliser et de surchauffer complètement et constamment vos processeurs.



Examinons de plus près, une option un peu compliquée. Il existe une requête avec une expression dans la sous-requête. Dans la sous-requête, nous avons 100 millions de nombres aléatoires. Et nous filtrons simplement ce résultat.

Nous voyons une telle image. Au fait, qui dira avec quel outil je peux voir cette magnifique photo? Absolument vrai - perf. Je suis très content que vous le sachiez.

J'ai ouvert la perf, pensant que maintenant je comprends tout. J'ouvre la liste des assembleurs. Là, j'ai écrit combien de fois l'exécution du programme était sur une instruction particulière, c'est-à-dire combien de fois il y avait un pointeur d'instruction. Ici, les nombres sont en pourcentage, et il est écrit que près de 90% du temps, l'instruction test% edx,% edx a été exécutée, c'est-à-dire la vérification de zéro sur quatre octets.

La question est: pourquoi un processeur peut-il prendre autant de temps pour comparer simplement quatre octets à zéro? (réponses du public ...) Il n'y a pas de reste de division. Il y a des décalages de bits, puis il y a une instruction crc32q, mais comme si le pointeur d'instruction ne s'y produisait jamais. Et la génération de nombres aléatoires n'est pas dans cette liste. Il y avait une fonction séparée, et elle est très bien optimisée, elle ne ralentit pas. Quelque chose d'autre ralentit ici. L'exécution du code s'arrête à cette instruction et passe beaucoup de temps. Boucle inactive? Non. Pourquoi devrais-je insérer des boucles vides? De plus, si j'insérais la boucle Idle, cela serait également visible dans perf. Il n'y a pas de division par zéro, il y a simplement une comparaison avec zéro.

Le processeur dispose d'un pipeline, il peut exécuter plusieurs instructions en parallèle. Et lorsque le pointeur d'instruction est à un certain endroit, cela ne signifie pas du tout qu'il exécute cette instruction. Il attend peut-être d'autres instructions.

Nous avons une table de hachage pour vérifier qu'un certain nombre se produit dans un ensemble. Pour cela, nous effectuons une recherche en mémoire. Lorsque nous effectuons une recherche en mémoire, nous avons un échec de cache, car la table de hachage contient 100 millions de chiffres, il n'est pas garanti de tenir dans n'importe quel cache. Ainsi, pour exécuter l'instruction de vérification du zéro, ces données doivent déjà être chargées à partir de la mémoire. Et nous attendons qu'ils soient chargés.



Maintenant, la ressource suivante, un peu plus complexe - lecteurs. Les SSD sont également parfois appelés disques, bien que ce ne soit pas tout à fait correct. Les SSD seront également inclus dans cet exemple.

Nous ouvrons, par exemple, iostat, il montre une utilisation de 100%.

Lors des conférences, il arrive souvent que le locuteur monte sur scène et déclare avec pathos: «Les bases de données sont toujours en appui sur le disque. Par conséquent, nous avons créé une base de données en mémoire. Elle ne ralentira pas. " Si une personne s'approche de vous et vous le dit, vous pouvez l'envoyer en toute sécurité. Il y aura des problèmes - vous dites, je l'ai résolu. :)

Supposons qu'un programme repose sur des disques, l'utilisation est de 100. Mais cela, bien sûr, ne signifie pas que nous utilisons les disques de manière optimale.

Un exemple typique est lorsque vous avez juste beaucoup d'accès aléatoire. Même si l'accès est séquentiel, vous lisez simplement le fichier séquentiellement, mais il peut toujours être plus ou moins optimal.

Par exemple, vous avez une matrice RAID, plusieurs périphériques - disons, 8 disques. Et vous venez de lire séquentiellement sans lire à l'avance, avec une taille de tampon de 1 Mo, et la taille de bloc dans votre bande en RAID est également de 1 Mo. Ensuite, chaque lecture que vous aurez d'un appareil. Ou, s'il n'est pas aligné, à partir de deux appareils. Un demi-mégaoctet ira quelque part, un autre demi-mégaoctet quelque part, et ainsi de suite - les disques seront utilisés tour à tour: un, puis un autre, puis un troisième.

Il doit être lu à l'avance. Ou, si vous avez O_DIRECT, augmentez la taille du tampon. Autrement dit, la règle est la suivante: 8 disques, taille de bloc 1 Mo, définissez la taille du tampon sur au moins 8 Mo. Mais cela ne fonctionnera de manière optimale que si la lecture est alignée. Et s'il n'est pas aligné, il y aura d'abord des pièces supplémentaires, et vous devrez en mettre plus, multiplier par quelques autres.

Ou, par exemple, vous avez RAID 10. Avec quelle vitesse pouvez-vous lire à partir de RAID 10 - par exemple, à partir de 8 disques? Quel sera l'avantage? Quatre fois, parce qu'il y a un miroir, ou huit fois? En fait, cela dépend de la façon dont le RAID est créé, avec quelle disposition des morceaux en bandes.

Si vous utilisez mdadm sous Linux, vous pouvez spécifier une disposition proche et une disposition éloignée, avec presque mieux pour l'écriture, loin pour la lecture.

Je recommande toujours d'utiliser une mise en page éloignée, car lorsque vous écrivez dans la base de données analytique, ce n'est généralement pas si critique dans le temps - même s'il y a beaucoup plus d'écriture que de lecture. Cela se fait par un processus d'arrière-plan. Mais lorsque vous lisez, vous devez le terminer le plus rapidement possible. Il est donc préférable d'optimiser le RAID pour la lecture en définissant une disposition éloignée.

Par chance, sous Linux, mdadm vous mettra par défaut dans une disposition proche, et vous n'obtiendrez que la moitié des performances. Il y a beaucoup de tels râteaux.

Un autre rake terrible est RAID 5 ou RAID 6. Tout y évolue bien par des lectures et des écritures séquentielles. En RAID 5, la multiplicité est «le nombre de périphériques moins un». Cela évolue bien même avec des lectures aléatoires, mais cela ne fonctionne pas bien avec des lectures aléatoires. Faites un enregistrement à n'importe quel endroit, et vous devez lire les données de tous les autres disques, les poksorit (XOR - environ Ed.) Et écrire à un autre endroit. Pour cela, une certaine cache de bandes est utilisée, un terrible râteau. Sous Linux, c'est par défaut que vous créez RAID 5 et ça va ralentir pour vous. Et vous penserez que RAID 5 ralentit toujours, car cela est compréhensible. Mais en fait, la raison est la mauvaise configuration.

Un autre exemple. Vous lisez à partir d'un SSD, et vous vous êtes acheté un bon SSD, il indique 300 000 lectures aléatoires par seconde dans la spécification. Et pour une raison quelconque, vous ne pouvez pas le faire. Et vous pensez - oui, ils se trouvent tous dans leurs spécifications, il n'y a rien de tel. Mais toutes ces lectures doivent se faire en parallèle, avec le maximum de parallélisme. La seule façon de procéder de manière tout à fait optimale consiste à utiliser des E / S asynchrones, qui sont implémentées à l'aide des appels système io_submit, io_getevents, io_setup, etc.

Soit dit en passant, les données sur le disque, si vous les stockez, vous devez toujours les compresser. Je vais donner un exemple tiré de la pratique. Une personne nous a contacté dans le chat d'assistance ClickHouse et a déclaré:

- ClickHouse compresse les données. Je vois que cela repose sur le processeur. J'ai des SSD NVMe très rapides, ils ont une vitesse de lecture de plusieurs gigaoctets par seconde. Est-il possible de désactiver en quelque sorte la compression dans ClickHouse?
«Non, pas du tout», dis-je. - Vous devez conserver les données compressées.
- ArrĂŞtons-le, il y aura juste un autre algorithme de compression qui ne fait rien.
- Facile. Entrez ces lettres dans cette ligne de code.
"En effet, tout est très simple", a-t-il répondu un jour plus tard. - Oui.
- Dans quelle mesure les performances ont-elles changé?
"Échec du test", écrit-il un autre jour plus tard. - Il y a trop de données. Ils ne tiennent plus sur les SSD.

Voyons maintenant à quoi pourrait ressembler la lecture à partir du disque. On démarre dstat, ça montre la vitesse de lecture.

Le premier exemple de dstat et iostat


Voici la colonne de lecture - 300 Mo / s. Nous lisons des disques. C'est beaucoup ou un peu - je ne sais pas.

Maintenant, je lance iostat pour vérifier cela. Ici vous pouvez voir la répartition par appareil. J'ai RAID, md2 et huit disques durs. Chacun d'eux montre du recyclage, il n'atteint même pas 100% (50-60%). Mais la chose la plus importante est que je ne lis sur chaque disque qu'à une vitesse de 20-30 Mo / s. Et depuis l'enfance, je me suis souvenu de la règle selon laquelle vous pouvez lire quelque part à partir de 100 Mo / s depuis le disque dur. Pour une raison quelconque, cela n'a pas encore beaucoup changé.

Deuxième exemple de dstat et iostat


Voici un autre exemple. La lecture est plus optimale. J'exécute dstat, et j'ai une vitesse de lecture de 1 Go / s à partir de ce RAID 5 sur huit disques. Que montre iostat? Oui, près de 1 Go / s.

Maintenant, les disques sont enfin chargés à 100%. Certes, pour une raison quelconque, deux sont à 100% et les autres à 95%. Probablement, ils sont encore un peu différents. Mais avec chacun d'eux, j'ai lu 150 Mo / s, encore plus cool qu'il ne peut l'être. Quelle est la différence? Dans le premier cas, j'ai lu avec une taille de tampon insuffisante en morceaux insuffisants. C'est simple, je vous dis des vérités communes.

Soit dit en passant, si vous pensez que les données n'ont toujours pas besoin d'être compressées pour une base de données analytiques, c'est-à-dire un rapport de la conférence HighLoad ++ Siberia ( habrastaty basé sur le rapport - environ Ed.). Les organisateurs ont décidé de faire les reportages les plus hardcore à Novossibirsk.



L'exemple suivant est la mémoire. Poursuite des vérités communes. Tout d'abord, sous Linux, ne voyez jamais ce que les émissions gratuites. Pour ceux qui regardent, ils ont spécialement créé le site linuxatemyram.com. Entrez, il y aura une explication. Vous n'avez pas non plus besoin de regarder la quantité de mémoire virtuelle, car quelle est la différence, combien d'espace d'adressage le programme a-t-il alloué? Regardez combien de mémoire physique est utilisée.

Et un râteau de plus avec lequel on ne sait même pas comment se battre. N'oubliez pas: le fait que les allocateurs n'aiment souvent pas donner de mémoire au système est normal. Ils ont fait mmap, mais munmap ne le fait plus. La mémoire ne reviendra pas au système. Le programme pense - je sais mieux comment j'utiliserai la mémoire. Je m'en remets à moi. Parce que les appels système mmap et munmap sont assez lents. Changer l'espace d'adressage, réinitialiser les caches TLB du processeur - il vaut mieux ne pas le faire. Cependant, le système d'exploitation a toujours la capacité de libérer correctement la mémoire à l'aide de l'appel système madvise. L'espace d'adressage restera, mais physiquement la mémoire peut être déchargée.

Et n'activez jamais l'échange sur des serveurs de production avec des bases de données. Vous pensez - il n'y a pas assez de mémoire, je vais inclure l'échange. Après cela, la demande cessera de fonctionner. Il craquera un temps infini.



Avec un réseau en râteau trop typique. Si vous créez une connexion TCP à chaque fois, il faut un certain temps avant que la taille de fenêtre correcte soit sélectionnée, car le protocole TCP ne sait pas à quelle vitesse il sera nécessaire de transmettre des données. Il s'y adapte.

Ou imaginez - vous transférez un fichier, et vous avez une grande latence sur votre réseau et une perte de paquets décente. Il n’est alors pas du tout évident qu’il soit judicieux d’utiliser TCP pour transférer des fichiers. Je pense que c'est faux, car TCP garantit la cohérence. D'un autre côté, vous pouvez transférer une moitié du fichier et l'autre en même temps. Utilisez au moins plusieurs connexions TCP ou n'utilisez pas TCP du tout pour le transfert de données. , , , TCP . .

100- , . 10 -, , , . . .



? — . , , , 10 . , .



: « - » — . iotop, , , iops.

, . .

: top


top -, , clickHouse-server - , - . , , Shift+H, . , ClickHouse . ParalInputsProc, . BackgrProcPool — merges . , .

? ClickHouse, , . BackgroundProcessingPool. 15 . 16 1, 1 — . 16? , Linux — , : «16 . ». :)

clickhouse-benchmark. clickhouse-client. , clickhouse-client, . - . .

: clickhouse-benchmark + perf top


. clickhouse-benchmark, , , , , . peft top. peft top, . , - -, uniq: UniquesHashSet. . , . , .

, , . — -. , , XOR - . -. - -. , -.

, , crc32q. , , - , - .

, ClickHouse. , , . ClickHouse.



. , — , SHOW PROCESSLIST. . , SELECT * FROM system processes. : , , . ClickHouse top.

ClickHouse ? background-. Background- — merges. , merges , SELECT * FROM system.merges.

c


, . -. . — ClickHouse. . , , . , . - traf_testing. ? , , . ClickHouse .



. , . , , , , . query_log. — , - , SELECT , - . query_log , . - . — , . : .

, , — merge, inserts, . part_log. , .



query_log clickhouse-benchmark. select , , stdin clickhouse-benchmark.

query_log - , .



, , . . SET send_logs_level = 'trace', , .

:


, . , 98%. , . C'est très simple. SET send_logs_level = 'trace', , . - : merging aggregated data, . 1% . , .

, , query_log.

. SELECT * FROM system.query_log . . , , , query_log. . — , , , . .



ClickHouse . — system.events, system.metrics system.asynchronous_metrics. Events — , , . 100 . — 10 . system.metrics — . , 10 , 10 .

system.asynchronous_metrics , . . — . , system.asynchronous_metrics — , - . , .

, . SHOW PROCESSLIST . query_log, .



, . , . , . , , . , Linux, . Linux . , . , . .

, OSReadChars OSReadBytes. ? , , , . , . , , , . , - , .

page cache


, . - . , 40 , 6,7 . . , , . , , .

, 1,3 , 5 . Pourquoi? , — page cache. ?

c


. . , , . . : 3,2 , — 2,5 . , , , . Pourquoi? -, : read ahead. , — ? -, — 4 , , 512 KB. . , . , - read ahead.



. . , . , , ReadBytes — , . 3 , 3 . , , .

— IOWait. 87 . 7 , IOWait — 87. ? — . . , , 87 . , - .

— CPUWait. , , , . - — , . CPU. CPU. - , . — , , user space. , - . Et bien.

— , Linux. - , . , , .

: query_thread_log


, : query_thread_log. , .

, query_id « , user space». . 16 . 800 . 16 , 0,25 . , .

HighLoad++:

Source: https://habr.com/ru/post/fr459198/


All Articles