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

O informe presenta algúns enfoques que permiten supervisar o rendemento das consultas SQL cando hai millóns delas ao día, e hai centos de servidores PostgreSQL monitorizados.

Que solucións técnicas nos permiten procesar de forma eficiente tal volume de información e como facilita isto a vida dun programador común?


A quen lle interesa? análise de problemas específicos e diversas técnicas de optimización Consultas SQL e resolución de problemas típicos de DBA en PostgreSQL, tamén podes ler unha serie de artigos sobre este tema.

Optimización masiva de consultas PostgreSQL. Kirill Borovikov (tensor)
Chámome Kirill Borovikov, represento Compañía tensor. En concreto, estou especializado en traballar con bases de datos da nosa empresa.

Hoxe vouvos contar como optimizamos as consultas, cando non é necesario "descomprimir" o rendemento dunha soa consulta, senón resolver o problema en masa. Cando hai millóns de solicitudes, e necesitas atopar algunhas enfoques de solución este gran problema.

En xeral, Tensor para un millón dos nosos clientes é VLSI é a nosa aplicación: rede social corporativa, solucións para comunicación por vídeo, para o fluxo de documentos internos e externos, sistemas de contabilidade para contabilidade e almacéns,... É dicir, unha “mega-combinación” para a xestión empresarial integrada, na que hai máis de 100 diferentes proxectos internos.

Para garantir que todos funcionen e se desenvolvan con normalidade, contamos con 10 centros de desenvolvemento por todo o país, con máis neles 1000 desenvolvedores.

Levamos traballando con PostgreSQL desde 2008 e acumulamos unha gran cantidade do que procesamos: datos de clientes, estatísticos, analíticos, datos de sistemas de información externos. máis de 400 TB. Só en produción hai uns 250 servidores e, en total, hai uns 1000 servidores de bases de datos que supervisamos.

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

SQL é unha linguaxe declarativa. Non describes "como" debería funcionar algo, senón "que" queres conseguir. O DBMS sabe mellor como facer un JOIN: como conectar as túas táboas, que condicións impoñer, que pasará polo índice, que non...

Algúns DBMS aceptan suxestións: "Non, conecte estas dúas táboas en tal ou cal cola", pero PostgreSQL non pode facelo. Esta é a posición consciente dos principais desenvolvedores: "Preferimos rematar o optimizador de consultas que permitir que os desenvolvedores utilicen algún tipo de suxestións".

Pero, a pesar de que PostgreSQL non permite que o "exterior" se controle, permíteo perfectamente ver o que pasa dentro delcando executa a súa consulta e onde está a ter problemas.

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

En xeral, que problemas clásicos adoita ter un programador [a un DBA]? “Aquí cumprimos a petición, e todo vai lento connosco, todo está colgando, algo está pasando... Algún tipo de problema!”

As razóns son case sempre as mesmas:

  • algoritmo de consulta ineficiente
    Desenvolvedor: "Agora estou a darlle 10 táboas en SQL a través de JOIN..." - e espera que as súas condicións sexan "desatadas" de forma milagrosa e que consiga todo rapidamente. Pero os milagres non ocorren, e calquera sistema con tal variabilidade (10 táboas nun FROM) sempre dá algún tipo de erro. [artigo]
  • estatísticas obsoletas
    Este punto é moi relevante específicamente para PostgreSQL, cando "verteu" un gran conxunto de datos no servidor, realice unha solicitude e "sexcanit" a súa tableta. Porque onte había 10 rexistros nel, e hoxe hai 10 millóns, pero PostgreSQL aínda non é consciente diso e hai que contalo. [artigo]
  • "conectar" aos recursos
    Instalou unha base de datos grande e moi cargada nun servidor débil que non ten o rendemento suficiente de disco, memoria ou procesador. E iso é todo... Nalgún lugar hai un teito de rendemento por riba do que xa non podes saltar.
  • bloqueo
    Este é un punto difícil, pero son máis relevantes para varias consultas de modificación (INSERT, UPDATE, DELETE) - este é un gran tema separado.

Obtendo un plan

...E para todo o demais nós precisa un plan! Necesitamos ver o que está a suceder dentro do servidor.

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

