Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

El informe presenta algunos enfoques que permiten monitorear el rendimiento de las consultas SQL cuando hay millones de ellas por díay hay cientos de servidores PostgreSQL monitoreados.

¿Qué soluciones técnicas nos permiten procesar de manera eficiente tal volumen de información y cómo facilita esto la vida de un desarrollador común y corriente?


¿Quien esta interesado? Análisis de problemas específicos y diversas técnicas de optimización. Consultas SQL y resolución de problemas típicos de DBA en PostgreSQL: también puede leer una serie de artículos sobre este tema.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)
Mi nombre es Kirill Borovikov, represento empresa tensorial. En concreto, me especializo en el trabajo con bases de datos en nuestra empresa.

Hoy te contaré cómo optimizamos las consultas, cuando no necesitas "separar" el rendimiento de una sola consulta, sino resolver el problema en masa. Cuando hay millones de solicitudes y necesitas encontrar algunas enfoques para la solución este gran problema.

En general, Tensor para un millón de nuestros clientes es VLSI es nuestra aplicación: red social corporativa, soluciones para comunicación por vídeo, para el flujo de documentos internos y externos, sistemas contables para contabilidad y almacenes,... Es decir, una “megacombinadora” para la gestión empresarial integrada, en la que hay más de 100 diferentes proyectos internos.

Para que todos funcionen y se desarrollen con normalidad, contamos con 10 centros de desarrollo en todo el país, y hay más en ellos 1000 desarrolladores.

Trabajamos con PostgreSQL desde 2008 y hemos acumulado una gran cantidad de lo que procesamos (datos de clientes, estadísticos, analíticos, datos de sistemas de información externos). más de 400 TB. Solo hay alrededor de 250 servidores en producción y en total hay alrededor de 1000 servidores de bases de datos que monitoreamos.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

SQL es un lenguaje declarativo. No describe "cómo" debería funcionar algo, sino "qué" quiere lograr. El DBMS sabe mejor cómo hacer una JOIN: cómo conectar sus tablas, qué condiciones imponer, qué pasará por el índice, qué no...

Algunos DBMS aceptan sugerencias: "No, conecte estas dos tablas en tal o cual cola", pero PostgreSQL no puede hacer esto. Esta es la posición consciente de los desarrolladores líderes: "Preferimos terminar el optimizador de consultas que permitir que los desarrolladores utilicen algún tipo de sugerencia".

Pero, a pesar de que PostgreSQL no permite que el “exterior” se controle, permite perfectamente mira lo que esta pasando dentro de elcuándo ejecuta su consulta y dónde tiene problemas.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

En general, ¿con qué problemas clásicos suele encontrarse un desarrollador [de un DBA]? “Aquí cumplimos el pedido, y todo es lento con nosotros, todo está colgado, algo está pasando... ¡Algún tipo de problema!

Las razones son casi siempre las mismas:

  • algoritmo de consulta ineficiente
    Desarrollador: "Ahora le estoy dando 10 tablas en SQL a través de JOIN..." - y espera que sus condiciones se "desatan" milagrosamente de manera efectiva y obtendrá todo rápidamente. Pero los milagros no suceden, y cualquier sistema con tal variabilidad (10 tablas en una DE) siempre da algún tipo de error. [artículo]
  • estadísticas irrelevantes
    Este punto es muy relevante específicamente para PostgreSQL, cuando "viertes" un gran conjunto de datos en el servidor, haces una solicitud y "sexcaniza" tu tableta. Porque ayer había 10 registros y hoy hay 10 millones, pero PostgreSQL aún no lo sabe y debemos contárselo. [artículo]
  • "conectar" los recursos
    Ha instalado una base de datos grande y muy cargada en un servidor débil que no tiene suficiente rendimiento de disco, memoria o procesador. Y eso es todo... En algún lugar existe un límite de rendimiento por encima del cual ya no se puede saltar.
  • bloqueo
    Este es un punto difícil, pero es más relevante para varias consultas de modificación (INSERTAR, ACTUALIZAR, ELIMINAR); este es un gran tema aparte.

Obtener un plan

...Y para todo lo demás nosotros necesito un plan! Necesitamos ver qué está sucediendo dentro del servidor.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Un plan de ejecución de consultas para PostgreSQL es un árbol del algoritmo de ejecución de consultas en representación de texto. Precisamente el algoritmo que, tras el análisis del planificador, resultó ser el más eficaz.

