PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Muchos de los que ya están usando Explique.tensor.ru - Es posible que nuestro servicio de visualización de planes PostgreSQL no sea consciente de uno de sus superpoderes: convertir una parte difícil de leer del registro del servidor...

PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta
... en una consulta bellamente diseñada con sugerencias contextuales para los nodos del plan correspondientes:

PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta
En esta transcripción de la segunda parte de su informe en PGConf.Rusia 2020 Te diré cómo logramos hacer esto.

La transcripción de la primera parte, dedicada a los problemas típicos de rendimiento de consultas y sus soluciones, se puede encontrar en el artículo. "Recetas para consultas SQL defectuosas".



Primero, comencemos a colorear, y ya no colorearemos el plan, ya lo hemos coloreado, ya lo tenemos hermoso y comprensible, pero una solicitud.

Nos pareció que con una "hoja" sin formato, la solicitud extraída del registro se ve muy fea y, por lo tanto, inconveniente.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Especialmente cuando los desarrolladores "pegan" el cuerpo de la solicitud en el código (esto es, por supuesto, un antipatrón, pero sucede) en una línea. ¡Horrible!

Dibujemos esto de alguna manera más hermosa.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Y si podemos dibujar esto bellamente, es decir, desmontar y volver a armar el cuerpo de la solicitud, entonces podemos "adjuntar" una pista a cada objeto de esta solicitud: lo que sucedió en el punto correspondiente del plan.

Árbol de sintaxis de consulta

Para hacer esto, primero se debe analizar la solicitud.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Porque tenemos el núcleo del sistema se ejecuta en NodeJS, luego creamos un módulo para ello, puedes encuéntralo en GitHub. De hecho, estos son "vínculos" extendidos a las partes internas del propio analizador PostgreSQL. Es decir, la gramática se compila simplemente en binario y se realizan enlaces desde NodeJS. Tomamos como base los módulos de otras personas; aquí no hay ningún gran secreto.

Alimentamos el cuerpo de la solicitud como entrada a nuestra función; en la salida obtenemos un árbol de sintaxis analizado en forma de un objeto JSON.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Ahora podemos recorrer este árbol en la dirección opuesta y armar una solicitud con las sangrías, el color y el formato que queramos. No, esto no es personalizable, pero nos pareció que sería conveniente.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Mapeo de nodos de consulta y plan

Ahora veamos cómo podemos combinar el plan que analizamos en el primer paso y la consulta que analizamos en el segundo.

Tomemos un ejemplo simple: tenemos una consulta que genera un CTE y lo lee dos veces. Él genera tal plan.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

CTE

Si lo miras detenidamente, hasta la versión 12 (o a partir de ella con la palabra clave MATERIALIZED) formación CTE es una barrera absoluta para el planificador.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Esto significa que si vemos una generación de CTE en algún lugar de la solicitud y un nodo en algún lugar del plan CTE, entonces estos nodos definitivamente "luchan" entre sí, podemos combinarlos inmediatamente.

Problema con un asterisco: Los CTE se pueden anidar.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta
Los hay muy mal anidados, e incluso algunos con el mismo nombre. Por ejemplo, puedes dentro CTE A сделать CTE X, y al mismo nivel en el interior CTE B hazlo otra vez CTE X:

WITH A AS (
  WITH X AS (...)
  SELECT ...
)
, B AS (
  WITH X AS (...)
  SELECT ...
)
...

Al comparar, debes entender esto. Comprender esto "con los ojos", incluso ver el plan, incluso ver el cuerpo de la solicitud, es muy difícil. Si su generación de CTE es compleja, anidada y las solicitudes son grandes, entonces es completamente inconsciente.

UNIÓN

Si tenemos una palabra clave en la consulta UNION [ALL] (operador de unir dos muestras), entonces en el plano corresponde a un nodo Append, o algunos Recursive Union.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Lo que está "arriba" arriba UNION - este es el primer descendiente de nuestro nodo, que está "abajo" - el segundo. si a través UNION tenemos varios bloques “pegados” a la vez, entonces Append-Seguirá habiendo un solo nodo, pero no tendrá dos, sino muchos hijos, en el orden en que van, respectivamente:

  (...) -- #1
UNION ALL
  (...) -- #2
UNION ALL
  (...) -- #3

Append
  -> ... #1
  -> ... #2
  -> ... #3

Problema con un asterisco: dentro de la generación de muestreo recursivo (WITH RECURSIVE) también puede ser más de uno UNION. Pero sólo el último bloque después del último es siempre recursivo. UNION. Todo lo anterior es uno, pero diferente. UNION:

WITH RECURSIVE T AS(
  (...) -- #1
UNION ALL
  (...) -- #2, тут кончается генерация стартового состояния рекурсии
UNION ALL
  (...) -- #3, только этот блок рекурсивный и может содержать обращение к T
)
...

También es necesario poder "destacar" estos ejemplos. En este ejemplo vemos que UNION-Había 3 segmentos en nuestra solicitud. En consecuencia, uno UNION partido Append-nodo, y al otro - Recursive Union.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Datos de lectura y escritura

Todo está dispuesto, ahora sabemos qué parte de la solicitud corresponde a qué parte del plan. Y en estas piezas podemos encontrar de forma fácil y natural aquellos objetos que son “legibles”.

Desde el punto de vista de la consulta no sabemos si es una tabla o un CTE, pero están designados por el mismo nodo. RangeVar. Y en términos de "legibilidad", este también es un conjunto bastante limitado de nodos:

  • Seq Scan on [tbl]
  • Bitmap Heap Scan on [tbl]
  • Index [Only] Scan [Backward] using [idx] on [tbl]
  • CTE Scan on [cte]
  • Insert/Update/Delete on [tbl]

