Análisis de rendimiento de consultas en ClickHouse. Informe Yandex

¿Qué pasa si su consulta de base de datos no se ejecuta lo suficientemente rápido? ¿Cómo saber si una consulta utiliza los recursos informáticos de manera óptima o puede acelerarse? En la última conferencia de HighLoad ++ en Moscú, hablé sobre la introspección del rendimiento de las consultas, y sobre lo que proporciona el DBMS ClickHouse, y sobre las características del sistema operativo que todos deberían conocer.



Cada vez que hago una solicitud, me preocupa no solo el resultado, sino también lo que hace esta solicitud. Por ejemplo, funciona por un segundo. ¿Es mucho o poco? Siempre pienso: ¿por qué no medio segundo? Luego optimizo algo, lo acelero y funciona durante 10 ms. Por lo general estoy satisfecho. Pero aún así, en este caso trato de hacer una expresión facial disgustada y pregunto: "¿Por qué no 5 ms?" ¿Cómo puedo saber cuánto tiempo pasa procesando la solicitud? ¿Se puede acelerar en principio?

Por lo general, la velocidad de procesamiento de solicitudes es aritmética simple. Escribimos el código, probablemente de manera óptima, y ​​tenemos algún dispositivo en el sistema. Los dispositivos tienen especificaciones. Por ejemplo, la velocidad de lectura del caché L1. O el número de lecturas aleatorias que puede hacer un SSD. Todos lo sabemos. Necesitamos tomar estas características, sumar, restar, multiplicar, dividir y verificar la respuesta. Pero esto es en el caso ideal, esto casi nunca sucede. Casi. De hecho, esto a veces sucede en ClickHouse.

Considere los hechos triviales sobre qué dispositivos y qué recursos hay en nuestros servidores.



Procesador, memoria, disco, red. Organicé especialmente estos recursos de tal manera, comenzando por el más simple y conveniente para su revisión y optimización, y terminando con el más inconveniente y complejo. Por ejemplo, ejecuto una solicitud y veo que mi programa parece descansar en la CPU. ¿Qué significa esto? Lo que encontraré es algún tipo de bucle interno, una función que se ejecuta con mayor frecuencia, reescribe el código, vuelve a compilar y, una vez, mi programa se ejecuta más rápido.

Si gastas demasiada RAM, entonces todo es un poco más complicado. Necesita repensar la estructura de datos, exprimir algunos bits. En cualquier caso, reinicio mi programa y gasta menos RAM. Es cierto que esto es a menudo en detrimento del procesador.

Si todo depende de los discos, entonces esto también es más difícil, porque puedo cambiar la estructura de datos en el disco, pero tengo que convertir estos datos más tarde. Si hago una nueva versión, la gente tendrá que hacer algún tipo de migración de datos. Resulta que el disco ya es mucho más complicado, y es mejor pensarlo de antemano.

Una red ... Realmente no me gusta una red, porque a menudo no está completamente claro lo que está sucediendo en ella, especialmente si es una red entre continentes, entre centros de datos. Algo se está desacelerando allí, y ni siquiera es su red, ni su servidor, y no puede hacer nada. Lo único que puede pensar de antemano es cómo se transmitirán los datos y cómo minimizar la interacción en la red.

Sucede que no se utiliza un solo recurso en el sistema, y ​​el programa solo está esperando algo. De hecho, este es un caso muy común, porque nuestro sistema está distribuido, y puede haber muchos procesos y flujos diferentes, y algunos están esperando a otro, y todo esto debe estar conectado de alguna manera entre sí para considerarlo adecuadamente.



Lo más simple es observar la utilización de los recursos, en algún valor numérico. Por ejemplo, comienzas algo superior, y él escribe: el procesador es 100%. O ejecute iostat, y él escribe: los discos son 100%. Es cierto que esto a menudo no es suficiente. Una persona verá que el programa se basa en discos. Que se puede hacer Simplemente puede notar esto y descansar, decidir que todo, nada puede optimizarse. Pero, de hecho, cada uno de los dispositivos dentro de sí mismo es bastante complicado. El procesador tiene un montón de dispositivos informáticos para diferentes tipos de operaciones. Los discos pueden tener una matriz RAID. Si hay un SSD, entonces hay dentro de su propio procesador, su propio controlador, lo que hace que no esté claro qué. Y un valor, 50% o 100%, no es suficiente. La regla básica: si ve que algún recurso se utiliza al 100%, no se rinda. A menudo, aún puedes mejorar algo. Pero sucede y viceversa. Digamos que ve que el reciclaje es del 50%, pero no se puede hacer nada.

