PostgreSQL Query Profiler: como combinar plano e consulta

Muitos que já estão usando explica.tensor.ru - nosso serviço de visualização de plano PostgreSQL pode não estar ciente de um de seus superpoderes - transformar uma parte difícil de ler do log do servidor...

PostgreSQL Query Profiler: como combinar plano e consulta
... em uma consulta lindamente projetada com dicas contextuais para os nós do plano correspondentes:

PostgreSQL Query Profiler: como combinar plano e consulta
Nesta transcrição da segunda parte de seu relatório na PGConf.Russia 2020 Vou te contar como conseguimos fazer isso.

A transcrição da primeira parte, dedicada a problemas típicos de desempenho de consultas e suas soluções, pode ser encontrada no artigo "Receitas para consultas SQL problemáticas".



Primeiro vamos começar a colorir - e não vamos mais colorir o plano, já pintamos, já temos lindo e compreensível, mas um pedido.

Pareceu-nos que com uma “folha” tão não formatada a solicitação retirada do log parece muito feia e, portanto, inconveniente.
PostgreSQL Query Profiler: como combinar plano e consulta

Especialmente quando os desenvolvedores “colam” o corpo da solicitação no código (isso é, claro, um antipadrão, mas acontece) em uma linha. Horrível!

Vamos desenhar isso de uma forma mais bonita.
PostgreSQL Query Profiler: como combinar plano e consulta

E se pudermos desenhar isso lindamente, ou seja, desmontar e remontar o corpo da solicitação, então poderemos “anexar” uma dica a cada objeto dessa solicitação - o que aconteceu no ponto correspondente do plano.

Árvore de sintaxe de consulta

Para fazer isso, a solicitação deve primeiro ser analisada.
PostgreSQL Query Profiler: como combinar plano e consulta

Porque nós temos o núcleo do sistema é executado em NodeJS, então fizemos um módulo para ele, você pode encontre-o no GitHub. Na verdade, essas são “ligações” estendidas aos componentes internos do próprio analisador PostgreSQL. Ou seja, a gramática é simplesmente compilada binariamente e ligações são feitas a ela a partir do NodeJS. Tomamos como base os módulos de outras pessoas - não há grande segredo aqui.

Alimentamos o corpo da solicitação como entrada para nossa função - na saída obtemos uma árvore de sintaxe analisada na forma de um objeto JSON.
PostgreSQL Query Profiler: como combinar plano e consulta

Agora podemos percorrer esta árvore na direção oposta e montar uma solicitação com os recuos, cores e formatação que desejamos. Não, isso não é personalizável, mas pareceu-nos que seria conveniente.
PostgreSQL Query Profiler: como combinar plano e consulta

Mapeando nós de consulta e plano

Agora vamos ver como podemos combinar o plano que analisamos na primeira etapa e a consulta que analisamos na segunda.

Vejamos um exemplo simples: temos uma consulta que gera um CTE e o lê duas vezes. Ele gera tal plano.
PostgreSQL Query Profiler: como combinar plano e consulta

CTE

Se você olhar com atenção, até a versão 12 (ou começando com a palavra-chave MATERIALIZED) formação CTE é uma barreira absoluta para o planejador.
PostgreSQL Query Profiler: como combinar plano e consulta

Isso significa que se virmos uma geração de CTE em algum lugar da solicitação e um nó em algum lugar do plano CTE, então esses nós definitivamente “lutam” entre si, podemos combiná-los imediatamente.

Problema com um asterisco: CTEs podem ser aninhados.
PostgreSQL Query Profiler: como combinar plano e consulta
Existem alguns muito mal aninhados e até aqueles com o mesmo nome. Por exemplo, você pode dentro CTE A fazer CTE X, e no mesmo nível dentro CTE B faça isso novamente CTE X:

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

Ao comparar, você deve entender isso. Entender isso “com os olhos” – até ver o plano, até ver o corpo do pedido – é muito difícil. Se a sua geração de CTE for complexa, aninhada e houver grandes solicitações, ela será completamente inconsciente.

UNIÃO

Se tivermos uma palavra-chave na consulta UNION [ALL] (operador de união de duas amostras), então no plano corresponde a um nó Append, ou alguns Recursive Union.
PostgreSQL Query Profiler: como combinar plano e consulta

Aquilo que está "acima" acima UNION - este é o primeiro descendente do nosso nó, que está “abaixo” - o segundo. Se através UNION temos vários blocos “colados” de uma vez, então Append- ainda haverá apenas um nó, mas não terá dois, mas muitos filhos - na ordem em que vão, respectivamente:

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

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

Problema com um asterisco: dentro da geração de amostragem recursiva (WITH RECURSIVE) também pode ser mais de um UNION. Mas apenas o último bloco após o último é sempre recursivo UNION. Tudo acima é um, mas diferente UNION:

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

Você também precisa ser capaz de “destacar” esses exemplos. Neste exemplo vemos que UNION-havia 3 segmentos em nosso pedido. Assim, um UNION соответствует Append-nó, e para o outro - Recursive Union.
PostgreSQL Query Profiler: como combinar plano e consulta

Dados de leitura e gravação

Está tudo definido, agora sabemos qual parte do pedido corresponde a qual parte do plano. E nestas peças podemos encontrar de forma fácil e natural aqueles objetos que são “legíveis”.

Do ponto de vista da consulta, não sabemos se é uma tabela ou um CTE, mas são designados pelo mesmo nó RangeVar. E em termos de “legibilidade”, este também é um conjunto bastante limitado de nós:

  • 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]

Conhecemos a estrutura do plano e da consulta, conhecemos a correspondência dos blocos, conhecemos os nomes dos objetos - fazemos uma comparação um a um.
PostgreSQL Query Profiler: como combinar plano e consulta