Cada nodo del árbol es una operación: recuperar datos de una tabla o índice, crear un mapa de bits, unir dos tablas, unir, intersecar o excluir selecciones. Ejecutar una consulta implica recorrer los nodos de este árbol.

Para obtener el plan de consulta, la forma más sencilla es ejecutar la declaración EXPLAIN. Obtener todos los atributos reales, es decir, ejecutar realmente una consulta en la base: EXPLAIN (ANALYZE, BUFFERS) SELECT ....

Lo malo: cuando lo ejecutas, sucede "aquí y ahora", por lo que solo es adecuado para la depuración local. Si toma un servidor muy cargado que está bajo un fuerte flujo de cambios de datos y ve: “¡Oh! Aquí tenemos una ejecución lenta.Xia pedido." Hace media hora, hace una hora, mientras ejecutaba y obtenía esta solicitud de los registros y la devolvía al servidor, todo su conjunto de datos y estadísticas cambiaron. Lo ejecuta para depurar, ¡y se ejecuta rápidamente! Y no puedes entender por qué, por qué era despacio.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Para comprender qué sucedió exactamente en el momento en que se ejecutó la solicitud en el servidor, personas inteligentes escribieron módulo auto_explain. Está presente en casi todas las distribuciones de PostgreSQL más comunes y puede activarse simplemente en el archivo de configuración.

Si se da cuenta de que alguna solicitud se está ejecutando por más tiempo que el límite que le indicó, lo hace. “instantánea” del plan de esta solicitud y los escribe juntos en el registro.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Todo parece estar bien ahora, vamos al tronco y vemos allí... [texto de tela]. Pero no podemos decir nada al respecto, aparte del hecho de que es un plan excelente porque tardó 11 ms en ejecutarse.

Todo parece ir bien, pero no está claro qué pasó realmente. Aparte del tiempo general, realmente no vemos nada. Porque mirar un “cordero” de texto plano generalmente no es visual.

Pero incluso si no es obvio, incluso si es inconveniente, hay problemas más fundamentales:

  • El nodo indica suma de recursos de todo el subárbol debajo de él. Es decir, no se puede saber simplemente cuánto tiempo se dedicó a este Index Scan en particular si existe alguna condición anidada debajo de él. Debemos observar dinámicamente si hay "hijos" y variables condicionales, CTE en su interior, y restar todo esto "en nuestras mentes".
  • Segundo punto: la hora que está indicada en el nodo es tiempo de ejecución de un solo nodo. Si este nodo se ejecutó como resultado de, por ejemplo, un bucle a través de los registros de la tabla varias veces, entonces el número de bucles (ciclos de este nodo) aumenta en el plan. Pero el tiempo de ejecución atómica en sí sigue siendo el mismo en términos de plan. Es decir, para comprender cuánto tiempo se realizó este nodo en total, debes multiplicar una cosa por otra, nuevamente, "en tu cabeza".

En tales situaciones, comprenda "¿Quién es el eslabón más débil?" casi imposible. Por lo tanto, incluso los propios desarrolladores escriben en el "manual" que “Comprender un plan es un arte que hay que aprender, experimentar...”.

Pero tenemos 1000 desarrolladores y no se puede transmitir esta experiencia a cada uno de ellos. Yo, tú, él lo sé, pero alguien de allá ya no lo sabe. Tal vez aprenda, o tal vez no, pero necesita trabajar ahora. ¿Y de dónde sacará esta experiencia?

Visualización del plano

Por lo tanto, nos dimos cuenta de que para abordar estos problemas, necesitamos buena visualización del plan. [artículo]

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Primero fuimos "a través del mercado"; busquemos en Internet para ver qué existe.

Pero resultó que hay muy pocas soluciones relativamente "vivas" que estén más o menos en desarrollo; literalmente, solo una: explicar.depesz.com por Hubert Lubaczewski. Cuando ingresa al campo "feed" una representación de texto del plan, le muestra una tabla con los datos analizados:

  • tiempo de procesamiento propio del nodo
  • tiempo total para todo el subárbol
  • Número de registros recuperados que se esperaban estadísticamente.
  • el propio cuerpo del nodo

Este servicio también tiene la capacidad de compartir un archivo de enlaces. Lanzaste tu plan allí y dijiste: "Oye, Vasya, aquí tienes un enlace, hay algo mal allí".

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Pero también hay pequeños problemas.