Echemos un vistazo más de cerca a esto.



El recurso más fácil y conveniente es el procesador. Te ves en la parte superior, dice que el procesador es 100%. Pero debe tenerse en cuenta que este no es un procesador 100%. El programa superior no sabe qué hace el procesador allí. Ella mira desde la perspectiva del planificador del sistema operativo. Es decir, ahora se está ejecutando algún tipo de subproceso de programa en el procesador. El procesador hace algo, y luego se mostrará el 100% si se promedia con el tiempo. Al mismo tiempo, el procesador está haciendo algo y no está claro qué tan efectivo es. Puede ejecutar un número diferente de instrucciones por ciclo. Si hay pocas instrucciones, el propio procesador puede esperar algo, por ejemplo, cargar datos desde la memoria. Al mismo tiempo, se mostrará lo mismo en la parte superior: 100%. Estamos esperando que el procesador siga nuestras instrucciones. Y lo que hace por dentro no está claro.

Finalmente, solo hay un rastrillo cuando crees que tu programa se basa en el procesador. Esto es cierto, pero por alguna razón el procesador tiene una frecuencia más baja. Puede haber muchas razones: sobrecalentamiento, limitación de potencia. Por alguna razón, en el centro de datos hay una limitación de energía, o simplemente se puede activar el ahorro de energía. Luego, el procesador cambiará constantemente de una frecuencia más alta a una más baja, pero si su carga es inestable, esto no será suficiente y, en promedio, el código se ejecutará más lentamente. Vea el turbostato para la frecuencia actual del procesador. Verifique el sobrecalentamiento en dmesg. Si sucediera algo así, diría: “Sobrecalentamiento. Frecuencia bajada.

Si está interesado en la cantidad de errores de caché que hay dentro, cuántas instrucciones se ejecutan por ciclo, use el registro de rendimiento. Grabe alguna muestra del programa. Además, será posible mirarlo utilizando estadísticas de rendimiento o informe de rendimiento.



Y viceversa. Digamos que miras hacia arriba y el procesador tiene menos del 50% de reciclaje. Suponga que tiene 32 núcleos de procesador virtual en su sistema y 16 núcleos físicos. En los procesadores Intel, esto se debe a que el hiperprocesamiento es doble. Pero esto no significa que los núcleos adicionales sean inútiles. Todo depende de la carga. Supongamos que tiene algunas operaciones de álgebra lineal bien optimizadas o tiene hashes para extraer bitcoins. Luego, el código será claro, se ejecutarán muchas instrucciones por ciclo, no habrá errores de caché, predicciones erróneas de ramificaciones también. E hiperprocesamiento no ayuda. Ayuda cuando tienes un núcleo esperando algo, mientras que el otro puede ejecutar simultáneamente instrucciones desde otro hilo.

ClickHouse tiene ambas situaciones. Por ejemplo, cuando hacemos agregación de datos (GROUP BY) o filtrado por conjunto (subconsulta IN), tendremos una tabla hash. Si la tabla hash no cabe en el caché del procesador, se producirán errores de caché. Esto difícilmente se puede evitar. En este caso, el hiperprocesamiento nos ayudará.

De manera predeterminada, ClickHouse usa solo núcleos de procesadores físicos, excluyendo hyper-threading. Si sabe que su solicitud puede beneficiarse del hiperprocesamiento, solo duplique el número de subprocesos: SET max threads = 32, y su solicitud será más rápida.

Sucede que el procesador se usa perfectamente, pero mira el gráfico y ve, por ejemplo, el 10%. Y su horario, por ejemplo, es de cinco minutos en el peor de los casos. Incluso si es un segundo, todavía hay algún tipo de valor promedio. De hecho, constantemente tenía solicitudes, se ejecutan rápidamente, en 100 ms por segundo, y esto es normal. Porque ClickHouse intenta ejecutar la solicitud lo más rápido posible. Él no intenta usar y sobrecalentar completa y constantemente sus procesadores.



Echemos un vistazo más de cerca, una opción un poco complicada. Hay una consulta con una expresión en subconsulta. Dentro de la subconsulta, tenemos 100 millones de números aleatorios. Y simplemente filtramos este resultado.