Un plan de execución de consultas para PostgreSQL é unha árbore do algoritmo de execución de consultas na representación de texto. É precisamente o algoritmo que, como resultado da análise do planificador, resultou ser o máis eficaz.

Cada nodo da árbore é unha operación: recuperar datos dunha táboa ou índice, construír un mapa de bits, unir dúas táboas, unir, cruzar ou excluír seleccións. Executar unha consulta implica percorrer os nodos desta árbore.

Para obter o plan de consulta, o xeito máis sinxelo é executar a instrución EXPLAIN. Para obter todos os atributos reais, é dicir, executar realmente unha consulta na base - EXPLAIN (ANALYZE, BUFFERS) SELECT ....

A parte mala: cando o executas, ocorre "aquí e agora", polo que só é adecuado para a depuración local. Se tomas un servidor moi cargado que está baixo un forte fluxo de cambios de datos, e ves: "Oh! Aquí temos unha execución lentasya solicitude". Hai media hora, unha hora, mentres estabas executando e recibindo esta solicitude dos rexistros, devolvéndoa ao servidor, todo o teu conxunto de datos e estatísticas cambiaron. Execútao para depurar e execútase rapidamente! E non podes entender por que, por que foi lentamente.

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

Para comprender o que pasou exactamente no momento en que se executou a solicitude no servidor, escribiu xente intelixente módulo auto_explain. Está presente en case todas as distribucións PostgreSQL máis comúns e simplemente pódese activar no ficheiro de configuración.

Se se dá conta de que algunha solicitude está a executarse máis tempo que o límite que lle indicaches, faino "instantánea" do plano desta solicitude e escríbeas xuntos no rexistro.

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

Agora parece que todo vai ben, imos ao rexistro e vemos alí... [texto pano]. Pero non podemos dicir nada respecto diso, salvo o feito de que é un excelente plan porque levou 11 ms en executarse.

Todo parece estar ben, pero nada está claro que pasou realmente. Ademais do tempo xeral, realmente non vemos nada. Porque mirar un "cordeiro" deste tipo de texto sinxelo xeralmente non é visual.

Pero aínda que non sexa obvio, aínda que sexa inconveniente, hai problemas máis fundamentais:

  • O nodo indica suma de recursos de toda a subárbore baixo el. É dicir, non podes descubrir canto tempo pasou nesta exploración de índices en particular se hai algunha condición aniñada baixo ela. Debemos mirar dinámicamente para ver se hai "fillos" e variables condicionais, CTE dentro - e restar todo isto "na nosa mente".
  • Segundo punto: o tempo que se indica no nodo é tempo de execución dun único nodo. Se este nodo se executou como resultado, por exemplo, dun bucle a través dos rexistros da táboa varias veces, entón o número de bucles (ciclos deste nodo) aumenta no plan. Pero o tempo de execución atómica en si segue sendo o mesmo en termos de plan. É dicir, para comprender canto tempo se realizou este nodo en total, cómpre multiplicar unha cousa por outra, de novo, "na túa cabeza".

En tales situacións, entende "Quen é o elo máis débil?" case imposible. Polo tanto, mesmo os propios desenvolvedores escriben no "manual" que "Entender un plan é unha arte que hai que aprender, experimentar...".

Pero temos 1000 desenvolvedores e non podes transmitir esta experiencia a cada un deles. Eu, ti, el sei, pero alguén de alí xa non o sabe. Quizais aprenda, ou quizais non, pero necesita traballar agora, e de onde conseguiría esta experiencia?

Visualización do plan

Polo tanto, decatámonos de que para facer fronte a estes problemas, necesitamos boa visualización do plan. [artigo]

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

Primeiro fomos "polo mercado": miremos en Internet para ver o que existe.

Pero resultou que hai moi poucas solucións relativamente "en vivo" que están máis ou menos en desenvolvemento, literalmente, só unha: explica.depesz.com por Hubert Lubaczewski. Cando introduces no campo "alimentación" unha representación de texto do plan, móstrache unha táboa cos datos analizados:

  • tempo de procesamento do propio nodo
  • tempo total para toda a subárbore
  • número de rexistros que se recuperaron que se esperaban estatisticamente
  • o propio corpo do nodo

Este servizo tamén ten a posibilidade de compartir un arquivo de ligazóns. Lanzaches o teu plan alí e dixeches: "Ei, Vasya, aquí tes unha ligazón, hai algo mal alí".

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

