Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

L'informe presenta alguns enfocaments que permeten supervisar el rendiment de les consultes SQL quan n'hi ha milions al dia, i hi ha centenars de servidors PostgreSQL supervisats.

Quines solucions tècniques ens permeten processar de manera eficient aquest volum d'informació, i com fa això que la vida d'un desenvolupador normal sigui més fàcil?


A qui li interessa? anàlisi de problemes específics i diverses tècniques d'optimització Consultes SQL i resoldre problemes típics de DBA a PostgreSQL, també podeu llegir una sèrie d'articles sobre aquest tema.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)
Em dic Kirill Borovikov, represento Empresa tensor. En concret, estic especialitzat a treballar amb bases de dades de la nostra empresa.

Avui us explicaré com optimitzem les consultes, quan no cal que "seleccioneu" el rendiment d'una sola consulta, sinó que resolgueu el problema en massa. Quan hi ha milions de peticions i n'has de trobar plantejaments de solució aquest gran problema.

En general, Tensor per a un milió dels nostres clients ho és VLSI és la nostra aplicació: xarxa social corporativa, solucions per a la comunicació de vídeo, per al flux de documents interns i externs, sistemes comptables de comptabilitat i magatzems,... És a dir, una “mega-combinada” per a la gestió integrada del negoci, en la qual hi ha més de 100 diferents projectes interns.

Per garantir que tots funcionin i es desenvolupin amb normalitat, disposem de 10 centres de desenvolupament arreu del país, amb més en ells 1000 desenvolupadors.

Treballem amb PostgreSQL des del 2008 i hem acumulat una gran quantitat del que processem: dades de clients, estadístiques, analítiques, dades de sistemes d'informació externs. més de 400 TB. Només en producció hi ha uns 250 servidors, i en total hi ha uns 1000 servidors de bases de dades que controlem.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

SQL és un llenguatge declaratiu. No descriu "com" hauria de funcionar una cosa, sinó "què" voleu aconseguir. El SGBD sap millor com fer un JOIN: com connectar les teves taules, quines condicions imposar, què passarà per l'índex, què no...

Alguns SGBD accepten pistes: "No, connecteu aquestes dues taules en tal o tal cua", però PostgreSQL no pot fer-ho. Aquesta és la posició conscient dels principals desenvolupadors: "Preferim acabar l'optimitzador de consultes que permetre als desenvolupadors utilitzar algun tipus de pistes".

Però, malgrat que PostgreSQL no permet que l'"exterior" es controli, ho permet perfectament veure què passa dins seuquan executeu la vostra consulta i on hi ha problemes.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

En general, amb quins problemes clàssics sol tenir un desenvolupador [a un DBA]? “Aquí vam complir la petició, i tot va lent amb nosaltres, tot està penjat, alguna cosa està passant... Alguna mena de problema!”

Els motius són gairebé sempre els mateixos:

  • algorisme de consulta ineficient
    Desenvolupador: "Ara li dono 10 taules en SQL mitjançant JOIN..." - i espera que les seves condicions es "deslliguin" de manera eficaç i ho obtindrà tot ràpidament. Però els miracles no succeeixen, i qualsevol sistema amb aquesta variabilitat (10 taules en un FROM) sempre dóna algun tipus d'error. [article]
  • estadístiques obsoletes
    Aquest punt és molt rellevant específicament per a PostgreSQL, quan heu "abocat" un conjunt de dades gran al servidor, feu una sol·licitud i "sexcanita" la vostra tauleta. Perquè ahir hi havia 10 registres, i avui n'hi ha 10 milions, però PostgreSQL encara no n'és conscient i cal que ho expliquem. [article]
  • "connectar" als recursos
    Heu instal·lat una base de dades gran i molt carregada en un servidor feble que no té prou rendiment de disc, memòria o processador. I això és tot... En algun lloc hi ha un sostre d'actuació per sobre del qual ja no pots saltar.
  • bloqueig
    Aquest és un punt difícil, però són més rellevants per a diverses consultes de modificació (INSERT, ACTUALITZACIÓ, ELIMINAR) - aquest és un gran tema a part.

Aconseguint un pla

...I per a tota la resta nosaltres necessita un pla! Hem de veure què passa dins del servidor.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Un pla d'execució de consultes per a PostgreSQL és un arbre de l'algorisme d'execució de consultes en la representació de text. És precisament l'algoritme que, com a resultat de l'anàlisi del planificador, va resultar més efectiu.