En primer lugar, una gran cantidad de "copiar y pegar". Tomas un trozo del tronco, lo pegas allí, una y otra vez.

En segundo lugar, la sin análisis de la cantidad de datos leídos - los mismos buffers que generan EXPLAIN (ANALYZE, BUFFERS), no lo vemos aquí. Simplemente no sabe cómo desmontarlos, entenderlos y trabajar con ellos. Cuando estás leyendo una gran cantidad de datos y te das cuenta de que es posible que estés asignando mal el disco y la memoria caché, esta información es muy importante.

El tercer punto negativo es el muy débil desarrollo de este proyecto. Las confirmaciones son muy pequeñas, es bueno que sean una vez cada seis meses y el código está en Perl.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Pero todo esto son "letras", de alguna manera podríamos vivir con esto, pero hay una cosa que nos alejó enormemente de este servicio. Estos son errores en el análisis de Common Table Expression (CTE) y varios nodos dinámicos como InitPlan/SubPlan.

Si cree en esta imagen, entonces el tiempo total de ejecución de cada nodo individual es mayor que el tiempo total de ejecución de toda la solicitud. Es sencillo - el tiempo de generación de este CTE no se restó del nodo CTE Scan. Por lo tanto, ya no sabemos la respuesta correcta sobre cuánto tiempo tomó la exploración CTE.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Entonces nos dimos cuenta de que había llegado el momento de escribir el nuestro: ¡hurra! Todos los desarrolladores dicen: "¡Ahora escribiremos el nuestro, será muy fácil!"

Tomamos una pila típica de servicios web: un núcleo basado en Node.js + Express, usamos Bootstrap y D3.js para obtener hermosos diagramas. Y nuestras expectativas se cumplieron plenamente: recibimos el primer prototipo en 2 semanas:

  • analizador de planes personalizados
    Es decir, ahora podemos analizar cualquier plan de los generados por PostgreSQL.
  • análisis correcto de nodos dinámicos - Escaneo CTE, InitPlan, SubPlan
  • análisis de distribución de buffers - dónde se leen las páginas de datos desde la memoria, dónde desde el caché local, dónde desde el disco
  • tengo claridad
    Para no "excavar" todo esto en el registro, sino para ver el "eslabón más débil" inmediatamente en la imagen.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Obtuvimos algo como esto, con resaltado de sintaxis incluido. Pero normalmente nuestros desarrolladores ya no trabajan con una representación completa del plan, sino con una más corta. Después de todo, ya analizamos todos los números y los tiramos a izquierda y derecha, y en el medio dejamos solo la primera línea, qué tipo de nodo es: CTE Scan, generación CTE o Seq Scan según algún signo.

Esta es la representación abreviada que llamamos plantilla de plan.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

¿Qué más sería conveniente? Sería conveniente ver qué parte de nuestro tiempo total se asigna a cada nodo y simplemente "dejarlo" a un lado. Gráfico circular.

Señalamos el nodo y vemos: resulta que Seq Scan tomó menos de una cuarta parte del tiempo total y los 3/4 restantes fueron tomados por CTE Scan. ¡Horror! Esta es una pequeña nota sobre la "velocidad de disparo" de CTE Scan si los utiliza activamente en sus consultas. No son muy rápidos; son inferiores incluso al escaneo de tablas normal. [artículo] [artículo]

Pero normalmente estos diagramas son más interesantes, más complejos, cuando apuntamos inmediatamente a un segmento y vemos, por ejemplo, que más de la mitad del tiempo algún Seq Scan “se comió”. Además, dentro había una especie de filtro, según él se descartaron muchos registros... Puedes enviarle esta imagen directamente al desarrollador y decirle: “¡Vasya, aquí todo está mal para ti! Descúbrelo, mira: ¡algo anda mal!”

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Naturalmente, hubo algunos “rastrillos” involucrados.

Lo primero que encontramos fue el problema del redondeo. El tiempo de cada nodo individual en el plan se indica con una precisión de 1 μs. Y cuando el número de ciclos de nodo excede, por ejemplo, 1000, después de la ejecución, PostgreSQL se divide "con precisión", luego, al calcular, obtenemos el tiempo total "entre 0.95 ms y 1.05 ms". Cuando el conteo llega a microsegundos, está bien, pero cuando ya son [mili]segundos, hay que tener en cuenta esta información al “desatar” recursos a los nodos del plan “quién consumió cuánto”.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

