Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

O relatório apresenta algumas abordagens que permitem monitorar o desempenho de consultas SQL quando há milhões delas por dia, e existem centenas de servidores PostgreSQL monitorados.

Que soluções técnicas nos permitem processar eficientemente tal volume de informações e como isso facilita a vida de um desenvolvedor comum?


Quem está interessado? análise de problemas específicos e diversas técnicas de otimização Consultas SQL e solução de problemas típicos de DBA no PostgreSQL - você também pode leia uma série de artigos sobre este assunto.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)
Meu nome é Kirill Borovikov, represento Empresa tensora. Especificamente, sou especialista em trabalhar com bancos de dados em nossa empresa.

Hoje vou contar como otimizamos as consultas, quando você não precisa “separar” o desempenho de uma única consulta, mas sim resolver o problema em massa. Quando há milhões de solicitações e você precisa encontrar algumas abordagens para solução esse grande problema.

Em geral, o Tensor para um milhão de nossos clientes é VLSI é a nossa aplicação: rede social corporativa, soluções para videocomunicação, para fluxo de documentos internos e externos, sistemas contábeis para contabilidade e almoxarifados,... Ou seja, uma tal “mega-combina” para gestão integrada de negócios, na qual existem mais de 100 diferentes projetos internos.

Para garantir que todos funcionem e se desenvolvam normalmente, contamos com 10 centros de desenvolvimento em todo o país, sendo que há mais neles 1000 desenvolvedores.

Trabalhamos com PostgreSQL desde 2008 e acumulamos grande parte do que processamos - dados de clientes, estatísticos, analíticos, dados de sistemas de informação externos - mais de 400 TB. Existem cerca de 250 servidores somente em produção e, no total, são cerca de 1000 servidores de banco de dados que monitoramos.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

SQL é uma linguagem declarativa. Você descreve não “como” algo deveria funcionar, mas “o que” você deseja alcançar. O SGBD sabe melhor como fazer um JOIN - como conectar suas tabelas, quais condições impor, o que vai passar pelo índice, o que não vai...

Alguns SGBDs aceitam dicas: “Não, conecte essas duas tabelas em tal e tal fila”, mas o PostgreSQL não pode fazer isso. Esta é a posição consciente dos principais desenvolvedores: “Preferimos terminar o otimizador de consultas do que permitir que os desenvolvedores usem algum tipo de dica”.

Mas, apesar de o PostgreSQL não permitir que o “externo” se controle, ele permite perfeitamente veja o que está acontecendo dentro delequando você executa sua consulta e onde ela está apresentando problemas.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Em geral, quais problemas clássicos um desenvolvedor [para um DBA] costuma enfrentar? “Aqui atendemos ao pedido, e tudo é lento conosco, está tudo pendurado, alguma coisa está acontecendo... Algum tipo de problema!”

Os motivos são quase sempre os mesmos:

  • algoritmo de consulta ineficiente
    Desenvolvedor: “Agora estou dando a ele 10 tabelas em SQL via JOIN...” - e espera que suas condições milagrosamente sejam efetivamente “desamarradas” e ele consiga tudo rapidamente. Mas milagres não acontecem, e qualquer sistema com tanta variabilidade (10 tabelas em um FROM) sempre dá algum tipo de erro. [artigo]
  • estatísticas irrelevantes
    Este ponto é muito relevante especificamente para o PostgreSQL, quando você “despeja” um grande conjunto de dados no servidor, faz uma solicitação e ele “sexcaniza” seu tablet. Porque ontem havia 10 registros nele e hoje são 10 milhões, mas o PostgreSQL ainda não está ciente disso e precisamos informá-lo sobre isso. [artigo]
  • "conectar" recursos
    Você instalou um banco de dados grande e muito carregado em um servidor fraco que não possui disco, memória ou desempenho de processador suficientes. E isso é tudo... Em algum lugar existe um limite de desempenho acima do qual você não pode mais pular.
  • bloqueio
    Este é um ponto difícil, mas eles são mais relevantes para várias consultas de modificação (INSERT, UPDATE, DELETE) - este é um grande tópico separado.

Obtendo um plano

...E para todo o resto nós preciso de um plano! Precisamos ver o que está acontecendo dentro do servidor.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Um plano de execução de consulta para PostgreSQL é uma árvore do algoritmo de execução de consulta em representação de texto. É precisamente o algoritmo que, como resultado da análise do planejador, foi considerado o mais eficaz.