Conocemos la estructura del plan y la consulta, conocemos la correspondencia de los bloques, conocemos los nombres de los objetos; hacemos una comparación uno a uno.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Otra vez tarea "con un asterisco". Tomamos la solicitud, la ejecutamos, no tenemos ningún alias, simplemente la leemos dos veces desde el mismo CTE.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Miramos el plan: ¿cuál es el problema? ¿Por qué teníamos un alias? No lo pedimos. ¿De dónde saca ese “número número”?

PostgreSQL lo agrega él mismo. Solo necesitas entender eso tal alias Para nosotros, a efectos de comparación con el plan, no tiene ningún sentido, simplemente se añade aquí. No le hagamos caso.

El segundo tarea "con un asterisco": si estamos leyendo de una tabla particionada, obtendremos un nodo Append o Merge Append, que estará formado por un gran número de "niños", y cada uno de los cuales será de alguna manera Scan'om de la sección de la tabla: Seq Scan, Bitmap Heap Scan o Index Scan. Pero, en cualquier caso, estos "hijos" no serán consultas complejas; así es como se pueden distinguir estos nodos de Append en UNION.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

También entendemos esos nudos, los recogemos "en una pila" y decimos: "todo lo que lees en megatable está aquí y en el árbol".

Nodos de recepción de datos "simples"

PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Values Scan corresponde en plan VALUES en la solicitud.

Result es una petición sin FROM tipo de SELECT 1. O cuando tienes una expresión deliberadamente falsa en WHERE-block (entonces aparece el atributo One-Time Filter):

EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- или 0 = 1

Result  (cost=0.00..0.00 rows=0 width=230) (actual time=0.000..0.000 rows=0 loops=1)
  One-Time Filter: false

Function Scan “mapa” a los SRF del mismo nombre.

Pero con las consultas anidadas todo es más complicado; desafortunadamente, no siempre se convierten en InitPlan/SubPlan. A veces se convierten en ... Join o ... Anti Join, especialmente cuando escribes algo como WHERE NOT EXISTS .... Y aquí no siempre es posible combinarlos: en el texto del plan no hay operadores correspondientes a los nodos del plan.

Otra vez tarea "con un asterisco": alguno VALUES en la solicitud. En este caso y en el plan obtendrás varios nodos. Values Scan.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Los sufijos "numerados" ayudarán a distinguirlos entre sí: se agregan exactamente en el orden en que se encuentran los correspondientes. VALUES-bloques a lo largo de la solicitud de arriba a abajo.

Procesamiento de datos

Parece que todo en nuestra solicitud se ha solucionado; todo lo que queda es Limit.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Pero aquí todo es simple: nodos como Limit, Sort, Aggregate, WindowAgg, Unique "Asignar" uno a uno a los operadores correspondientes en la solicitud, si están allí. Aquí no hay “estrellas” ni dificultades.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

SUSCRÍBETE

Las dificultades surgen cuando queremos combinar JOIN entre ellos mismos. Esto no siempre es posible, pero es posible.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Desde el punto de vista del analizador de consultas, tenemos un nodo JoinExpr, que tiene exactamente dos hijos: izquierda y derecha. En consecuencia, esto es lo que está “encima” de su JOIN y lo que está escrito “debajo” en la solicitud.

Y desde el punto de vista del plan, estos son dos descendientes de algunos * Loop/* Join-nodo. Nested Loop, Hash Anti Join,... - algo como eso.

Usemos una lógica simple: si tenemos las tablas A y B que se “unen” en el plan, entonces en la solicitud podrían ubicarse ya sea A-JOIN-BO B-JOIN-A. Intentemos combinar de esta manera, intentemos combinar al revés, y así sucesivamente hasta que nos quedemos sin esos pares.

Tomemos nuestro árbol de sintaxis, tomemos nuestro plan, mirémoslos... ¡no son similares!
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Volvamos a dibujarlo en forma de gráficos. ¡Oh, ya parece algo!
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Observemos que tenemos nodos que tienen hijos B y C simultáneamente; no nos importa en qué orden. Combinémoslos y volteemos la imagen del nodo.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Miremos de nuevo. Ahora tenemos nodos con hijos A y pares (B + C), compatibles con ellos también.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

¡Excelente! Resulta que somos estos dos. JOIN de la solicitud con los nodos del plan se combinaron con éxito.

Lamentablemente, este problema no siempre se resuelve.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Por ejemplo, si en una solicitud A JOIN B JOIN C, y en el plan, en primer lugar, se conectaron los nodos "externos" A y C. Pero no existe tal operador en la solicitud, no tenemos nada que resaltar, nada a lo que adjuntar una pista. Lo mismo ocurre con la "coma" cuando escribes. A, B.

Pero, en la mayoría de los casos, casi todos los nodos se pueden "desatar" y puedes obtener este tipo de perfiles a la izquierda a tiempo, literalmente, como en Google Chrome cuando analizas el código JavaScript. Puede ver cuánto tiempo tardó en "ejecutarse" cada línea y cada declaración.
PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Y para que le resulte más cómodo utilizar todo esto, hemos hecho que el almacenamiento el archivo, donde puedes guardar y luego encontrar tus planes junto con las solicitudes asociadas o compartir el enlace con alguien.

Si sólo necesita llevar una consulta ilegible a un formato adecuado, utilice nuestro “normalizador”.

PostgreSQL Query Profiler: cómo hacer coincidir el plan y la consulta

Fuente: habr.com

Añadir un comentario