Cada node d'arbre és una operació: recuperar dades d'una taula o índex, construir un mapa de bits, unir dues taules, unir, intersectar o excloure seleccions. L'execució d'una consulta implica caminar pels nodes d'aquest arbre.

Per obtenir el pla de consulta, la manera més senzilla és executar la instrucció EXPLAIN. Per obtenir tots els atributs reals, és a dir, per executar realment una consulta a la base - EXPLAIN (ANALYZE, BUFFERS) SELECT ....

La part dolenta: quan l'executeu, passa "aquí i ara", de manera que només és adequat per a la depuració local. Si prens un servidor molt carregat que està sota un fort flux de canvis de dades i veus: "Oh! Aquí tenim una execució lentaся petició". Fa mitja hora, una hora, mentre estaves executant i rebent aquesta sol·licitud dels registres, tornant-la al servidor, tot el teu conjunt de dades i estadístiques van canviar. L'executeu per depurar i s'executa ràpidament! I no pots entendre per què, per què era lentament.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Per entendre què va passar exactament en el moment en què es va executar la sol·licitud al servidor, van escriure persones intel·ligents mòdul auto_explain. Està present en gairebé totes les distribucions PostgreSQL més comunes i simplement es pot activar al fitxer de configuració.

Si s'adona que alguna sol·licitud s'executa més que el límit que li vau dir, ho fa "instantània" del pla d'aquesta sol·licitud i els escriu junts al registre.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Ara sembla que tot va bé, anem al registre i ho veiem... [text topo]. Però no podem dir res al respecte, a part del fet que és un pla excel·lent perquè va trigar 11 ms a executar-se.

Sembla que tot va bé, però res no està clar què va passar realment. A part del temps general, realment no veiem res. Perquè mirar un "xai" com aquest de text senzill generalment no és visual.