Cada nó da árvore é uma operação: recuperar dados de uma tabela ou índice, construir um bitmap, unir duas tabelas, unir, cruzar ou excluir seleções. A execução de uma consulta envolve percorrer os nós desta árvore.

Para obter o plano de consulta, a maneira mais fácil é executar a instrução EXPLAIN. Para obter todos os atributos reais, ou seja, para realmente executar uma consulta na base - EXPLAIN (ANALYZE, BUFFERS) SELECT ....

A parte ruim: quando você executa, isso acontece "aqui e agora", então só é adequado para depuração local. Se você pegar um servidor altamente carregado que está sob um forte fluxo de alterações de dados, e você vir: “Oh! Aqui temos uma execução lentacampismo solicitar." Meia hora, uma hora atrás - enquanto você estava executando e obtendo essa solicitação dos logs, trazendo-a de volta ao servidor, todo o seu conjunto de dados e estatísticas foram alterados. Você o executa para depurar - e ele é executado rapidamente! E você não consegue entender por que, por que foi lentamente.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Para entender o que aconteceu exatamente no momento em que a solicitação foi executada no servidor, pessoas inteligentes escreveram módulo auto_explain. Está presente em quase todas as distribuições mais comuns do PostgreSQL e pode simplesmente ser ativado no arquivo de configuração.

Se ele perceber que alguma solicitação está sendo executada por mais tempo do que o limite que você informou, ele não “instantâneo” do plano desta solicitação e os escreve juntos no log.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Parece que está tudo bem agora, a gente vai no tronco e vê lá... [texto no rodapé]. Mas não podemos dizer nada sobre isso, a não ser que é um plano excelente porque demorou 11ms para ser executado.

Tudo parece estar bem – mas nada está claro sobre o que realmente aconteceu. Além do horário geral, não vemos realmente nada. Porque olhar para esse “cordeiro” de texto simples geralmente não é visual.

Mas mesmo que não seja óbvio, mesmo que seja inconveniente, existem problemas mais fundamentais:

  • O nó indica soma dos recursos de toda a subárvore abaixo dele. Ou seja, você não pode simplesmente descobrir quanto tempo foi gasto nesta varredura de índice específica se houver alguma condição aninhada nela. Devemos olhar dinamicamente para ver se existem “filhos” e variáveis ​​condicionais, CTEs dentro – e subtrair tudo isso “em nossas mentes”.
  • Segundo ponto: o tempo que está indicado no nó é tempo de execução de nó único. Se este nó foi executado como resultado, por exemplo, de um loop através dos registros da tabela várias vezes, então o número de loops – ciclos deste nó – aumenta no plano. Mas o próprio tempo de execução atômica permanece o mesmo em termos de plano. Ou seja, para entender quanto tempo esse nó foi executado no total, você precisa multiplicar uma coisa por outra - novamente, “na sua cabeça”.

Em tais situações, entenda “Quem é o elo mais fraco?” quase impossível. Portanto, até os próprios desenvolvedores escrevem no “manual” que “Compreender um plano é uma arte que deve ser aprendida, experimentada...”.

Mas temos 1000 desenvolvedores e você não pode transmitir essa experiência a cada um deles. Eu, você, ele sabe, mas alguém aí não sabe mais. Talvez ele aprenda, ou talvez não, mas precisa trabalhar agora - e onde ele conseguiria essa experiência?

Visualização do plano

Portanto, percebemos que, para lidar com esses problemas, precisamos boa visualização do plano. [artigo]

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Primeiro fomos “pelo mercado” - vamos procurar na Internet para ver o que existe.

Mas descobriu-se que existem muito poucas soluções relativamente “vivas” que estão mais ou menos em desenvolvimento - literalmente, apenas uma: explicação.depesz.com por Hubert Lubaczewski. Ao inserir no campo “feed” uma representação de texto do plano, ele mostra uma tabela com os dados analisados:

  • tempo de processamento do próprio nó
  • tempo total para toda a subárvore
  • número de registros recuperados que eram estatisticamente esperados
  • o próprio corpo do nó