El segundo punto, más complejo, es la distribución de recursos (esos buffers) entre nodos dinámicos. Esto nos costó las primeras 2 semanas del prototipo más otras 4 semanas.

Es bastante fácil tener este tipo de problema: hacemos un CTE y supuestamente leemos algo en él. De hecho, PostgreSQL es "inteligente" y no leerá nada directamente allí. Luego le sacamos el primer registro, y a él el centésimo primero del mismo CTE.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Miramos el plan y lo entendemos: es extraño, tenemos 3 buffers (páginas de datos) "consumidos" en Seq Scan, 1 más en CTE Scan y 2 más en el segundo CTE Scan. Es decir, si simplemente sumamos todo, obtendremos 6, ¡pero desde la tableta solo leemos 3! CTE Scan no lee nada desde ningún lado, sino que trabaja directamente con la memoria del proceso. Es decir, ¡algo claramente anda mal aquí!

De hecho resulta que aquí están todas esas 3 páginas de datos que le solicitaron a Seq Scan, primero 1 pidió el 1er CTE Scan, luego la 2da y le leyeron 2 más, es decir, un total de Se leyeron 3 páginas de datos, no 6.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Y esta imagen nos llevó a comprender que la ejecución de un plan ya no es un árbol, sino simplemente una especie de gráfico acíclico. Y obtuvimos un diagrama como este, para que entendamos “qué vino de dónde en primer lugar”. Es decir, aquí creamos un CTE desde pg_class y lo solicitamos dos veces, y casi todo nuestro tiempo lo pasamos en la sucursal cuando lo solicitamos por segunda vez. Está claro que leer la entrada 2 es mucho más caro que simplemente leer la entrada 101 desde la tableta.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Exhalamos un rato. Dijeron: “¡Ahora, Neo, sabes kung fu! Ahora nuestra experiencia está justo en tu pantalla. Ahora puedes usarlo." [artículo]

Consolidación de registros

Nuestros 1000 desarrolladores dieron un suspiro de alivio. Pero entendimos que solo tenemos cientos de servidores de "combate", y todo este "copiar y pegar" por parte de los desarrolladores no es nada conveniente. Nos dimos cuenta de que teníamos que recogerlo nosotros mismos.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

En general, existe un módulo estándar que puede recopilar estadísticas, sin embargo, también debe activarse en la configuración; esto módulo pg_stat_statements. Pero no nos convenía.

En primer lugar, asigna las mismas consultas utilizando diferentes esquemas dentro de la misma base de datos. diferentes ID de consulta. Es decir, si primero haces SET search_path = '01'; SELECT * FROM user LIMIT 1;y luego SET search_path = '02'; y la misma solicitud, entonces las estadísticas de este módulo tendrán registros diferentes y no podré recopilar estadísticas generales específicamente en el contexto de este perfil de solicitud, sin tener en cuenta los esquemas.

El segundo punto que nos impidió usarlo es falta de planes. Es decir, no hay ningún plan, sólo existe la petición en sí. Vemos lo que se estaba desacelerando, pero no entendemos por qué. Y aquí volvemos al problema de un conjunto de datos que cambia rápidamente.

Y el último momento falta de "hechos". Es decir, no se puede abordar una instancia específica de ejecución de consultas; no hay ninguna, solo hay estadísticas agregadas. Aunque es posible trabajar con esto, es muy difícil.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Por eso, decidimos luchar contra el copiar y pegar y comenzamos a escribir. colector.

El recopilador se conecta a través de SSH, establece una conexión segura con el servidor con la base de datos mediante un certificado y tail -F "se aferra" a él en el archivo de registro. Entonces en esta sesión obtenemos un "espejo" completo de todo el archivo de registro, que genera el servidor. La carga en el servidor en sí es mínima, porque no analizamos nada allí, solo reflejamos el tráfico.

Como ya habíamos comenzado a escribir la interfaz en Node.js, continuamos escribiendo el recopilador en ella. Y esta tecnología se justificó porque es muy conveniente usar JavaScript para trabajar con datos de texto con formato débil, como es el registro. Y la propia infraestructura de Node.js como plataforma backend le permite trabajar fácil y cómodamente con conexiones de red y, de hecho, con cualquier flujo de datos.