Però encara que no sigui obvi, encara que sigui inconvenient, hi ha problemes més fonamentals:

  • El node indica suma de recursos de tot el subarbre sota ell. És a dir, no només podeu esbrinar quant de temps s'ha dedicat a aquesta exploració d'índexs en particular si hi ha alguna condició imbricada. Hem de mirar dinàmicament per veure si hi ha "fills" i variables condicionals, CTEs dins, i restar tot això "a la nostra ment".
  • Segon punt: l'hora que s'indica al node és temps d'execució d'un sol node. Si aquest node s'ha executat com a resultat, per exemple, d'un bucle a través dels registres de la taula diverses vegades, aleshores el nombre de bucles (cicles d'aquest node) augmenta al pla. Però el temps d'execució atòmica en si segueix sent el mateix en termes de pla. És a dir, per entendre quant de temps es va realitzar aquest node en total, heu de multiplicar una cosa per una altra, de nou, "al vostre cap".

En aquestes situacions, enteneu "Qui és l'enllaç més feble?" gairebé impossible. Per tant, fins i tot els mateixos desenvolupadors escriuen al "manual" que “Entendre un pla és un art que s'ha d'aprendre, experimentar...”.

Però tenim 1000 desenvolupadors i no pots transmetre aquesta experiència a cadascun d'ells. Jo, tu, ell ho sé, però algú d'allà ja no ho sap. Potser aprendrà, o potser no, però ara ha de treballar, i d'on obtindria aquesta experiència?

Visualització del pla

Per tant, ens vam adonar que per fer front a aquests problemes, necessitem bona visualització del pla. [article]

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Primer vam anar "pel mercat": mirem a Internet per veure què existeix.

Però va resultar que hi ha molt poques solucions relativament "vives" que s'estan desenvolupant més o menys, literalment, només una: explica.depesz.com per Hubert Lubaczewski. Quan introduïu al camp "alimentació" una representació de text del pla, us mostra una taula amb les dades analitzades:

  • temps de processament del propi node
  • temps total per a tot el subarbre
  • nombre de registres que es van recuperar que s'esperaven estadísticament
  • el propi cos del node

Aquest servei també té la possibilitat de compartir un arxiu d'enllaços. Vas llançar el teu pla allà dins i vas dir: "Ei, Vasya, aquí tens un enllaç, hi ha alguna cosa que no va bé".

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Però també hi ha petits problemes.

En primer lloc, una gran quantitat de "copiar i enganxar". Agafes un tros del tronc, l'enganxes allà dins, i una altra i una altra vegada.

En segon lloc, la cap anàlisi de la quantitat de dades llegides — els mateixos buffers que la sortida EXPLAIN (ANALYZE, BUFFERS), aquí no ho veiem. Simplement no sap com desmuntar-los, entendre-los i treballar-hi. Quan esteu llegint moltes dades i us adoneu que podeu estar assignant malament la memòria cau del disc i la memòria, aquesta informació és molt important.

El tercer punt negatiu és el desenvolupament molt feble d'aquest projecte. Els commits són molt petits, és bo si un cop cada sis mesos, i el codi està en Perl.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Però tot això és "lletra", d'alguna manera podríem viure amb això, però hi ha una cosa que ens va allunyar molt d'aquest servei. Es tracta d'errors en l'anàlisi de Common Table Expression (CTE) i de diversos nodes dinàmics com InitPlan/SubPlan.

Si creieu aquesta imatge, aleshores el temps d'execució total de cada node individual és més gran que el temps d'execució total de tota la sol·licitud. És fàcil - el temps de generació d'aquest CTE no es va restar del node CTE Scan. Per tant, ja no sabem la resposta correcta a quant de temps va trigar l'exploració CTE.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Llavors ens vam adonar que era el moment d'escriure la nostra pròpia, hurra! Tots els desenvolupadors diuen: "Ara escriurem el nostre, serà molt fàcil!"

Vam agafar una pila típica per als serveis web: un nucli basat en Node.js + Express, vam utilitzar Bootstrap i D3.js per a diagrames bonics. I les nostres expectatives estaven totalment justificades: vam rebre el primer prototip en dues setmanes:

  • analitzador de plans personalitzats
    És a dir, ara podem analitzar qualsevol pla dels generats per PostgreSQL.
  • anàlisi correcta dels nodes dinàmics - CTE Scan, InitPlan, SubPlan
  • anàlisi de la distribució dels buffers - on es llegeixen les pàgines de dades des de la memòria, on des de la memòria cau local, on des del disc
  • va tenir claredat
    Per no "excavar" tot això al registre, sinó per veure l'"enllaç més feble" immediatament a la imatge.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Tenim alguna cosa com això, amb el ressaltat de sintaxi inclòs. Però normalment els nostres desenvolupadors ja no treballen amb una representació completa del pla, sinó amb una de més curta. Al cap i a la fi, ja hem analitzat tots els números i els hem llençat a dreta i esquerra, i al mig només hem deixat la primera línia, quin tipus de node és: CTE Scan, CTE generation o Seq Scan segons algun signe.

Aquesta és la representació abreujada que anomenem plantilla de pla.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Què més seria convenient? Seria convenient veure quina part del nostre temps total s'assigna a quin node, i simplement "enganxar-lo" al costat gràfic circular.

Assenyalem el node i veiem: resulta que Seq Scan va trigar menys d'una quarta part del temps total, i els 3/4 restants els va prendre CTE Scan. Horror! Aquesta és una petita nota sobre la "cadena de foc" de CTE Scan si les feu servir activament a les vostres consultes. No són molt ràpids: són inferiors fins i tot a l'escaneig de taula normal. [article] [article]

Però normalment aquests diagrames són més interessants, més complexos, quan apuntem immediatament a un segment i veiem, per exemple, que més de la meitat de les vegades algun Seq Scan "menja". A més, hi havia una mena de filtre a l'interior, es van descartar molts registres segons això... Pots llançar aquesta imatge directament al desenvolupador i dir-li: "Vasya, tot està malament aquí per a tu! Descobriu-ho, mira, hi ha alguna cosa malament!"

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Naturalment, hi havia alguns "rastells" implicats.

El primer que ens vam trobar va ser el problema d'arrodoniment. El temps de cada node individual del pla s'indica amb una precisió d'1 μs. I quan el nombre de cicles de nodes supera, per exemple, 1000, després de l'execució, PostgreSQL dividit "dins de la precisió", aleshores en calcular de nou obtenim el temps total "entre 0.95 ms i 1.05 ms". Quan el recompte passa a microsegons, està bé, però quan ja són [mil·li]segons, cal tenir en compte aquesta informació a l'hora de "deslligar" recursos als nodes del pla "qui va consumir quant".

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

El segon punt, més complex, és la distribució de recursos (aquells buffers) entre nodes dinàmics. Això ens va costar les 2 primeres setmanes del prototip més 4 setmanes més.

És bastant fàcil trobar aquest tipus de problema: fem un CTE i suposadament llegim alguna cosa. De fet, PostgreSQL és "intel·ligent" i no hi llegirà res directament. Llavors li agafem el primer disc, i a ell el cent primer del mateix CTE.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Mirem el pla i entenem: és estrany, tenim 3 buffers (pàgines de dades) "consumits" a Seq Scan, 1 més a CTE Scan i 2 més al segon CTE Scan. És a dir, si simplement ho resumim tot, en sortirem 6, però des de la tauleta només en llegim 3! CTE Scan no llegeix res des de cap lloc, però funciona directament amb la memòria del procés. És a dir, aquí hi ha alguna cosa clarament malament!

De fet, resulta que aquí hi ha totes aquelles 3 pàgines de dades que es van sol·licitar a Seq Scan, primer 1 va demanar el 1r CTE Scan, i després se li van llegir la 2a, i 2 més, és a dir, un total de Es van llegir dades de 3 pàgines, no 6.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

I aquesta imatge ens va portar a entendre que l'execució d'un pla ja no és un arbre, sinó simplement una mena de gràfic acíclic. I tenim un diagrama com aquest, de manera que entenem "què va venir d'on en primer lloc". És a dir, aquí vam crear un CTE a partir de pg_class, i el vam demanar dues vegades, i gairebé tot el nostre temps el vam passar a la branca quan el vam demanar per segona vegada. És evident que llegir l'entrada 2 és molt més car que només llegir la primera entrada des de la tauleta.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Vam exhalar una estona. Van dir: "Ara, Neo, saps kung fu! Ara la nostra experiència és a la pantalla. Ara pots utilitzar-lo". [article]

Consolidació de registres

Els nostres 1000 desenvolupadors van respirar alleujats. Però vam entendre que només tenim centenars de servidors de "combat", i tot aquest "copiar i enganxar" per part dels desenvolupadors no és gens convenient. Ens vam adonar que l'havíem de recollir nosaltres mateixos.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

En general, hi ha un mòdul estàndard que pot recopilar estadístiques, però també s'ha d'activar a la configuració, això mòdul pg_stat_statements. Però no ens agradava.

En primer lloc, assigna a les mateixes consultes utilitzant diferents esquemes dins de la mateixa base de dades diferents QueryIds. És a dir, si primer ho fas SET search_path = '01'; SELECT * FROM user LIMIT 1;i després SET search_path = '02'; i la mateixa sol·licitud, llavors les estadístiques d'aquest mòdul tindran registres diferents, i no podré recollir estadístiques generals específicament en el context d'aquest perfil de sol·licitud, sense tenir en compte els esquemes.

El segon punt que ens va impedir utilitzar-lo és manca de plans. És a dir, no hi ha cap pla, només hi ha la petició mateixa. Veiem què es va alentir, però no entenem per què. I aquí tornem al problema d'un conjunt de dades que canvia ràpidament.

I l'últim moment - manca de "fets". És a dir, no podeu abordar una instància específica d'execució de consultes; no n'hi ha cap, només hi ha estadístiques agregades. Tot i que és possible treballar amb això, és molt difícil.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Per tant, vam decidir lluitar contra el còpia-enganxa i vam començar a escriure col·lector.

El col·lector es connecta mitjançant SSH, estableix una connexió segura al servidor amb la base de dades mitjançant un certificat i tail -F s'hi "aferra" al fitxer de registre. Així que en aquesta sessió obtenim un "mirall" complet de tot el fitxer de registre, que genera el servidor. La càrrega al servidor en si és mínima, perquè no analitzem res allà, només reflectim el trànsit.

Com que ja havíem començat a escriure la interfície a Node.js, vam continuar escrivint-hi el col·lector. I aquesta tecnologia s'ha justificat, perquè és molt convenient utilitzar JavaScript per treballar amb dades de text amb format dèbil, que és el registre. I la pròpia infraestructura Node.js com a plataforma de fons us permet treballar de manera fàcil i còmoda amb connexions de xarxa i, de fet, amb qualsevol flux de dades.

En conseqüència, "estirem" dues connexions: la primera per "escoltar" el propi registre i prendre-la per nosaltres mateixos, i la segona per preguntar periòdicament a la base. "Però el registre mostra que el signe amb oid 123 està bloquejat", però això no significa res per al desenvolupador, i seria bo preguntar a la base de dades: "Què és OID = 123 de totes maneres?" I per tant preguntem periòdicament a la base allò que encara no sabem de nosaltres mateixos.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

"Només hi ha una cosa que no heu tingut en compte, hi ha una espècie d'abelles semblants a un elefant!..." Vam començar a desenvolupar aquest sistema quan volíem controlar 10 servidors. El més crític al nostre entendre, on van sorgir alguns problemes difícils de tractar. Però durant el primer trimestre n'hem rebut un centenar per al seguiment, perquè el sistema funcionava, tothom ho volia, tothom estava còmode.

Tot això s'ha de sumar, el flux de dades és gran i actiu. De fet, el que controlem, el que podem tractar, és el que fem servir. També fem servir PostgreSQL com a emmagatzematge de dades. I res és més ràpid per "abocar-hi" dades que l'operador COPY Encara no.

Però simplement "abocar" dades no és realment la nostra tecnologia. Perquè si teniu aproximadament 50 sol·licituds per segon en un centenar de servidors, això generarà entre 100 i 150 GB de registres per dia. Per tant, vam haver de "tallar" la base amb cura.

En primer lloc, ho vam fer repartiment per dia, perquè, en general, a ningú li interessa la correlació entre dies. Quina diferència hi ha amb el que teníeu ahir, si aquesta nit heu llançat una nova versió de l'aplicació, i ja algunes estadístiques noves.

En segon lloc, vam aprendre (ens van forçar) molt, molt ràpid d'escriure utilitzant COPY. És a dir, no només COPYperquè és més ràpid que INSERT, i encara més ràpid.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

El tercer punt - ho havia de fer abandona els activadors, respectivament, i les claus externes. És a dir, no tenim cap integritat referencial. Perquè si tens una taula que té un parell de FK i dius a l'estructura de la base de dades que "aquí hi ha un registre de registre al qual FK fa referència, per exemple, a un grup de registres", aleshores quan l'insereixes, PostgreSQL no li queda res més que com prendre-ho i fer-ho honestament SELECT 1 FROM master_fk1_table WHERE ... amb l'identificador que esteu intentant inserir - només per comprovar que aquest registre hi és present, que no "trengueu" aquesta clau estrangera amb la vostra inserció.

En lloc d'un registre a la taula de destinació i als seus índexs, obtenim l'avantatge afegit de llegir totes les taules a les quals fa referència. Però això no ho necessitem en absolut: la nostra tasca és gravar el màxim possible i el més ràpid possible amb la menor càrrega. Així que FK - avall!

El següent punt és l'agregació i el hashing. Inicialment, els vam implementar a la base de dades; després de tot, és convenient fer-ho immediatament, quan arriba un registre, en algun tipus de tauleta. "més un" just al disparador. Bé, és convenient, però el mateix dolent: inseriu un registre, però us oblideu a llegir i escriure una altra cosa d'una altra taula. A més, no només llegeixes i escrius, també ho fas cada vegada.

Ara imagineu que teniu una taula en la qual simplement compteu el nombre de sol·licituds que han passat per un amfitrió específic: +1, +1, +1, ..., +1. I tu, en principi, no ho necessites: tot és possible suma a la memòria al col·lector i enviar a la base de dades d'una vegada +10.

Sí, en cas d'alguns problemes, la vostra integritat lògica pot "desaparèixer", però aquest és un cas gairebé poc realista: perquè teniu un servidor normal, té una bateria al controlador, teniu un registre de transaccions, un registre al sistema de fitxers... En general, no val la pena. La pèrdua de productivitat que obteniu en executar activadors/FK no val la despesa en què incorreu.

Passa el mateix amb el hashing. Una determinada sol·licitud vola cap a tu, en calcules un determinat identificador a la base de dades, l'escrius a la base de dades i després ho dius a tothom. Tot va bé fins que, en el moment de gravar, et ve una segona persona que vol gravar el mateix, i et bloquegen, i això ja és dolent. Per tant, si podeu transferir la generació d'alguns identificadors al client (relatiu a la base de dades), és millor fer-ho.

Va ser perfecte per a nosaltres utilitzar MD5 del text: sol·licitud, pla, plantilla,... Ho calculem pel costat del col·lector i "aboquem" l'identificador ja fet a la base de dades. La durada de l'MD5 i la partició diària ens permeten no preocupar-nos per possibles col·lisions.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Però per gravar tot això ràpidament, calia modificar el propi procediment d'enregistrament.

Com s'escriu habitualment les dades? Tenim algun tipus de conjunt de dades, el dividim en diverses taules i després el COPIEM: primer a la primera, després a la segona, a la tercera... És un inconvenient, perquè sembla que estem escrivint un flux de dades en tres passos. seqüencialment. Desagradable. Es pot fer més ràpid? Llauna!

Per fer-ho, n'hi ha prou amb descompondre aquests fluxos en paral·lel entre si. Resulta que tenim errors, peticions, plantilles, bloquejos,... volant en fils separats -i ho escrivim tot en paral·lel. Prou per això mantenir un canal COPY obert constantment per a cada taula de destinació individual.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

És a dir, al col·leccionista sempre hi ha un corrent, on puc escriure les dades que necessito. Però perquè la base de dades vegi aquestes dades i algú no es quedi encallat esperant que s'escriguin aquestes dades, COPY s'ha d'interrompre a determinats intervals. Per a nosaltres, el període més efectiu va ser d'uns 100 ms: el tanquem i el tornem a obrir immediatament a la mateixa taula. I si no en tenim prou d'un cabal durant alguns pics, fem una agrupació fins a un cert límit.

A més, vam descobrir que per a aquest perfil de càrrega, qualsevol agregació, quan els registres es recullen per lots, és dolenta. El mal clàssic és INSERT ... VALUES i més de 1000 registres. Perquè en aquest moment teniu un pic d'escriptura al suport, i tots els altres que intentin escriure alguna cosa al disc estaran esperant.

Per desfer-se d'aquestes anomalies, simplement no afegiu res, no amortigueu gens. I si es produeix la memòria intermèdia al disc (afortunadament, l'API Stream a Node.js us permet descobrir-ho), posposeu aquesta connexió. Quan rebeu un esdeveniment que torna a ser gratuït, escriu-hi des de la cua acumulada. I mentre estigui ocupat, agafeu el següent gratuït de la piscina i escriviu-hi.