Pero tamén hai pequenos problemas.

En primeiro lugar, unha gran cantidade de "copiar e pegar". Colles un anaco do rexistro, pégalo alí, e outra vez, e outra vez.

En segundo lugar ningunha análise da cantidade de datos lidos - os mesmos buffers que saen EXPLAIN (ANALYZE, BUFFERS), non o vemos aquí. Simplemente non sabe como desmontalos, entendelos e traballar con eles. Cando estás lendo moitos datos e te das conta de que podes estar asignando mal o disco e a caché de memoria, esta información é moi importante.

O terceiro punto negativo é o moi débil desenvolvemento deste proxecto. Os commits son moi pequenos, é bo se unha vez cada seis meses, e o código está en Perl.

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

Pero todo isto son "letras", dalgunha maneira poderiamos vivir con isto, pero hai unha cousa que nos apartou moito deste servizo. Estes son erros na análise da Common Table Expression (CTE) e varios nodos dinámicos como InitPlan/SubPlan.

Se cres esta imaxe, entón o tempo de execución total de cada nodo individual é maior que o tempo de execución total de toda a solicitude. É sinxelo - o tempo de xeración deste CTE non se subtraeu do nodo CTE Scan. Polo tanto, xa non sabemos a resposta correcta ao tempo que levou a propia exploración CTE.

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

Entón decatámonos de que era hora de escribir o noso propio - ¡Xura! Todos os desenvolvedores di: "Agora escribiremos o noso, será moi sinxelo!"

Tomamos unha pila típica dos servizos web: un núcleo baseado en Node.js + Express, empregamos Bootstrap e D3.js para fermosos diagramas. E as nosas expectativas estaban totalmente xustificadas: recibimos o primeiro prototipo en 2 semanas:

  • analizador de plan personalizado
    É dicir, agora podemos analizar calquera plan dos xerados por PostgreSQL.
  • análise correcta dos nodos dinámicos - CTE Scan, InitPlan, SubPlan
  • análise da distribución de buffers - onde se len páxinas de datos da memoria, onde desde a caché local, onde dende o disco
  • obtivo claridade
    Para non "cavar" todo isto no rexistro, senón para ver a "ligazón máis débil" inmediatamente na imaxe.

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

Temos algo así, con resaltado de sintaxe incluído. Pero normalmente os nosos desenvolvedores xa non traballan cunha representación completa do plan, senón cunha máis curta. Despois de todo, xa analizamos todos os números e os tiramos á esquerda e á dereita, e no medio deixamos só a primeira liña, que tipo de nodo é: CTE Scan, CTE generation ou Seq Scan segundo algún signo.

Esta é a representación abreviada que chamamos modelo de plan.

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

Que máis sería conveniente? Sería conveniente ver que parte do noso tempo total se lle atribúe a que nodo e simplemente "pégalo" a un lado gráfico circular.

Apuntamos ao nodo e vemos: resulta que Seq Scan levou menos dunha cuarta parte do tempo total, e os 3/4 restantes foron tomados por CTE Scan. Horror! Esta é unha pequena nota sobre a "cadencia de lume" de CTE Scan se as utilizas activamente nas túas consultas. Non son moi rápidos - son inferiores incluso á dixitalización normal da táboa. [artigo] [artigo]

Pero normalmente tales diagramas son máis interesantes, máis complexos, cando apuntamos inmediatamente a un segmento e vemos, por exemplo, que máis da metade das veces algún Seq Scan "comía". Ademais, había algún tipo de Filtro dentro, descartáronse moitos rexistros segundo el... Podes lanzar directamente esta imaxe ao desenvolvedor e dicir: "Vasya, todo está mal aquí para ti! Descúbreo, mira, algo está mal!"

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

Por suposto, houbo algúns "raciños" implicados.

O primeiro que nos atopamos foi o problema do redondeo. O tempo de cada nodo individual do plano indícase cunha precisión de 1 μs. E cando o número de ciclos de nodos supera, por exemplo, 1000 - despois da execución PostgreSQL dividiu "dentro da precisión", entón ao calcular de novo obtemos o tempo total "entre 0.95 ms e 1.05 ms". Cando o reconto pasa a microsegundos, está ben, pero cando xa son [mili]segundos, hai que ter en conta esta información á hora de "desvincular" os recursos aos nodos do plan "quen consumiu canto".

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