Vemos una imagen así. Por cierto, ¿quién dirá con qué herramienta puedo ver esta maravillosa imagen? Absolutamente cierto - perf. Estoy muy contento de que sepas esto.

Abrí perf, pensando que ahora entiendo todo. Abro la lista de ensambladores. Allí escribí con qué frecuencia la ejecución del programa se realizó en una instrucción en particular, es decir, con qué frecuencia hubo un puntero de instrucción. Aquí los números están en porcentaje, y está escrito que casi el 90% de las veces se ejecutó la instrucción% edx,% edx, es decir, verificar cuatro bytes para cero.

La pregunta es: ¿por qué un procesador puede tardar tanto en simplemente comparar cuatro bytes con cero? (respuestas de la audiencia ...) No hay resto de la división. Hay cambios de bit, luego hay una instrucción crc32q, pero como si el puntero de instrucción nunca ocurriera en ella. Y la generación de números aleatorios no está en este listado. Había una función separada, y está muy bien optimizada, no se ralentiza. Algo más se está desacelerando aquí. La ejecución del código se detiene en esta instrucción y pasa mucho tiempo. Idle loop? No ¿Por qué debería insertar bucles vacíos? Además, si inserto el bucle inactivo, eso también sería visible en perf. No hay división por cero, simplemente hay una comparación con cero.

El procesador tiene una tubería, puede ejecutar varias instrucciones en paralelo. Y cuando el puntero de la instrucción está en algún lugar, esto no significa en absoluto que esté ejecutando esta instrucción. Tal vez está esperando otras instrucciones.

Tenemos una tabla hash para verificar que algún número ocurra en algún conjunto. Para esto, hacemos una búsqueda en la memoria. Cuando hacemos una búsqueda en la memoria, tenemos una falta de memoria caché, ya que la tabla hash contiene 100 millones de números, no se garantiza que quepa en ninguna memoria caché. Entonces, para ejecutar la instrucción de verificación cero, estos datos ya deberían estar cargados desde la memoria. Y esperamos hasta que se carguen.



Ahora el siguiente recurso, un poco más complejo: las unidades. Los SSD también se denominan a veces unidades, aunque esto no es del todo correcto. Las SSD también se incluirán en este ejemplo.

Abrimos, por ejemplo, iostat, muestra una utilización del 100%.

En las conferencias, a menudo sucede que el orador sube al escenario y dice con pathos: “Las bases de datos siempre se apoyan en el disco. Por lo tanto, creamos una base de datos en memoria. Ella no disminuirá la velocidad ". Si una persona se te acerca y te lo dice, puedes enviarlo con seguridad. Habrá algunos problemas: dices que lo resolví. :)

Supongamos que un programa se basa en discos, la utilización es 100. Pero esto, por supuesto, no significa que usemos discos de manera óptima.

Un ejemplo típico es cuando solo tienes mucho acceso aleatorio. Incluso si el acceso es secuencial, simplemente lee el archivo secuencialmente, pero aún puede ser más o menos óptimo.

Por ejemplo, tiene una matriz RAID, varios dispositivos, por ejemplo, 8 discos. Y solo lee de forma secuencial sin leer con anticipación, con un tamaño de búfer de 1 MB, y el tamaño del fragmento en su banda en RAID también es de 1 MB. Luego, cada lectura que tendrá de un dispositivo. O, si no está alineado, desde dos dispositivos. Medio megabyte irá a alguna parte, otro medio megabyte a otra parte, y así sucesivamente: los discos se utilizarán por turnos: uno, luego otro, luego un tercero.

Necesita ser leído con anticipación. O, si tiene O_DIRECT, aumente el tamaño del búfer. Es decir, la regla es: 8 discos, tamaño de fragmento 1 MB, establezca el tamaño del búfer en al menos 8 MB. Pero esto funcionará de manera óptima solo si la lectura está alineada. Y si no está alineado, primero habrá piezas adicionales, y debe poner más, multiplicar por unas pocas más.

O, por ejemplo, tiene RAID 10. ¿Con qué velocidad puede leer desde RAID 10, por ejemplo, desde 8 discos? ¿Cuál será la ventaja? Cuádruple, porque hay un espejo, u ocho veces? En realidad, depende de cómo se crea el RAID, con qué disposición de trozos en franjas.