Abans d'introduir aquest enfocament a l'enregistrament de dades, teníem aproximadament operacions d'escriptura 4K i, d'aquesta manera, vam reduir la càrrega 4 vegades. Ara han crescut 6 vegades més a causa de les noves bases de dades supervisades: fins a 100 MB/s. I ara emmagatzemem els registres dels darrers 3 mesos en un volum d'uns 10-15 TB, amb l'esperança que en només tres mesos qualsevol desenvolupador pugui resoldre qualsevol problema.

Entenem els problemes

Però simplement recollir totes aquestes dades és bo, útil, rellevant, però no suficient: cal entendre-ho. Perquè aquests són milions de plans diferents al dia.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Però milions són inmanejables, primer hem de fer "més petit". I, en primer lloc, heu de decidir com organitzareu aquesta cosa "menor".

Hem identificat tres punts clau:

  • qui? enviat aquesta petició
    És a dir, de quina aplicació va “arribar”: interfície web, backend, sistema de pagament o una altra cosa.
  • on succeir
    En quin servidor concret? Perquè si teniu diversos servidors sota una aplicació i, de sobte, un "es fa estúpid" (perquè el "disc està podrit", la "memòria filtrada", algun altre problema), heu d'adreçar-vos específicament al servidor.
  • как el problema es va manifestar d'una manera o altra