O segundo punto, máis complexo, é a distribución dos recursos (eses buffers) entre nodos dinámicos. Isto custounos as 2 primeiras semanas do prototipo máis outras 4 semanas.

É bastante fácil conseguir este tipo de problema: facemos un CTE e supostamente lemos algo nel. De feito, PostgreSQL é "intelixente" e non lerá nada directamente alí. Despois collemos o primeiro disco, e a el o cento primeiro do mesmo CTE.

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

Miramos o plan e entendemos: é estraño, temos 3 búfers (páxinas de datos) "consumidos" en Seq Scan, 1 máis en CTE Scan e 2 máis no segundo CTE Scan. É dicir, se simplemente sumamos todo, obteremos 6, pero dende a tableta só lemos 3! CTE Scan non le nada desde ningún lugar, pero funciona directamente coa memoria do proceso. É dicir, algo está claramente mal aquí!

De feito, resulta que aquí están todas esas 3 páxinas de datos que se solicitaron a Seq Scan, primeiro 1 pediu o 1o CTE Scan, e despois leronlle a 2a, e 2 máis, é dicir, un total de Leron datos de 3 páxinas, non 6.

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

E esta imaxe levounos a entender que a execución dun plano xa non é unha árbore, senón simplemente unha especie de gráfico acíclico. E obtivemos un diagrama coma este, para que entendamos "o que veu de onde en primeiro lugar". É dicir, aquí creamos un CTE a partir de pg_class, e pedimos por dúas veces, e case todo o noso tempo pasámolo na filial cando o pedimos a 2a vez. Está claro que ler a entrada 101 é moito máis caro que só ler a entrada 1 desde a tableta.

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

Expiramos un tempo. Eles dixeron: "Agora, Neo, sabes kung fu! Agora a nosa experiencia está na túa pantalla. Agora podes usalo". [artigo]

Consolidación de rexistros

Os nosos 1000 desenvolvedores respiraron aliviados. Pero entendemos que só temos centos de servidores de "combate", e todo este "copiar-pegar" por parte dos desenvolvedores non é nada conveniente. Démonos conta de que tiñamos que recollelo nós.

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

En xeral, hai un módulo estándar que pode recoller estatísticas, pero tamén hai que activalo na configuración. módulo pg_stat_statements. Pero non nos conviña.

En primeiro lugar, asigna as mesmas consultas utilizando diferentes esquemas dentro da mesma base de datos diferentes QueryIds. É dicir, se primeiro o fas SET search_path = '01'; SELECT * FROM user LIMIT 1;e logo SET search_path = '02'; e a mesma solicitude, entón as estatísticas deste módulo terán rexistros diferentes, e non poderei recoller estatísticas xerais especificamente no contexto deste perfil de solicitude, sen ter en conta os esquemas.

O segundo punto que nos impediu usalo é falta de plans. É dicir, non hai ningún plan, só existe a propia solicitude. Vemos o que se ralentizaba, pero non entendemos por que. E aquí volvemos ao problema dun conxunto de datos que cambia rapidamente.

E o último momento - falta de "feitos". É dicir, non pode abordar unha instancia específica de execución de consulta; non hai ningunha, só hai estatísticas agregadas. Aínda que é posible traballar con isto, é moi difícil.

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

Por iso, decidimos loitar contra o copiar e pegar e comezamos a escribir coleccionista.

O colector conéctase a través de SSH, establece unha conexión segura co servidor coa base de datos mediante un certificado e tail -F "enganchase" a el no ficheiro de rexistro. Así que nesta sesión obtemos un "espello" completo de todo o ficheiro de rexistro, que xera o servidor. A carga no propio servidor é mínima, porque non analizamos nada alí, só reflectimos o tráfico.

Como xa comezamos a escribir a interface en Node.js, seguimos escribindo o colector nel. E esta tecnoloxía xustificárase, porque é moi conveniente usar JavaScript para traballar con datos de texto con formato débil, que é o rexistro. E a propia infraestrutura Node.js como plataforma de backend permítelle traballar de xeito sinxelo e cómodo con conexións de rede e, de feito, con calquera fluxo de datos.