Si usa mdadm en Linux, puede especificar el diseño cercano y el diseño lejano allí, siendo casi mejor para escribir, lejos para leer.

Siempre recomiendo usar un diseño lejano, porque cuando escribes en la base de datos analítica, generalmente no es tan crítico en el tiempo, incluso si hay mucha más escritura que lectura. Esto se hace mediante algún proceso en segundo plano. Pero cuando lees, debes completarlo lo más rápido posible. Por lo tanto, es mejor optimizar RAID para lectura configurando un diseño lejano.

Por suerte, en Linux mdadm lo establecerá en un diseño cercano de forma predeterminada, y obtendrá solo la mitad del rendimiento. Hay muchos rastrillos de este tipo.

Otro rastrillo terrible es RAID 5 o RAID 6. Todo escala bien allí mediante lecturas y escrituras secuenciales. En RAID 5, la multiplicidad es "el número de dispositivos menos uno". Esto escala bien incluso con lecturas aleatorias, pero no escala bien con lecturas aleatorias. Haga un registro en cualquier lugar, y necesita leer los datos de todos los otros discos, introducirlos (XOR - aprox. Ed.) Y escribir en otro lugar. Para esto, se utiliza un cierto caché de tiras, un rastrillo terrible. En Linux, de manera predeterminada, se crea RAID 5 y se ralentizará. Y pensarás que RAID 5 siempre se ralentiza, porque esto es comprensible. Pero, de hecho, la razón es la configuración incorrecta.

Otro ejemplo. Estás leyendo desde un SSD, y te compraste un buen SSD, dice 300 mil lecturas aleatorias por segundo en la especificación. Y por alguna razón no puedes hacerlo. Y usted piensa: sí, todos ellos se encuentran en sus especificaciones, no existe tal cosa. Pero todas estas lecturas deben hacerse en paralelo, con el máximo grado de paralelismo. La única forma de hacer esto de manera óptima es usar E / S asíncrona, que se implementa usando las llamadas al sistema io_submit, io_getevents, io_setup, etc.

Por cierto, los datos en el disco, si los almacena, siempre necesita comprimir. Daré un ejemplo de la práctica. Una persona nos contactó en el chat de soporte de ClickHouse y dijo:

- ClickHouse comprime los datos. Veo que descansa en el procesador. Tengo SSD NVMe muy rápidos, tienen una velocidad de lectura de varios gigabytes por segundo. ¿Es posible de alguna manera deshabilitar la compresión en ClickHouse?
"No, de ninguna manera", le digo. - Necesita mantener los datos comprimidos.
- Detengámonos, solo habrá otro algoritmo de compresión que no hace nada.
- Fácil Ingrese estas letras en esta línea de código.
"De hecho, todo es muy simple", respondió un día después. - Lo hice
- ¿Cuánto ha cambiado el rendimiento?
"No se pudo probar", escribió otro día después. - Hay demasiados datos. Ya no caben en SSD.

Veamos ahora cómo se vería la lectura del disco. Comenzamos dstat, muestra la velocidad de lectura.

El primer ejemplo de dstat y iostat


Aquí está la columna de lectura: 300 MB / s. Leemos de discos. Es mucho o poco, no lo sé.

Ahora ejecuto iostat para verificar esto. Aquí puedes ver el desglose por dispositivo. Tengo RAID, md2 y ocho discos duros. Cada uno de ellos muestra reciclaje, ni siquiera alcanza el 100% (50-60%). Pero lo más importante es que leo de cada disco solo a una velocidad de 20-30 MB / s. Y desde pequeño recordé la regla de que puedes leer en algún lugar desde 100 MB / s desde el disco duro. Por alguna razón, esto todavía no ha cambiado mucho.

Segundo ejemplo de dstat y iostat


Aquí hay otro ejemplo. La lectura es más óptima. Ejecuto dstat y tengo una velocidad de lectura de 1 GB / s de este RAID 5 de ocho unidades. ¿Qué muestra iostat? Sí, casi 1 GB / s.

Ahora las unidades están finalmente cargadas al 100%. Es cierto, por alguna razón, dos son 100%, y el resto son 95%. Probablemente, todavía son un poco diferentes. Pero con cada uno de ellos leo 150 MB / s, incluso más genial de lo que puede ser. Cual es la diferencia En el primer caso, leí con un tamaño de búfer insuficiente en piezas insuficientes. Es simple, te digo verdades comunes.