Este serviço também tem a capacidade de compartilhar um arquivo de links. Você lançou seu plano lá e disse: “Ei, Vasya, aqui está um link, há algo errado aí”.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Mas também existem pequenos problemas.

Em primeiro lugar, uma enorme quantidade de “copiar e colar”. Você pega um pedaço do tronco, enfia ali, e de novo, e de novo.

R'Rѕ-RІS, RѕSЂS <C ... nenhuma análise da quantidade de dados lidos - os mesmos buffers que geram EXPLAIN (ANALYZE, BUFFERS), não vemos isso aqui. Ele simplesmente não sabe como desmontá-los, compreendê-los e trabalhar com eles. Quando você está lendo muitos dados e percebe que pode estar alocando incorretamente o disco e o cache de memória, essa informação é muito importante.

O terceiro ponto negativo é o desenvolvimento muito fraco deste projeto. Os commits são muito pequenos, é bom que sejam uma vez a cada seis meses e o código esteja em Perl.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Mas isso tudo são “letras”, poderíamos de alguma forma conviver com isso, mas tem uma coisa que nos afastou muito desse serviço. Esses são erros na análise de Common Table Expression (CTE) e vários nós dinâmicos como InitPlan/SubPlan.

Se você acredita nesta imagem, então o tempo total de execução de cada nó individual é maior que o tempo total de execução de toda a solicitação. É simples - o tempo de geração deste CTE não foi subtraído do nó Scan do CTE. Portanto, não sabemos mais a resposta correta sobre quanto tempo demorou a varredura CTE.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Então percebemos que era hora de escrever o nosso - viva! Todo desenvolvedor diz: “Agora vamos escrever o nosso, será super fácil!”

Pegamos uma pilha típica de serviços web: um núcleo baseado em Node.js + Express, usamos Bootstrap e D3.js para belos diagramas. E nossas expectativas foram plenamente justificadas - recebemos o primeiro protótipo em 2 semanas:

  • analisador de plano personalizado
    Ou seja, agora podemos analisar qualquer plano daqueles gerados pelo PostgreSQL.
  • análise correta de nós dinâmicos - Varredura CTE, InitPlan, SubPlan
  • análise da distribuição de buffers - onde as páginas de dados são lidas da memória, onde do cache local, onde do disco
  • tenho clareza
    Para não “cavar” tudo isso no log, mas para ver logo o “elo mais fraco” na imagem.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Conseguimos algo assim, com destaque de sintaxe incluído. Mas normalmente nossos desenvolvedores não trabalham mais com uma representação completa do plano, mas sim com uma representação mais curta. Afinal, já analisamos todos os números e os jogamos para a esquerda e para a direita, e no meio deixamos apenas a primeira linha, que tipo de nó é: CTE Scan, geração CTE ou Seq Scan de acordo com algum sinal.

Esta é a representação abreviada que chamamos modelo de plano.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

O que mais seria conveniente? Seria conveniente ver qual parte do nosso tempo total está alocada para qual nó - e apenas “colocá-lo” de lado gráfico de pizza.

Apontamos para o nó e vemos - acontece que o Seq Scan levou menos de um quarto do tempo total e os 3/4 restantes foram levados pelo CTE Scan. Horror! Esta é uma pequena observação sobre a “taxa de disparo” do CTE Scan se você os usar ativamente em suas consultas. Eles não são muito rápidos - são inferiores até mesmo à digitalização normal de tabelas. [artigo] [artigo]

Mas geralmente tais diagramas são mais interessantes, mais complexos, quando apontamos imediatamente para um segmento e vemos, por exemplo, que mais da metade das vezes algum Seq Scan “comeu”. Além disso, havia uma espécie de Filtro dentro, muitos registros foram descartados de acordo com ele... Você pode jogar essa foto diretamente para o desenvolvedor e dizer: “Vasya, está tudo ruim aqui para você! Descubra, olhe - algo está errado!

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Naturalmente, houve alguns “ancinhos” envolvidos.

A primeira coisa que encontramos foi o problema de arredondamento. O tempo de cada nó individual no plano é indicado com precisão de 1 μs. E quando o número de ciclos de nós excede, por exemplo, 1000 - após a execução do PostgreSQL dividido “dentro da precisão”, então, ao calcular novamente, obtemos o tempo total “algo entre 0.95 ms e 1.05 ms”. Quando a contagem vai para microssegundos, tudo bem, mas quando já chega a [mili]segundos, é preciso levar essa informação em consideração na hora de “desvincular” recursos aos nós do plano “quem consumiu quanto”.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