En consecuencia, "estiramos" dúas conexións: a primeira para "escoitar" o propio rexistro e tomalo para nós mesmos, e a segunda para preguntar periodicamente á base. "Pero o rexistro mostra que o sinal con oid 123 está bloqueado", pero isto non significa nada para o desenvolvedor, e sería bo preguntarlle á base de datos: "Que é OID = 123 de todos os xeitos?" E por iso periódicamente preguntamos á base o que aínda non sabemos de nós mesmos.

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

"Só hai unha cousa que non tivo en conta, hai unha especie de abellas parecidas a elefantes!..." Comezamos a desenvolver este sistema cando queriamos supervisar 10 servidores. O máis crítico ao noso entender, onde xurdiron algúns problemas difíciles de tratar. Pero durante o primeiro trimestre, recibimos cen para o seguimento, porque o sistema funcionaba, todos queríano, todos estaban cómodos.

Todo isto hai que sumar, o fluxo de datos é grande e activo. De feito, o que monitorizamos, o que podemos tratar, é o que usamos. Tamén usamos PostgreSQL como almacenamento de datos. E nada é máis rápido para "verter" datos nel que o operador COPY Aínda non.

Pero simplemente "verter" datos non é realmente a nosa tecnoloxía. Porque se tes aproximadamente 50 solicitudes por segundo en cen servidores, isto xerará 100-150 GB de rexistros por día. Polo tanto, tivemos que "cortar" coidadosamente a base.

En primeiro lugar, fixemos partición por día, porque, en xeral, a ninguén lle interesa a correlación entre días. Que diferenza ten o que tiñas onte, se esta noite lanzaches unha nova versión da aplicación, e xa algunhas estatísticas novas.

En segundo lugar, aprendemos (fomos forzados) moi, moi rápido de escribir usando COPY. É dicir, non só COPYporque é máis rápido que INSERT, e aínda máis rápido.

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

O terceiro punto - tiven que facelo abandonar os disparadores, respectivamente, e as claves externas. É dicir, non temos integridade referencial en absoluto. Porque se tes unha táboa que ten un par de FK e dis na estrutura da base de datos que "aquí hai un rexistro de rexistro ao que FK fai referencia, por exemplo, a un grupo de rexistros", entón cando o inseris, PostgreSQL non lle queda máis que como tomalo e facelo honestamente SELECT 1 FROM master_fk1_table WHERE ... co identificador que estás tentando inserir, só para comprobar que este rexistro está presente alí, que non "rompes" esta chave estranxeira coa túa inserción.

En lugar dun rexistro para a táboa de destino e os seus índices, obtemos o beneficio adicional de ler todas as táboas ás que fai referencia. Pero non o necesitamos en absoluto: a nosa tarefa é gravar o máximo posible e o máis rápido posible coa menor carga. Entón FK - abaixo!

O seguinte punto é a agregación e o hash. Inicialmente, implementámolos na base de datos; despois de todo, é conveniente facelo inmediatamente, cando chega un rexistro, nalgún tipo de tableta. "máis un" xusto no gatillo. Ben, é conveniente, pero o mesmo malo: inseris un rexistro, pero tes obrigado a ler e escribir outra cousa doutra táboa. Ademais, non só le e escribe, tamén o fai cada vez.

Agora imaxina que tes unha táboa na que simplemente contas o número de solicitudes que pasaron por un host específico: +1, +1, +1, ..., +1. E ti, en principio, non necesitas isto: todo é posible suma na memoria no colector e enviar á base de datos dunha soa vez +10.

Si, en caso de problemas, a súa integridade lóxica pode "derrubarse", pero este é un caso case pouco realista: porque tes un servidor normal, ten unha batería no controlador, tes un rexistro de transaccións, un rexistro no sistema de ficheiros... En xeral, non paga a pena. A perda de produtividade que obtén ao executar disparadores/FK non paga a pena o gasto que incorre.

É o mesmo co hash. Unha determinada solicitude vola para ti, calculas un determinado identificador a partir del na base de datos, escríbeo na base de datos e despois dillo a todos. Todo está ben ata que, no momento da gravación, chega a ti unha segunda persoa que quere gravar o mesmo, e te bloqueas, e isto xa está mal. Polo tanto, se pode transferir a xeración dalgúns ID ao cliente (en relación á base de datos), é mellor facelo.