En consecuencia, "estiramos" dos conexiones: la primera para "escuchar" el propio registro y llevárnoslo, y la segunda para preguntar periódicamente a la base. "Pero el registro muestra que el signo con oid 123 está bloqueado", pero esto no significa nada para el desarrollador, y sería bueno preguntarle a la base de datos: "¿Qué es OID = 123 de todos modos?" Y por eso periódicamente preguntamos a la base lo que aún no sabemos sobre nosotros mismos.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

“Solo hay una cosa que no tomaste en cuenta: ¡hay una especie de abejas parecidas a elefantes!…” Comenzamos a desarrollar este sistema cuando queríamos monitorear 10 servidores. Los más críticos a nuestro entender, donde surgieron algunos problemas difíciles de afrontar. Pero durante el primer trimestre recibimos cien para el seguimiento, porque el sistema funcionó, todos lo querían, todos estaban cómodos.

Todo esto hay que sumarlo, el flujo de datos es grande y activo. De hecho, lo que monitoreamos, lo que podemos abordar, es lo que utilizamos. También utilizamos PostgreSQL como almacenamiento de datos. Y nada es más rápido para "verter" datos en él que el operador. COPY Aún no.

Pero simplemente “verter” datos no es realmente nuestra tecnología. Porque si tiene aproximadamente 50 solicitudes por segundo en cien servidores, esto generará entre 100 y 150 GB de registros por día. Por lo tanto, tuvimos que “cortar” con cuidado la base.

En primer lugar, hicimos partición por día, porque, en general, a nadie le interesa la correlación entre días. ¿Qué más da lo que tenía ayer si esta noche ha lanzado una nueva versión de la aplicación y ya algunas estadísticas nuevas?

En segundo lugar, aprendimos (fuimos obligados) muy, muy rápido de escribir usando COPY. Es decir, no sólo COPYporque es más rápido que INSERT, y aún más rápido.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

El tercer punto: tuve que abandonar desencadenadores, respectivamente, y claves externas. Es decir, no tenemos integridad referencial alguna. Porque si tiene una tabla que tiene un par de FK y dice en la estructura de la base de datos que "aquí hay un registro al que FK hace referencia, por ejemplo, a un grupo de registros", entonces, cuando lo inserte, PostgreSQL No le queda más que cómo tomarlo y hacerlo honestamente. SELECT 1 FROM master_fk1_table WHERE ... con el identificador que está intentando insertar, solo para verificar que este registro esté presente allí, que no "rompe" esta clave externa con su inserción.

En lugar de un registro en la tabla de destino y sus índices, obtenemos el beneficio adicional de leer todas las tablas a las que hace referencia. Pero no lo necesitamos en absoluto: nuestra tarea es grabar tanto como sea posible y lo más rápido posible con la menor carga. Entonces FK - ¡abajo!

El siguiente punto es la agregación y el hash. Inicialmente, los implementamos en la base de datos; después de todo, es conveniente hacerlo inmediatamente, cuando llega un registro, en algún tipo de tableta. "más uno" justo en el gatillo. Bueno, es conveniente, pero lo mismo es malo: insertas un registro, pero te ves obligado a leer y escribir algo más de otra tabla. Es más, no sólo lees y escribes, sino que también lo haces siempre.

Ahora imagina que tienes una tabla en la que simplemente cuentas el número de solicitudes que han pasado por un host específico: +1, +1, +1, ..., +1. Y usted, en principio, no lo necesita, todo es posible. suma en la memoria del coleccionista y enviar a la base de datos de una sola vez +10.

Sí, en caso de algunos problemas, su integridad lógica puede "desmoronarse", pero este es un caso casi irreal: porque tiene un servidor normal, tiene una batería en el controlador, tiene un registro de transacciones, un registro en el sistema de archivos... En general, no vale la pena. La pérdida de productividad que se produce al ejecutar activadores/FK no justifica el gasto en el que incurre.

Lo mismo ocurre con el hash. Una determinada solicitud llega a usted, usted calcula un determinado identificador a partir de ella en la base de datos, la escribe en la base de datos y luego se la cuenta a todos. Todo va bien hasta que, en el momento de grabar, se te acerca una segunda persona que quiere grabar lo mismo, y te bloqueas, y esto ya es malo. Por lo tanto, si puede transferir la generación de algunas ID al cliente (en relación con la base de datos), es mejor hacerlo.