O segundo ponto, mais complexo, é a distribuição de recursos (aqueles buffers) entre nós dinâmicos. Isso nos custou as primeiras 2 semanas do protótipo e mais 4 semanas.

É muito fácil ter esse tipo de problema - fazemos um CTE e supostamente lemos algo nele. Na verdade, o PostgreSQL é “inteligente” e não lê nada diretamente ali. Então pegamos o primeiro registro dele e para ele o centésimo primeiro do mesmo CTE.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Observamos o plano e entendemos - é estranho, temos 3 buffers (páginas de dados) “consumidos” no Seq Scan, mais 1 no CTE Scan e mais 2 no segundo CTE Scan. Ou seja, se simplesmente somarmos tudo, obteremos 6, mas no tablet lemos apenas 3! O CTE Scan não lê nada de lugar nenhum, mas trabalha diretamente com a memória do processo. Ou seja, algo está claramente errado aqui!

Na verdade, acontece que aqui estão todas aquelas 3 páginas de dados que foram solicitadas ao Seq Scan, primeiro 1 pediu o 1º CTE Scan, depois a 2ª, e mais 2 foram lidas para ele. Ou seja, um total de 3 páginas foram lidas de dados, não 6.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

E esse quadro nos levou ao entendimento de que a execução de um plano não é mais uma árvore, mas simplesmente uma espécie de gráfico acíclico. E obtivemos um diagrama como este, para que possamos entender “o que veio de onde, em primeiro lugar”. Ou seja, aqui criamos um CTE a partir de pg_class, e solicitamos duas vezes, e quase todo o nosso tempo foi gasto na filial quando solicitamos pela 2ª vez. É claro que ler a 101ª entrada é muito mais caro do que apenas ler a 1ª entrada no tablet.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Nós exalamos por um tempo. Eles disseram: “Agora, Neo, você conhece o kung fu! Agora nossa experiência está na sua tela. Agora você pode usá-lo." [artigo]

Consolidação de registros

Nossos 1000 desenvolvedores respiraram aliviados. Mas entendemos que temos apenas centenas de servidores de “combate”, e todo esse “copiar e colar” por parte dos desenvolvedores não é nada conveniente. Percebemos que tínhamos que coletá-lo nós mesmos.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Em geral, existe um módulo padrão que pode coletar estatísticas, porém, ele também precisa ser ativado na configuração - este Módulo pg_stat_statements. Mas ele não nos agradou.

Em primeiro lugar, atribui às mesmas consultas usando esquemas diferentes dentro do mesmo banco de dados QueryIds diferentes. Isto é, se você primeiro fizer SET search_path = '01'; SELECT * FROM user LIMIT 1;e depois SET search_path = '02'; e o mesmo pedido, então as estatísticas deste módulo terão registos diferentes, e não poderei recolher estatísticas gerais especificamente no contexto deste perfil de pedido, sem ter em conta os esquemas.

O segundo ponto que nos impediu de usá-lo é falta de planos. Ou seja, não existe um plano, existe apenas o pedido em si. Vemos o que estava desacelerando, mas não entendemos por quê. E aqui voltamos ao problema de um conjunto de dados em rápida mudança.

E o último momento - falta de "fatos". Ou seja, você não pode abordar uma instância específica de execução de consulta - não existe nenhuma, existem apenas estatísticas agregadas. Embora seja possível trabalhar com isso, é muito difícil.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Portanto, decidimos lutar contra copiar e colar e começamos a escrever colecionador.

O coletor se conecta via SSH, estabelece uma conexão segura entre o servidor e o banco de dados usando um certificado e tail -F “se apega” a ele no arquivo de log. Então nesta sessão obtemos um “espelho” completo de todo o arquivo de log, que o servidor gera. A carga no próprio servidor é mínima, pois não analisamos nada lá, apenas espelhamos o tráfego.

Como já havíamos começado a escrever a interface em Node.js, continuamos a escrever o coletor nela. E essa tecnologia se justificou, pois é muito conveniente usar JavaScript para trabalhar com dados de texto mal formatados, que é o log. E a própria infraestrutura Node.js como plataforma de back-end permite que você trabalhe de maneira fácil e conveniente com conexões de rede e, na verdade, com qualquer fluxo de dados.