Foi perfecto para nós usar MD5 a partir do texto: solicitude, plano, modelo,... Calculámolo no lado do coleccionista e "vertemos" o ID preparado na base de datos. A duración de MD5 e a partición diaria permítenos non preocuparnos por posibles colisións.

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

Pero para gravar todo isto rapidamente, necesitabamos modificar o propio procedemento de gravación.

Como adoita escribir datos? Temos algún tipo de conxunto de datos, dividímolo en varias táboas e despois COPIALO - primeiro na primeira, despois na segunda, na terceira... É un inconveniente, porque parece que estamos escribindo un fluxo de datos en tres pasos. secuencialmente. Desagradable. Pódese facer máis rápido? Pode!

Para iso, basta con descompoñer estes fluxos en paralelo entre si. Resulta que temos erros, solicitudes, modelos, bloqueos,... voando en fíos separados -e escribimos todo en paralelo. Abonda para isto manter unha canle COPY aberta constantemente para cada táboa de destino individual.

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

É dicir, no coleccionista sempre hai unha corrente, no que podo escribir os datos que necesito. Pero para que a base de datos vexa estes datos e alguén non se quede atascado á espera de que estes datos sexan escritos, COPY debe interromperse a determinados intervalos. Para nós, o período máis efectivo foi duns 100 ms: pechámolo e abrimos inmediatamente de novo á mesma mesa. E se non temos un fluxo suficiente durante algúns picos, entón facemos a agrupación ata un determinado límite.

Ademais, descubrimos que para tal perfil de carga, calquera agregación, cando os rexistros se recollen en lotes, é malo. O mal clásico é INSERT ... VALUES e máis 1000 rexistros. Porque nese momento tes un pico de escritura nos medios, e todos os demais que intenten escribir algo no disco estarán esperando.

Para desfacerse de tales anomalías, simplemente non agregue nada, non amortiguar nada. E se se produce o almacenamento no búfer no disco (afortunadamente, a API Stream en Node.js permíteche descubrir) - apraza esta conexión. Cando recibas un evento que está de novo gratuíto, escríbeo desde a cola acumulada. E mentres estea ocupado, colle o seguinte gratuíto da piscina e escríbeo.

Antes de introducir este enfoque para a gravación de datos, tivemos aproximadamente 4K operacións de escritura e, deste xeito, reducimos a carga 4 veces. Agora creceron outras 6 veces debido ás novas bases de datos supervisadas: ata 100 MB/s. E agora almacenamos os rexistros dos últimos 3 meses nun volume duns 10-15 TB, esperando que en só tres meses calquera desenvolvedor poida resolver calquera problema.

Entendemos os problemas

Pero simplemente recoller todos estes datos é bo, útil, relevante, pero non suficiente: hai que entendelo. Porque estes son millóns de plans diferentes ao día.

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

Pero millóns son inmanexables, primeiro hai que facer "menor". E, en primeiro lugar, debes decidir como organizarás esta cousa "pequena".

Identificamos tres puntos fundamentais:

  • quen enviou esta solicitude
    É dicir, de que aplicación "chegou": interface web, backend, sistema de pago ou outra cousa.
  • onde pasou
    En que servidor específico? Porque se tes varios servidores baixo unha aplicación e, de súpeto, un "vólvese estúpido" (porque o "disco está podre", "a memoria filtrada", algún outro problema), entón tes que abordar específicamente o servidor.
  • como o problema manifestouse dun xeito ou doutro

Para entender "quen" nos enviou unha solicitude, usamos unha ferramenta estándar: establecemos unha variable de sesión: SET application_name = '{bl-host}:{bl-method}'; — enviamos o nome do host de lóxica empresarial do que procede a solicitude e o nome do método ou aplicación que a iniciou.

Despois de pasar o "propietario" da solicitude, debe saír ao rexistro; para iso configuramos a variable log_line_prefix = ' %m [%p:%v] [%d] %r %a'. Para os interesados, quizais mira no manualque significa todo. Resulta que vemos no rexistro:

  • tempo
  • identificadores de proceso e transacción
  • nome da base de datos
  • IP da persoa que enviou esta solicitude
  • e nome do método

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