Para nosotros fue perfecto usar MD5 a partir del texto: solicitud, plan, plantilla,... Lo calculamos en el lado del recopilador y “vertemos” la identificación ya preparada en la base de datos. La longitud de MD5 y la partición diaria nos permiten no preocuparnos por posibles colisiones.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Pero para poder grabar todo esto rápidamente, necesitábamos modificar el procedimiento de grabación.

¿Cómo sueles escribir datos? Tenemos algún tipo de conjunto de datos, lo dividimos en varias tablas y luego lo COPIAMOS: primero en la primera, luego en la segunda, en la tercera... Es un inconveniente, porque parece que estamos escribiendo un flujo de datos en tres pasos. secuencialmente. Desagradable. ¿Se puede hacer más rápido? ¡Poder!

Para ello, basta con descomponer estos flujos en paralelo entre sí. Resulta que tenemos errores, solicitudes, plantillas, bloqueos, ... volando en hilos separados, y lo escribimos todo en paralelo. suficiente para esto mantener un canal COPY constantemente abierto para cada tabla de destino individual.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Es decir, en el coleccionista. siempre hay una corriente, en el que puedo escribir los datos que necesito. Pero para que la base de datos vea estos datos y nadie se quede atascado esperando a que se escriban estos datos, La COPIA debe interrumpirse a determinados intervalos.. Para nosotros, el período más efectivo fue de aproximadamente 100 ms: lo cerramos e inmediatamente lo volvemos a abrir en la misma tabla. Y si no tenemos suficiente flujo durante algunos picos, entonces agrupamos hasta un cierto límite.

Además, descubrimos que para dicho perfil de carga, cualquier agregación, cuando los registros se recopilan en lotes, es mala. El mal clásico es INSERT ... VALUES y más 1000 registros. Porque en ese punto tienes un pico de escritura en el medio y todos los demás que intenten escribir algo en el disco estarán esperando.

Para deshacerse de tales anomalías, simplemente no agregue nada, no almacene en búfer en absoluto. Y si se produce un almacenamiento en búfer en el disco (afortunadamente, la API Stream en Node.js le permite averiguarlo), posponga esta conexión. Cuando recibas un evento que vuelve a ser gratuito, escríbele desde la cola acumulada. Y mientras esté ocupado, toma el siguiente libre del grupo y escríbele.

Antes de introducir este enfoque en la grabación de datos, teníamos operaciones de escritura de aproximadamente 4K y de esta manera redujimos la carga 4 veces. Ahora han crecido otras 6 veces gracias a las nuevas bases de datos monitoreadas: hasta 100 MB/s. Y ahora almacenamos registros de los últimos 3 meses en un volumen de aproximadamente 10-15 TB, con la esperanza de que en solo tres meses cualquier desarrollador pueda resolver cualquier problema.

Entendemos los problemas

Pero simplemente recopilar todos estos datos es bueno, útil y relevante, pero no suficiente: es necesario comprenderlos. Porque son millones de planes diferentes por día.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Pero millones son inmanejables, primero debemos hacerlo “más pequeño”. Y, antes que nada, debes decidir cómo organizarás esta cosa "más pequeña".

Hemos identificado tres puntos clave:

  • que envió esta solicitud
    Es decir, de qué aplicación “llegó”: interfaz web, backend, sistema de pago u otra cosa.
  • donde ocurrió
    ¿En qué servidor específico? Porque si tiene varios servidores bajo una aplicación y de repente uno "se vuelve estúpido" (porque el "disco está podrido", "pérdida de memoria", algún otro problema), entonces necesita abordar específicamente el servidor.
  • cómo el problema se manifestó de una forma u otra

Para comprender "quién" nos envió una solicitud, utilizamos una herramienta estándar: configurando una variable de sesión: SET application_name = '{bl-host}:{bl-method}'; — enviamos el nombre del host de lógica de negocios del que proviene la solicitud y el nombre del método o aplicación que la inició.

Una vez que hayamos pasado el "propietario" de la solicitud, debemos enviarlo al registro; para esto configuramos la variable log_line_prefix = ' %m [%p:%v] [%d] %r %a'. Para aquellos interesados, tal vez mira en el manualQue significa todo esto. Resulta que vemos en el registro:

  • tiempo
  • identificadores de procesos y transacciones
  • nombre de la base de datos
  • IP de la persona que envió esta solicitud
  • y nombre del método

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Luego nos dimos cuenta de que no es muy interesante observar la correlación de una solicitud entre diferentes servidores. No es frecuente que te encuentres en una situación en la que una aplicación falla por igual aquí y allá. Pero aunque sea lo mismo, mira cualquiera de estos servidores.