Assim, “esticamos” duas conexões: a primeira para “ouvir” a própria tora e levá-la para nós, e a segunda para perguntar periodicamente à base. “Mas o log mostra que o sinal com oid 123 está bloqueado”, mas isso não significa nada para o desenvolvedor, e seria bom perguntar ao banco de dados: “Afinal, o que é OID = 123?” E assim perguntamos periodicamente à base o que ainda não sabemos sobre nós mesmos.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

“Só há uma coisa que vocês não levaram em conta, é uma espécie de abelha parecida com um elefante!..” Começamos a desenvolver este sistema quando queríamos monitorar 10 servidores. O mais crítico no nosso entendimento, onde surgiram alguns problemas difíceis de resolver. Mas durante o primeiro trimestre recebemos cem para monitoramento - porque o sistema funcionou, todo mundo queria, todo mundo estava confortável.

Tudo isso precisa ser somado, o fluxo de dados é grande e ativo. Na verdade, o que monitoramos, com o que podemos lidar, é o que usamos. Também usamos PostgreSQL como armazenamento de dados. E nada é mais rápido para “injetar” dados nele do que o operador COPY Ainda não.

Mas simplesmente “despejar” dados não é realmente a nossa tecnologia. Porque se você tiver aproximadamente 50 mil solicitações por segundo em cem servidores, isso gerará de 100 a 150 GB de logs por dia. Portanto, tivemos que “cortar” cuidadosamente a base.

Em primeiro lugar, fizemos particionamento por dia, porque, em geral, ninguém está interessado na correlação entre os dias. Que diferença faz o que você tinha ontem, se esta noite você lançou uma nova versão do aplicativo - e já algumas novas estatísticas.

Em segundo lugar, aprendemos (fomos forçados) muito, muito rápido para escrever usando COPY. Ou seja, não apenas COPYporque ele é mais rápido que INSERTe ainda mais rápido.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

O terceiro ponto - eu tive que abandonar gatilhos, respectivamente, e chaves estrangeiras. Ou seja, não temos nenhuma integridade referencial. Porque se você tem uma tabela que possui um par de FKs e diz na estrutura do banco de dados que “aqui está um registro de log que é referenciado por FK, por exemplo, para um grupo de registros”, então quando você o insere, o PostgreSQL não tem mais nada além de pegar e fazer isso honestamente SELECT 1 FROM master_fk1_table WHERE ... com o identificador que você está tentando inserir - apenas para verificar se esse registro está presente ali, se você não “quebra” essa Chave Estrangeira com sua inserção.

Em vez de um registro para a tabela de destino e seus índices, obtemos o benefício adicional de ler todas as tabelas às quais ela se refere. Mas não precisamos disso - nossa tarefa é gravar o máximo possível e o mais rápido possível com o mínimo de carga. Então FK - abaixo!

O próximo ponto é agregação e hash. Inicialmente, nós os implementamos no banco de dados - afinal, é conveniente fazer isso imediatamente, quando chega um registro, em algum tipo de tablet "mais um" bem no gatilho. Bem, é conveniente, mas é a mesma coisa ruim - você insere um registro, mas é forçado a ler e escrever outra coisa de outra tabela. Além disso, você não apenas lê e escreve, mas também o faz sempre.

Agora imagine que você tem uma tabela na qual simplesmente conta o número de solicitações que passaram por um host específico: +1, +1, +1, ..., +1. E você, em princípio, não precisa disso - tudo é possível soma na memória do coletor e enviar para o banco de dados de uma só vez +10.

Sim, no caso de alguns problemas, sua integridade lógica pode “desmoronar”, mas este é um caso quase irrealista - porque você tem um servidor normal, tem bateria no controlador, você tem um log de transações, um log no sistema de arquivos... Em geral, não vale a pena. A perda de produtividade que você obtém ao executar gatilhos/FK não compensa as despesas incorridas.

É o mesmo com hash. Uma determinada solicitação chega até você, você calcula um determinado identificador a partir dela no banco de dados, grava-o no banco de dados e depois informa a todos. Está tudo bem até que, na hora da gravação, chega até você uma segunda pessoa que quer gravar a mesma coisa - e você fica bloqueado, e isso já é ruim. Portanto, se você pode transferir a geração de alguns IDs para o cliente (relativos ao banco de dados), é melhor fazer isso.