Entón decatámonos de que non é moi interesante ver a correlación dunha solicitude entre distintos servidores. Non é frecuente que teñas unha situación na que unha aplicación falla igual aquí e alí. Pero aínda que sexa o mesmo, mira calquera destes servidores.

Entón, aquí está o corte "un servidor - un día" resultounos suficiente para calquera análise.

A primeira sección analítica é a mesma "mostra" - unha forma abreviada de presentación do plan, despexada de todos os indicadores numéricos. O segundo corte é a aplicación ou método, e o terceiro corte é o nodo do plan específico que nos causou problemas.

Cando pasamos de instancias específicas a modelos, obtivemos dúas vantaxes á vez:

  • redución múltiple do número de obxectos para a análise
    Temos que analizar o problema xa non por miles de consultas ou plans, senón por decenas de modelos.
  • cronograma
    É dicir, resumindo os "feitos" dentro dunha determinada sección, pode mostrar o seu aspecto durante o día. E aquí podes entender que se tes algún tipo de patrón que ocorre, por exemplo, unha vez por hora, pero debería ocorrer unha vez ao día, deberías pensar en que pasou mal: quen o causou e por que, quizais debería estar aquí. non debería. Este é outro método de análise non numérico, puramente visual.

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

Os métodos restantes baséanse nos indicadores que extraemos do plan: cantas veces se produciu tal patrón, o tempo total e medio, cantos datos se liron do disco e cantos da memoria...

Porque, por exemplo, chegas á páxina de análise do host, mira, algo está empezando a ler demasiado no disco. O disco do servidor non pode manexalo: quen le?

E podes ordenar por calquera columna e decidir o que te ocuparás agora mesmo: a carga no procesador ou no disco, ou o número total de solicitudes... Clasificamos, miramos as "superiores", arranxámola e lanzou unha nova versión da aplicación.
[video conferencia]

E inmediatamente podes ver diferentes aplicacións que veñen co mesmo modelo desde unha solicitude como SELECT * FROM users WHERE login = 'Vasya'. Frontend, backend, procesamento... E pregúntase por que o procesamento lería ao usuario se non interactúa con el.

O xeito oposto é ver inmediatamente desde a aplicación o que fai. Por exemplo, o frontend é isto, isto, isto e isto unha vez por hora (a liña de tempo axuda). E inmediatamente xorde a pregunta: parece que non é traballo do frontend facer algo unha vez por hora...

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

Despois dun tempo, decatámonos de que carecíamos de agregados estatísticas por nodos do plan. Illamos dos planos só aqueles nodos que fan algo cos datos das propias táboas (lélos/escribímolos por índice ou non). De feito, só se engade un aspecto en relación á imaxe anterior: cantos rexistros nos trouxo este nodo?, e cantas se descartaron (Filas eliminadas polo filtro).

Non tes un índice axeitado na placa, fais unha solicitude, pasa voando o índice, cae en Seq Scan... filtraches todos os rexistros excepto un. Por que necesitas 100 millóns de rexistros filtrados ao día? Non é mellor aumentar o índice?

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

Despois de analizar todos os planos nodo por nodo, decatámonos de que hai algunhas estruturas típicas nos planos que son moi susceptibles de parecer sospeitosas. E sería bo dicirlle ao desenvolvedor: "Amigo, aquí primeiro les por índice, despois ordenas e despois cortas" - por regra xeral, hai un rexistro.

Todos os que escribiron consultas probablemente atoparon este patrón: "Dáme o último pedido para Vasya, a súa data". E se non tes un índice por data ou non hai ningunha data no índice que usaches, entón pisa exactamente o mesmo "rastrillo".

Pero sabemos que este é un "rastrillo" - entón por que non dicir inmediatamente ao desenvolvedor o que debe facer. En consecuencia, ao abrir un plan agora, o noso desenvolvedor inmediatamente ve unha fermosa imaxe con consellos, onde inmediatamente lle din: "Tes problemas aquí e alí, pero resólvense deste xeito e doutro xeito".

Como resultado, a cantidade de experiencia que se necesitaba para resolver problemas ao principio e agora diminuíu significativamente. Este é o tipo de ferramenta que temos.

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

Fonte: www.habr.com

Engadir un comentario