Así que aquí está el corte. "un servidor - un día" resultó ser suficiente para cualquier análisis.

La primera sección analítica es la misma. "muestra" - una forma abreviada de presentación del plan, libre de todos los indicadores numéricos. El segundo corte es la aplicación o método, y el tercer corte es el nodo del plan específico que nos causó problemas.

Cuando pasamos de instancias específicas a plantillas, obtuvimos dos ventajas a la vez:

  • reducción múltiple en el número de objetos para el análisis
    Tenemos que analizar el problema ya no por miles de consultas o planes, sino por decenas de plantillas.
  • línea de tiempo
    Es decir, al resumir los “hechos” dentro de una determinada sección, puedes mostrar su apariencia durante el día. Y aquí puedes entender que si tienes algún tipo de patrón que sucede, por ejemplo, una vez cada hora, pero debería suceder una vez al día, debes pensar en qué salió mal: quién lo causó y por qué, tal vez debería estar aquí. no debería. Este es otro método de análisis no numérico, puramente visual.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

El resto de métodos se basan en los indicadores que extraemos del plan: cuántas veces se produjo dicho patrón, el tiempo total y medio, cuántos datos se leyeron del disco y cuántos de la memoria...

Porque, por ejemplo, llega a la página de análisis del host y mira: algo está empezando a leer demasiado en el disco. El disco del servidor no puede soportarlo. ¿Quién lee en él?

Y puede ordenar por cualquier columna y decidir con qué se ocupará en este momento: la carga en el procesador o el disco, o el número total de solicitudes... Lo ordenamos, miramos los "superiores", lo arreglamos y lanzó una nueva versión de la aplicación.
[videoconferencia]

E inmediatamente podrás ver diferentes aplicaciones que vienen con la misma plantilla a partir de una solicitud como SELECT * FROM users WHERE login = 'Vasya'. Frontend, backend, procesamiento… Y te preguntas por qué el procesamiento leería al usuario si este no interactúa con él.

La forma opuesta es ver inmediatamente en la aplicación lo que hace. Por ejemplo, la interfaz es this, this, this y this una vez por hora (la línea de tiempo ayuda). Y de inmediato surge la pregunta: parece que no es trabajo del frontend hacer algo una vez cada hora...

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Después de un tiempo, nos dimos cuenta de que nos faltaba información agregada. estadísticas por nodos del plan. Aislamos de los planes solo aquellos nodos que hacen algo con los datos de las tablas (leerlos/escribirlos por índice o no). De hecho, solo se agrega un aspecto con respecto a la imagen anterior: ¿Cuantos registros nos trajo este nodo?y cuántas se descartaron (filas eliminadas por filtro).

No tienes un índice adecuado en la placa, le haces una solicitud, pasa volando el índice, cae en Seq Scan... has filtrado todos los registros excepto uno. ¿Por qué necesitas 100 millones de registros filtrados por día? ¿No es mejor acumular el índice?

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Después de analizar todos los planos nodo por nodo, nos dimos cuenta de que hay algunas estructuras típicas en los planos que probablemente parezcan sospechosas. Y sería bueno decirle al desarrollador: "Amigo, aquí primero lees por índice, luego clasificas y luego cortas"; como regla general, hay un registro.

Todos los que escribieron consultas probablemente se hayan encontrado con este patrón: "Dame el último pedido de Vasya, su fecha". Y si no tienes un índice por fecha, o no hay ninguna fecha en el índice que usaste, entonces pisar exactamente el mismo “rastrillo”.

Pero sabemos que esto es un "rastrillo", así que ¿por qué no decirle inmediatamente al desarrollador lo que debe hacer? En consecuencia, al abrir un plan ahora, nuestro desarrollador ve inmediatamente una hermosa imagen con consejos, donde inmediatamente le dicen: "Tienes problemas aquí y allá, pero se resuelven de esta manera".

Como resultado, la cantidad de experiencia que se necesitaba para resolver problemas al principio y ahora se ha reducido significativamente. Este es el tipo de herramienta que tenemos.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (Tensor)

Fuente: habr.com

Añadir un comentario