Foi simplesmente perfeito para usarmos o MD5 do texto - solicitação, plano, modelo,... Calculamos no lado do coletor e “despejamos” o ID pronto no banco de dados. A duração do MD5 e o particionamento diário nos permitem não nos preocupar com possíveis colisões.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Mas para registrar tudo isso rapidamente, precisávamos modificar o próprio procedimento de gravação.

Como você costuma escrever dados? Temos algum tipo de conjunto de dados, dividimos em várias tabelas e depois COPIAMOS - primeiro na primeira, depois na segunda, na terceira... É inconveniente, porque parece que estamos escrevendo um fluxo de dados em três etapas sequencialmente. Desagradável. Isso pode ser feito mais rápido? Pode!

Para isso, basta decompor esses fluxos paralelamente entre si. Acontece que temos erros, solicitações, templates, bloqueios,... voando em threads separados - e escrevemos tudo em paralelo. Chega para isso manter um canal COPY constantemente aberto para cada tabela de destino individual.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Ou seja, no coletor há sempre um fluxo, no qual posso escrever os dados necessários. Mas para que o banco de dados veja esses dados e alguém não fique esperando a gravação desses dados, COPY deve ser interrompido em determinados intervalos. Para nós, o período mais eficaz foi de cerca de 100ms - fechamos e imediatamente abrimos novamente na mesma mesa. E se não tivermos um fluxo suficiente durante alguns picos, então fazemos pooling até um certo limite.

Além disso, descobrimos que para tal perfil de carga, qualquer agregação, quando os registros são coletados em lotes, é prejudicial. O mal clássico é INSERT ... VALUES e mais 1000 registros. Porque nesse ponto você terá um pico de gravação na mídia e todos os outros que tentarem gravar algo no disco estarão esperando.

Para se livrar de tais anomalias, basta não agregar nada, não bufferize nada. E se ocorrer buffer no disco (felizmente, a API Stream no Node.js permite que você descubra) - adie essa conexão. Ao receber um evento informando que ele está gratuito novamente, grave nele a partir da fila acumulada. E enquanto estiver ocupado, pegue o próximo livre do pool e escreva para ele.

Antes de introduzir essa abordagem para gravação de dados, tínhamos operações de gravação de aproximadamente 4K e, dessa forma, reduzimos a carga em 4 vezes. Agora eles cresceram mais 6 vezes devido aos novos bancos de dados monitorados - até 100 MB/s. E agora armazenamos logs dos últimos 3 meses em um volume de cerca de 10 a 15 TB, esperando que em apenas três meses qualquer desenvolvedor consiga resolver qualquer problema.

Nós entendemos os problemas

Mas simplesmente recolher todos estes dados é bom, útil, relevante, mas não é suficiente – precisa de ser compreendido. Porque são milhões de planos diferentes por dia.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Mas milhões são incontroláveis, temos primeiro de fazer “menores”. E, antes de mais nada, você precisa decidir como vai organizar essa coisa “menor”.

Identificamos três pontos principais:

  • que enviou este pedido
    Ou seja, de qual aplicativo ele “chegou”: interface web, backend, sistema de pagamento ou outra coisa.
  • onde aconteceu
    Em qual servidor específico? Porque se você tiver vários servidores em um aplicativo e, de repente, um “fica estúpido” (porque o “disco está podre”, “vazamento de memória”, algum outro problema), então você precisa abordar especificamente o servidor.
  • como o problema se manifestou de uma forma ou de outra

Para entender “quem” nos enviou uma solicitação, usamos uma ferramenta padrão - definindo uma variável de sessão: SET application_name = '{bl-host}:{bl-method}'; — enviamos o nome do host da lógica de negócios de onde vem a solicitação e o nome do método ou aplicativo que a iniciou.

Depois de passarmos o “dono” da solicitação, ela deve ser enviada para o log - para isso configuramos a variável log_line_prefix = ' %m [%p:%v] [%d] %r %a'. Para os interessados, talvez olhe no manualO que tudo isso significa. Acontece que vemos no log:

  • tempo
  • identificadores de processo e transação
  • nome do banco de dados
  • IP da pessoa que enviou esta solicitação
  • e nome do método

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Então percebemos que não é muito interessante observar a correlação de uma solicitação entre servidores diferentes. Não é sempre que você se depara com uma situação em que um aplicativo dá errado aqui e ali. Mas mesmo que seja igual, observe qualquer um desses servidores.