Por cierto, si cree que los datos aún no necesitan ser comprimidos para la base de datos analítica, es decir, un informe de la conferencia HighLoad ++ Siberia ( habrastaty basado en el informe - aprox . Ed .). Los organizadores decidieron hacer los informes más duros en Novosibirsk.



El siguiente ejemplo es la memoria. Continuando con verdades comunes. Primero, en Linux, nunca vea lo que muestra gratis. Para aquellos que están mirando, crearon especialmente el sitio linuxatemyram.com. Adelante, habrá una explicación. Tampoco necesita mirar la cantidad de memoria virtual, porque ¿cuál es la diferencia, cuánto espacio de direcciones ha asignado el programa? Mira cuánta memoria física se usa.

Y un rastrillo más con el que ni siquiera está claro cómo luchar. Recuerde: el hecho de que a los asignadores a menudo no les gusta dar memoria al sistema es normal. Hicieron mmap, pero munmap ya no lo hace. La memoria no volverá al sistema. El programa piensa: sé mejor cómo usaré la memoria. Me lo dejo a mí mismo. Porque las llamadas al sistema mmap y munmap son bastante lentas. Cambiar el espacio de direcciones, restablecer los cachés TLB del procesador; es mejor no hacerlo. Sin embargo, el sistema operativo todavía tiene la capacidad de liberar memoria correctamente utilizando la llamada al sistema madvise. El espacio de direcciones permanecerá, pero físicamente la memoria se puede descargar.

Y nunca habilite el intercambio en servidores de producción con bases de datos. Usted piensa: no hay suficiente memoria, incluiré el intercambio. Después de eso, la solicitud dejará de funcionar. Romperá el tiempo sin fin.



Con una red de rastrillo demasiado típica. Si crea una conexión TCP cada vez, se tarda un tiempo antes de seleccionar el tamaño de ventana correcto, ya que el protocolo TCP no sabe qué tan rápido será necesario transmitir datos. Se adapta a esto.

O imagine: está transfiriendo un archivo y tiene una gran latencia en su red y una pérdida de paquetes decente. Entonces no es del todo obvio si es correcto usar TCP para transferir archivos. Creo que está mal, ya que TCP garantiza la coherencia. Por otro lado, puede transferir la mitad del archivo y la otra al mismo tiempo.Use al menos varias conexiones TCP o no use TCP en absoluto para la transferencia de datos. Supongamos que si descarga datos, películas y programas de TV con torrentes, TCP no se puede usar allí. Y los datos necesitan ser comprimidos.

Si tiene una red de 100 gigabits dentro del bastidor, no puede comprimirla. Pero si tiene 10 gigabits entre centros de datos, especialmente entre Europa y Estados Unidos, entonces quién sabe cómo se arrastrarán sus bytes bajo el océano. Exprimirlos. Deje arrastrar menos bytes.



¿Todos vieron esta foto? Si todo es lento en el sistema, tiene las herramientas necesarias. Comenzará a usarlos, comenzará a lidiar con el problema y, por experiencia, encontrará otros 10 problemas. Estas herramientas son lo suficientemente potentes como para mantenerte ocupado durante mucho tiempo.



: « - » — . 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%. , . Es muy 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 . Por qué , — page cache. ?

c


. . , , . . : 3,2 , — 2,5 . , , , . Por qué -, : read ahead. , — ? -, — 4 , , 512 KB. . , . , - read ahead.



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

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

— CPUWait. , , , . - — , . CPU. CPU. - , . — , , user space. , - . Bien

— , Linux. - , . , , .

: query_thread_log


Y ahora lo más avanzado que tenemos: query_thread_log. Con él, puede comprender en qué perdió el tiempo cada ejecución de consulta.

Busco mi solicitud, selecciono por query_id e indico la métrica "La cantidad de tiempo de procesador empleado en el espacio del usuario". Aquí están nuestras corrientes. Para el procesamiento paralelo de la solicitud, se asignaron 16 hilos. Cada uno de ellos pasó 800 ms. Y luego se asignaron otros 16 subprocesos para la fusión del estado de las funciones agregadas, se gastaron 0.25 s en cada uno de ellos. Ahora puedo entender exactamente lo que cada solicitud tomó tiempo.

Informe en video sobre HighLoad ++:

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


All Articles