Per entendre "qui" ens ha enviat una sol·licitud, utilitzem una eina estàndard: establim una variable de sessió: SET application_name = '{bl-host}:{bl-method}'; — enviem el nom de l'amfitrió de la lògica empresarial des del qual prové la sol·licitud i el nom del mètode o aplicació que l'ha iniciat.

Després d'haver passat el "propietari" de la sol·licitud, s'ha de sortir al registre; per això configurem la variable log_line_prefix = ' %m [%p:%v] [%d] %r %a'. A qui li importa, potser mira al manualquè vol dir tot plegat. Resulta que veiem al registre:

  • temps
  • identificadors de procés i transacció
  • nom de la base de dades
  • IP de la persona que ha enviat aquesta sol·licitud
  • i nom del mètode

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Aleshores ens vam adonar que no és gaire interessant mirar la correlació d'una sol·licitud entre diferents servidors. No és freqüent que tingueu una situació en què una aplicació es desvirtui per igual aquí i allà. Però encara que sigui el mateix, mireu qualsevol d'aquests servidors.

Així que aquí teniu el tall "un servidor - un dia" ens va resultar suficient per a qualsevol anàlisi.

El primer apartat analític és el mateix "mostra" - una forma abreujada de presentació del pla, eliminada de tots els indicadors numèrics. El segon tall és l'aplicació o mètode, i el tercer tall és el node del pla específic que ens ha causat problemes.