Então aqui está o corte "um servidor - um dia" acabou sendo suficiente para qualquer análise.

A primeira seção analítica é a mesma "amostra" - uma forma abreviada de apresentação do plano, isenta de todos os indicadores numéricos. O segundo corte é o aplicativo ou método, e o terceiro corte é o nó do plano específico que nos causou problemas.

Quando passamos de instâncias específicas para modelos, obtivemos duas vantagens ao mesmo tempo:

  • redução múltipla no número de objetos para análise
    Temos que analisar o problema não mais por milhares de consultas ou planos, mas por dezenas de modelos.
  • Linha do tempo
    Ou seja, ao resumir os “fatos” dentro de uma determinada seção, você pode exibir sua aparência durante o dia. E aqui você pode entender que se você tem algum tipo de padrão que acontece, por exemplo, uma vez por hora, mas deveria acontecer uma vez por dia, você deve pensar no que deu errado - quem causou isso e por que, talvez devesse estar aqui não deveria. Este é outro método de análise não numérico e puramente visual.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Os restantes métodos baseiam-se nos indicadores que extraímos do plano: quantas vezes tal padrão ocorreu, o tempo total e médio, quantos dados foram lidos do disco e quantos da memória...

Porque, por exemplo, você chega à página de análise do host, veja - algo está começando a ler muito no disco. O disco no servidor não consegue lidar com isso - quem lê isso?

E você pode classificar por qualquer coluna e decidir com o que vai lidar agora - a carga no processador ou no disco, ou o número total de solicitações... Nós classificamos, olhamos os “principais”, corrigimos e lançou uma nova versão do aplicativo.
[vídeo-aula]

E imediatamente você pode ver diferentes aplicativos que vêm com o mesmo modelo de uma solicitação como SELECT * FROM users WHERE login = 'Vasya'. Frontend, backend, processamento... E você se pergunta por que o processamento leria o usuário se ele não interage com ele.

A maneira oposta é ver imediatamente no aplicativo o que ele faz. Por exemplo, o frontend é isto, isto, isto e isto uma vez por hora (a linha do tempo ajuda). E surge imediatamente a questão: parece que não é função do frontend fazer algo uma vez por hora...

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Depois de algum tempo, percebemos que faltava informação agregada estatísticas por nós do plano. Isolamos dos planos apenas aqueles nós que fazem algo com os dados das próprias tabelas (lê-los/escrevê-los por índice ou não). Na verdade, apenas um aspecto é adicionado em relação à imagem anterior - quantos registros esse nó nos trouxe?e quantas foram descartadas (Linhas removidas por filtro).

Você não tem um índice adequado na placa, você faz uma solicitação, ele passa voando pelo índice, cai no Seq Scan... você filtrou todos os registros, exceto um. Por que você precisa de 100 milhões de registros filtrados por dia? Não é melhor acumular o índice?

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Depois de analisar todos os planos nó por nó, percebemos que existem algumas estruturas típicas nos planos que muito provavelmente parecerão suspeitas. E seria bom dizer ao desenvolvedor: “Amigo, aqui você primeiro lê pelo índice, depois classifica e depois corta” - via de regra, há um registro.

Todos que escreveram consultas provavelmente encontraram este padrão: “Dê-me o último pedido do Vasya, sua data.” E se você não tiver um índice por data, ou não houver data no índice que você usou, então você pise exatamente no mesmo “ancinho”.

Mas sabemos que isso é um “rake” - então por que não dizer imediatamente ao desenvolvedor o que ele deve fazer? Assim, ao abrir um plano agora, nosso desenvolvedor vê imediatamente uma bela foto com dicas, onde imediatamente lhe dizem: “Você tem problemas aqui e ali, mas eles são resolvidos de um jeito ou de outro”.

Como resultado, a quantidade de experiência necessária para resolver problemas no início e agora caiu significativamente. Este é o tipo de ferramenta que temos.

Otimização em massa de consultas PostgreSQL. Kirill Borovikov (Tensor)

Fonte: habr.com

Adicionar um comentário