Novamente tarefa "com um asterisco". Pegamos a solicitação, executamos, não temos nenhum aliases - apenas lemos duas vezes no mesmo CTE.
PostgreSQL Query Profiler: como combinar plano e consulta

Analisamos o plano - qual é o problema? Por que tínhamos um pseudônimo? Nós não pedimos. Onde ele consegue esse “número numérico”?

O PostgreSQL o adiciona sozinho. Você só precisa entender isso apenas um apelido assim para nós, para efeito de comparação com o plano, não faz sentido, é simplesmente acrescentado aqui. Não vamos prestar atenção nele.

O segundo tarefa "com um asterisco": se estivermos lendo de uma tabela particionada, obteremos um nó Append ou Merge Append, que consistirá em um grande número de “filhos”, e cada um dos quais será de alguma forma Scan'om da seção da tabela: Seq Scan, Bitmap Heap Scan ou Index Scan. Mas, em qualquer caso, esses “filhos” não serão consultas complexas - é assim que esses nós podem ser distinguidos Append em UNION.
PostgreSQL Query Profiler: como combinar plano e consulta

Também entendemos esses nós, reunimos-os “em uma pilha” e dizemos: “tudo que você lê no megatable está aqui e embaixo da árvore".

Nós de recepção de dados "simples"

PostgreSQL Query Profiler: como combinar plano e consulta

Values Scan corresponde no plano VALUES no pedido.

Result é um pedido sem FROM como SELECT 1. Ou quando você tem uma expressão deliberadamente falsa em WHERE-block (então o atributo aparece 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 “mapear” para os SRFs de mesmo nome.

Mas com consultas aninhadas tudo é mais complicado - infelizmente, elas nem sempre se transformam em InitPlan/SubPlan. Às vezes eles se transformam em ... Join ou ... Anti Join, especialmente quando você escreve algo como WHERE NOT EXISTS .... E aqui nem sempre é possível combiná-los - no texto do plano não existem operadores correspondentes aos nós do plano.

Novamente tarefa "com um asterisco": alguns VALUES no pedido. Neste caso e no plano você obterá vários nós Values Scan.
PostgreSQL Query Profiler: como combinar plano e consulta

Os sufixos “numerados” ajudarão a distingui-los uns dos outros - eles são adicionados exatamente na ordem em que os correspondentes são encontrados VALUES-blocos ao longo da solicitação de cima para baixo.

Processamento de dados

Parece que tudo em nosso pedido foi resolvido - tudo o que resta é Limit.
PostgreSQL Query Profiler: como combinar plano e consulta

Mas aqui tudo é simples - nós como Limit, Sort, Aggregate, WindowAgg, Unique “mapear” um a um para os operadores correspondentes na solicitação, se eles estiverem lá. Não há “estrelas” ou dificuldades aqui.
PostgreSQL Query Profiler: como combinar plano e consulta

Cadastre-se

As dificuldades surgem quando queremos combinar JOIN entre eles mesmos. Isso nem sempre é possível, mas é possível.
PostgreSQL Query Profiler: como combinar plano e consulta

Do ponto de vista do analisador de consulta, temos um nó JoinExpr, que tem exatamente dois filhos - esquerdo e direito. Isso, portanto, é o que está “acima” do seu JOIN e o que está escrito “abaixo” dele na solicitação.

E do ponto de vista do plano, estes são dois descendentes de alguns * Loop/* Join-nó. Nested Loop, Hash Anti Join,... - algo parecido.

Vamos usar uma lógica simples: se tivermos as tabelas A e B que se “juntam” no plano, então na solicitação elas poderiam ser localizadas A-JOIN-BOu B-JOIN-A. Vamos tentar combinar assim, vamos tentar combinar ao contrário e assim por diante até ficarmos sem esses pares.

Vamos pegar nossa árvore de sintaxe, pegar nosso plano, dar uma olhada neles... não é parecido!
PostgreSQL Query Profiler: como combinar plano e consulta

Vamos redesenhar em forma de gráficos - ah, já parece alguma coisa!
PostgreSQL Query Profiler: como combinar plano e consulta

Observemos que temos nós que possuem simultaneamente filhos B e C - não nos importamos em que ordem. Vamos combiná-los e virar a imagem do nó.
PostgreSQL Query Profiler: como combinar plano e consulta

Vamos olhar novamente. Agora temos nós com filhos A e pares (B + C) - compatíveis com eles também.
PostgreSQL Query Profiler: como combinar plano e consulta

Ótimo! Acontece que somos esses dois JOIN da solicitação com os nós do plano foram combinados com sucesso.

Infelizmente, esse problema nem sempre é resolvido.
PostgreSQL Query Profiler: como combinar plano e consulta

Por exemplo, se em uma solicitação A JOIN B JOIN C, e no plano, em primeiro lugar, foram conectados os nós “externos” A e C. Mas não existe tal operador na solicitação, não temos nada a destacar, nada a que anexar uma dica. É o mesmo com a "vírgula" quando você escreve A, B.

Mas, na maioria dos casos, quase todos os nós podem ser “desamarrados” e você pode obter esse tipo de perfil à esquerda a tempo - literalmente, como no Google Chrome quando você analisa o código JavaScript. Você pode ver quanto tempo cada linha e cada instrução levou para “executar”.
PostgreSQL Query Profiler: como combinar plano e consulta

E para tornar mais conveniente o uso de tudo isso, criamos armazenamento o arquivo, onde você pode salvar e depois encontrar seus planos junto com as solicitações associadas ou compartilhar o link com alguém.

Se você só precisa trazer uma consulta ilegível para um formato adequado, use nosso “normalizador”.

PostgreSQL Query Profiler: como combinar plano e consulta

Fonte: habr.com

Adicionar um comentário