Quan vam passar d'instàncies específiques a plantilles, vam obtenir dos avantatges alhora:

  • reducció múltiple del nombre d'objectes per a l'anàlisi
    Hem d'analitzar el problema ja no per milers de consultes o plans, sinó per desenes de plantilles.
  • cronologia
    És a dir, resumint els "fets" dins d'una secció determinada, podeu mostrar la seva aparició durant el dia. I aquí podeu entendre que si teniu algun tipus de patró que passa, per exemple, una vegada per hora, però hauria de passar una vegada al dia, hauríeu de pensar què va fallar: qui ho va causar i per què, potser hauria d'estar aquí. no hauria. Aquest és un altre mètode d'anàlisi no numèric, purament visual.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

La resta de mètodes es basen en els indicadors que extreim del pla: quantes vegades s'ha produït un patró d'aquest tipus, el temps total i mitjà, quantes dades es van llegir del disc i quantes de la memòria...

Perquè, per exemple, arribeu a la pàgina d'anàlisi de l'amfitrió, mira: alguna cosa comença a llegir massa al disc. El disc del servidor no pot gestionar-ho: qui ho llegeix?

I pots ordenar per qualsevol columna i decidir amb què tractaràs ara mateix: la càrrega del processador o del disc, o el nombre total de peticions... Ho vam ordenar, vam mirar les "superiors", ho vam arreglar i va llançar una nova versió de l'aplicació.
[videoconferència]

I immediatament podeu veure diferents aplicacions que vénen amb la mateixa plantilla des d'una sol·licitud com SELECT * FROM users WHERE login = 'Vasya'. Frontend, backend, processing... I et preguntes per què el processament llegiria l'usuari si no interactua amb ell.

La manera contrària és veure immediatament des de l'aplicació què fa. Per exemple, la interfície és això, això, això i això una vegada per hora (la línia de temps ajuda). I de seguida sorgeix la pregunta: sembla que no és feina del frontend fer alguna cosa un cop per hora...

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Al cap d'un temps, ens vam adonar que ens faltaven agregats estadístiques per nodes del pla. Aïllem dels plànols només aquells nodes que fan alguna cosa amb les dades de les mateixes taules (llegir-les/escriure per índex o no). De fet, només s'afegeix un aspecte respecte a la imatge anterior: quants registres ens ha portat aquest node?, i quants s'han descartat (Files eliminades pel filtre).

No tens un índex adequat a la placa, li fas una sol·licitud, passa volant per sobre de l'índex, cau en Seq Scan... has filtrat tots els registres menys un. Per què necessiteu 100 milions de registres filtrats al dia? No és millor augmentar l'índex?

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Després d'haver analitzat tots els plànols node per node, ens vam adonar que hi ha algunes estructures típiques als plànols que probablement semblen sospitoses. I seria bo dir-li al desenvolupador: "Amic, aquí primer llegiu per índex, després ordeneu i després talleu" - per regla general, hi ha un registre.

Tothom que va escriure consultes probablement s'ha trobat amb aquest patró: "Dóna'm l'última comanda per a Vasya, la seva data." I si no tens un índex per data, o no hi ha cap data a l'índex que has utilitzat, llavors trepitjar exactament el mateix "rastrell".

Però sabem que això és un "rastrell", així que per què no dir-li immediatament al desenvolupador què ha de fer. En conseqüència, quan obre un pla ara, el nostre desenvolupador veu immediatament una imatge bonica amb consells, on immediatament li diuen: "Tens problemes aquí i allà, però es resolen d'una manera i una altra".

Com a resultat, la quantitat d'experiència que es necessitava per resoldre problemes al principi i ara ha disminuït considerablement. Aquest és el tipus d'eina que tenim.

Optimització massiva de consultes PostgreSQL. Kirill Borovikov (tensor)

Font: www.habr.com

Afegeix comentari