Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Rapporten præsenterer nogle tilgange, der tillader det overvåge ydelsen af ​​SQL-forespørgsler, når der er millioner af dem om dagen, og der er hundredvis af overvågede PostgreSQL-servere.

Hvilke tekniske løsninger giver os mulighed for effektivt at behandle sådan en mængde information, og hvordan gør det livet for en almindelig udvikler lettere?


Hvem er interesseret? analyse af specifikke problemer og forskellige optimeringsteknikker SQL-forespørgsler og løsning af typiske DBA-problemer i PostgreSQL - du kan også læse en række artikler om dette emne.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)
Mit navn er Kirill Borovikov, jeg repræsenterer Tensor firma. Helt konkret har jeg specialiseret mig i at arbejde med databaser i vores virksomhed.

I dag vil jeg fortælle dig, hvordan vi optimerer forespørgsler, når du ikke behøver at "udvælge" ydeevnen af ​​en enkelt forespørgsel, men løse problemet i massevis. Når der er millioner af anmodninger, og du skal finde nogle tilgange til løsning dette store problem.

Generelt er Tensor for en million af vores kunder VLSI er vores applikation: virksomhedernes sociale netværk, løsninger til videokommunikation, til internt og eksternt dokumentflow, regnskabssystemer til regnskab og lager,... Altså sådan en “mega-kombination” til integreret virksomhedsledelse, hvori der er mere end 100 forskellige interne projekter.

For at sikre, at de alle fungerer og udvikler sig normalt, har vi 10 udviklingscentre i hele landet, med flere i dem 1000 udviklere.

Vi har arbejdet med PostgreSQL siden 2008 og har akkumuleret en stor mængde af det, vi behandler - klientdata, statistisk, analytisk, data fra eksterne informationssystemer - mere end 400TB. Der er omkring 250 servere alene i produktion, og i alt er der omkring 1000 databaseservere, som vi overvåger.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

SQL er et deklarativt sprog. Du beskriver ikke "hvordan" noget skal fungere, men "hvad" du ønsker at opnå. DBMS ved bedre, hvordan man laver en JOIN - hvordan man forbinder dine tabeller, hvilke betingelser der skal pålægges, hvad vil gå gennem indekset, hvad vil ikke...

Nogle DBMS'er accepterer hints: "Nej, forbind disse to tabeller i sådan og sådan en kø," men PostgreSQL kan ikke gøre dette. Dette er førende udvikleres bevidste holdning: "Vi vil hellere afslutte forespørgselsoptimeringsværktøjet end at tillade udviklere at bruge en form for tip."

Men på trods af at PostgreSQL ikke tillader "ydersiden" at styre sig selv, tillader det perfekt se hvad der foregår indeni hamnår du kører din forespørgsel, og hvor den har problemer.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Generelt, hvilke klassiske problemer kommer en udvikler [til en DBA] normalt med? ”Her opfyldte vi anmodningen, og alt er langsomt hos os, alt hænger, der sker noget... En eller anden form for ballade!”

Årsagerne er næsten altid de samme:

  • ineffektiv forespørgselsalgoritme
    Udvikler: "Nu giver jeg ham 10 tabeller i SQL via JOIN..." - og forventer, at hans forhold mirakuløst vil være effektivt "ubundet", og han vil få alt hurtigt. Men mirakler sker ikke, og ethvert system med sådan variation (10 tabeller i en FROM) giver altid en eller anden form for fejl. [artiklen]
  • forældede statistikker
    Dette punkt er meget relevant specifikt for PostgreSQL, når du "hældte" et stort datasæt på serveren, laver en anmodning, og det "sexcaniterer" din tablet. For i går var der 10 poster i den, og i dag er der 10 millioner, men PostgreSQL er endnu ikke klar over dette, og vi skal fortælle det om det. [artiklen]
  • "tilslut" ressourcer
    Du har installeret en stor og tungt belastet database på en svag server, der ikke har nok disk, hukommelse eller processorydelse. Og det er alt... Et eller andet sted er der et præstationsloft, over hvilket du ikke længere kan hoppe.
  • blokering
    Dette er et vanskeligt punkt, men de er mest relevante for forskellige ændringsforespørgsler (INSERT, UPDATE, DELETE) - dette er et separat stort emne.

At få en plan

...Og for alt andet vi har brug for en plan! Vi skal se, hvad der sker inde på serveren.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

En forespørgselsudførelsesplan for PostgreSQL er et træ af forespørgselsudførelsesalgoritmen i tekstrepræsentation. Det er netop algoritmen, der, som et resultat af planlæggerens analyse, blev fundet at være den mest effektive.

Hver træknude er en operation: hentning af data fra en tabel eller et indeks, opbygning af et bitmap, sammenføjning af to tabeller, sammenføjning, krydsning eller ekskludering af markeringer. Udførelse af en forespørgsel involverer at gå gennem noderne i dette træ.

For at få forespørgselsplanen er den nemmeste måde at udføre erklæringen EXPLAIN. For at få med alle reelle attributter, det vil sige, faktisk at udføre en forespørgsel på basen - EXPLAIN (ANALYZE, BUFFERS) SELECT ....

Den dårlige del: når du kører det, sker det "her og nu", så det er kun egnet til lokal debugging. Hvis du tager en meget belastet server, der er under en stærk strøm af dataændringer, og du ser: "Åh! Her har vi en langsom udførelseXia anmodning." For en halv time, en time siden - mens du kørte og hentede denne anmodning fra logfilerne, og bragte den tilbage til serveren, ændredes hele dit datasæt og statistik. Du kører det for at fejlfinde - og det kører hurtigt! Og du kan ikke forstå hvorfor, hvorfor det var langsomt.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

For at forstå, hvad der præcis skete i det øjeblik, hvor anmodningen blev udført på serveren, skrev smarte folk auto_explain-modul. Det er til stede i næsten alle de mest almindelige PostgreSQL-distributioner og kan simpelthen aktiveres i konfigurationsfilen.

Hvis den indser, at en anmodning kører længere end den grænse, du har bedt den om, gør den det "øjebliksbillede" af planen for denne anmodning og skriver dem sammen i loggen.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Alt ser ud til at være i orden nu, vi går til loggen og ser der... [tekst-fodklæde]. Men vi kan ikke sige noget om det, andet end at det er en fremragende plan, fordi det tog 11 ms at udføre.

Alt ser ud til at være i orden - men intet er klart, hvad der egentlig skete. Bortset fra den generelle tid ser vi ikke rigtig noget. For at se på sådan et "lam" af almindelig tekst er generelt ikke visuelt.

Men selvom det ikke er indlysende, selvom det er ubelejligt, er der mere grundlæggende problemer:

  • Noden angiver summen af ​​ressourcer for hele undertræet under ham. Det vil sige, du kan ikke bare finde ud af, hvor meget tid der blev brugt på netop denne Index Scan, hvis der er en indlejret tilstand under den. Vi skal dynamisk se for at se, om der er "børn" og betingede variabler, CTE'er indeni - og trække alt dette fra "i vores sind".
  • Andet punkt: den tid, der er angivet på noden er enkelt node eksekveringstid. Hvis denne node blev udført som et resultat af, for eksempel, en loop gennem tabelposter flere gange, så stiger antallet af loops - cyklusser af denne node - i planen. Men selve atomudførelsestiden forbliver den samme planmæssigt. Det vil sige, for at forstå, hvor længe denne node blev udført i alt, skal du gange en ting med en anden - igen "i dit hoved."

I sådanne situationer skal du forstå "Hvem er det svageste led?" næsten umuligt. Derfor skriver selv udviklerne selv i "manualen" at "At forstå en plan er en kunst, der skal læres, opleves...".

Men vi har 1000 udviklere, og du kan ikke formidle denne oplevelse til hver af dem. Jeg, du, han ved det, men nogen derovre ved det ikke længere. Måske vil han lære, eller måske ikke, men han skal arbejde nu – og hvor skulle han få denne erfaring?

Planlæg visualisering

Derfor indså vi, at for at kunne håndtere disse problemer, har vi brug for det god visualisering af planen. [artikel]

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Vi gik først "gennem markedet" - lad os se på internettet for at se, hvad der overhovedet eksisterer.

Men det viste sig, at der er meget få relativt "live" løsninger, der er mere eller mindre under udvikling - bogstaveligt talt kun én: explain.depesz.com af Hubert Lubaczewski. Når du indtaster "feed"-feltet en tekstrepræsentation af planen, viser den dig en tabel med de analyserede data:

  • nodens egen behandlingstid
  • samlet tid for hele undertræet
  • antal poster, der blev hentet, som var statistisk forventet
  • selve knudelegemet

Denne tjeneste har også mulighed for at dele et arkiv af links. Du smed din plan derind og sagde: "Hej, Vasya, her er et link, der er noget galt der."

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Men der er også små problemer.

For det første en enorm mængde "copy-paste". Du tager et stykke af bjælken, stikker det ind, og igen og igen.

Sekund, ingen analyse af mængden af ​​læst data — de samme buffere, som output EXPLAIN (ANALYZE, BUFFERS), vi ser det ikke her. Han ved simpelthen ikke, hvordan man skiller dem ad, forstår dem og arbejder med dem. Når du læser en masse data og indser, at du muligvis misallokerer disken og hukommelsescachen, er disse oplysninger meget vigtige.

Det tredje negative punkt er den meget svage udvikling af dette projekt. Commits er meget små, det er godt, hvis en gang hver sjette måned, og koden er i Perl.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Men dette er alt sammen "tekster", vi kunne på en eller anden måde leve med dette, men der er en ting, der i høj grad afviste os fra denne tjeneste. Disse er fejl i analysen af ​​Common Table Expression (CTE) og forskellige dynamiske noder som InitPlan/SubPlan.

Hvis du tror på dette billede, så er den samlede eksekveringstid for hver enkelt node større end den samlede eksekveringstid for hele anmodningen. Det er simpelt - generationstiden for denne CTE blev ikke trukket fra CTE Scan-knuden. Derfor kender vi ikke længere det rigtige svar på, hvor lang tid selve CTE-scanningen tog.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Så indså vi, at det var tid til at skrive vores eget – hurra! Hver udvikler siger: "Nu skriver vi vores eget, det bliver super nemt!"

Vi tog en stak typisk for webtjenester: en kerne baseret på Node.js + Express, brugte Bootstrap og D3.js til smukke diagrammer. Og vores forventninger var fuldt ud berettigede - vi modtog den første prototype på 2 uger:

  • tilpasset planparser
    Det vil sige, nu kan vi parse enhver plan fra dem, der er genereret af PostgreSQL.
  • korrekt analyse af dynamiske knudepunkter - CTE Scan, InitPlan, SubPlan
  • analyse af bufferfordeling - hvor datasider læses fra hukommelsen, hvor fra den lokale cache, hvor fra disk
  • fået klarhed
    For ikke at "grave" alt dette i loggen, men for at se det "svageste led" med det samme på billedet.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Vi fik noget som dette, med syntaksfremhævning inkluderet. Men normalt arbejder vores udviklere ikke længere med en komplet repræsentation af planen, men med en kortere. Vi har jo allerede parset alle tallene og smidt dem til venstre og højre, og i midten forlod vi kun den første linje, hvilken slags node det er: CTE Scan, CTE generation eller Seq Scan ifølge et eller andet tegn.

Dette er den forkortede fremstilling, vi kalder plan skabelon.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Hvad ville ellers være praktisk? Det ville være praktisk at se, hvor stor en del af vores samlede tid, der er allokeret til hvilken node - og bare "holde den" til siden lagkagediagram.

Vi peger på noden og ser - det viser sig, at Seq Scan tog mindre end en fjerdedel af den samlede tid, og de resterende 3/4 blev taget af CTE Scan. Rædsel! Dette er en lille note om "brandhastigheden" af CTE Scan, hvis du aktivt bruger dem i dine forespørgsler. De er ikke særlig hurtige - de er ringere end almindelig bordscanning. [artikel] [artikel]

Men normalt er sådanne diagrammer mere interessante, mere komplekse, når vi med det samme peger på et segment og for eksempel ser, at mere end halvdelen af ​​tiden nogle Seq Scan "spiste". Desuden var der en slags filter inde, en masse poster blev kasseret ifølge det... Du kan direkte smide dette billede til udvikleren og sige: "Vasya, alt er dårligt her for dig! Find ud af det, se - der er noget galt!"

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Naturligvis var der nogle "river" involveret.

Det første, vi stødte på, var afrundingsproblemet. Tidspunktet for hver enkelt knude i planen er angivet med en nøjagtighed på 1 μs. Og når antallet af nodecyklusser for eksempel overstiger 1000 - efter udførelse PostgreSQL divideret "inden for nøjagtighed", så får vi ved tilbageregning den samlede tid "et sted mellem 0.95ms og 1.05ms". Når optællingen går til mikrosekunder, er det okay, men når det allerede er [milli]sekunder, skal du tage disse oplysninger i betragtning, når du "løsner" ressourcer til noderne i "hvem forbrugte hvor meget"-planen.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Det andet punkt, mere komplekst, er fordelingen af ​​ressourcer (disse buffere) blandt dynamiske noder. Dette kostede os de første 2 uger af prototypen plus yderligere 4 uger.

Det er ret nemt at få denne slags problemer - vi laver en CTE og læser angiveligt noget i den. Faktisk er PostgreSQL "smart" og vil ikke læse noget direkte der. Så tager vi den første rekord fra den, og til den den hundrede og første fra den samme CTE.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Vi ser på planen og forstår - det er mærkeligt, vi har 3 buffere (datasider) "forbrugt" i Seq Scan, 1 mere i CTE Scan, og 2 mere i den anden CTE Scan. Det vil sige, at hvis vi blot opsummerer alt, får vi 6, men fra tabletten læser vi kun 3! CTE Scan læser ikke noget fra nogen steder, men arbejder direkte med proceshukommelsen. Det vil sige, at der er noget helt klart galt her!

Faktisk viser det sig, at her er alle de 3 sider med data, der blev anmodet om fra Seq Scan, først 1 bad om 1. CTE Scan, og derefter 2., og 2 mere blev læst for ham. Det vil sige i alt 3 sider blev læst data, ikke 6.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Og dette billede førte os til forståelsen af, at udførelsen af ​​en plan ikke længere er et træ, men blot en form for acyklisk graf. Og vi fik et diagram som dette, så vi forstår "hvad der kom fra hvor i første omgang." Det vil sige, at vi her oprettede en CTE fra pg_class, og bad om den to gange, og næsten al vores tid blev brugt på grenen, da vi bad om den 2. gang. Det er klart, at det er meget dyrere at læse det 101. opslag end blot at læse det 1. opslag fra tabletten.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Vi pustede ud et stykke tid. De sagde: "Nu, Neo, du kender kung fu! Nu er vores oplevelse lige på din skærm. Nu kan du bruge det." [artikel]

Log konsolidering

Vores 1000 udviklere åndede lettet op. Men vi forstod, at vi kun har hundredvis af "combat"-servere, og alt dette "copy-paste" fra udviklernes side er slet ikke praktisk. Vi indså, at vi var nødt til at samle det selv.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Generelt er der et standardmodul, der kan indsamle statistik, dog skal det også aktiveres i konfigurationen - denne modul pg_stat_statements. Men han passede os ikke.

For det første tildeler den de samme forespørgsler ved hjælp af forskellige skemaer i den samme database forskellige QueryIds. Altså hvis du først gør det SET search_path = '01'; SELECT * FROM user LIMIT 1;og derefter SET search_path = '02'; og samme anmodning, så vil statistikken for dette modul have forskellige registreringer, og jeg vil ikke være i stand til at indsamle generel statistik specifikt i forbindelse med denne anmodningsprofil, uden at tage hensyn til ordningerne.

Det andet punkt, der forhindrede os i at bruge det, er mangel på planer. Det vil sige, der er ingen plan, der er kun selve anmodningen. Vi ser, hvad der bremsede, men vi forstår ikke hvorfor. Og her vender vi tilbage til problemet med et datasæt i hastig forandring.

Og det sidste øjeblik - mangel på "fakta". Det vil sige, at du ikke kan adressere en bestemt forekomst af forespørgselsudførelse - der er ingen, der er kun aggregeret statistik. Selvom det er muligt at arbejde med dette, er det bare meget svært.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Derfor besluttede vi at bekæmpe copy-paste og begyndte at skrive solfanger.

Samleren forbinder via SSH, etablerer en sikker forbindelse til serveren med databasen ved hjælp af et certifikat, og tail -F "klamrer sig" til det i logfilen. Så i denne session vi får et komplet "spejl" af hele logfilen, som serveren genererer. Belastningen på selve serveren er minimal, fordi vi ikke analyserer noget der, vi spejler bare trafikken.

Da vi allerede var begyndt at skrive grænsefladen i Node.js, fortsatte vi med at skrive samleren i den. Og denne teknologi har retfærdiggjort sig selv, fordi det er meget praktisk at bruge JavaScript til at arbejde med svagt formaterede tekstdata, som er loggen. Og selve Node.js-infrastrukturen som backend-platform giver dig mulighed for nemt og bekvemt at arbejde med netværksforbindelser, og faktisk med alle datastrømme.

Derfor "strækker" vi to forbindelser: den første for at "lytte" til selve loggen og tage den til os selv, og den anden for med jævne mellemrum at spørge basen. "Men loggen viser, at tegnet med oid 123 er blokeret," men dette betyder ikke noget for udvikleren, og det ville være rart at spørge databasen, "Hvad er OID = 123 alligevel?" Og derfor spørger vi med jævne mellemrum basen, hvad vi endnu ikke ved om os selv.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

"Der er kun én ting, du ikke tog højde for, der er en art af elefantlignende bier!.." Vi begyndte at udvikle dette system, da vi ville overvåge 10 servere. Det mest kritiske i vores forståelse, hvor der opstod nogle problemer, som var svære at håndtere. Men i løbet af det første kvartal modtog vi hundrede til overvågning - fordi systemet virkede, alle ville have det, alle var godt tilpas.

Alt dette skal lægges sammen, datastrømmen er stor og aktiv. Faktisk er det, vi overvåger, hvad vi kan håndtere, hvad vi bruger. Vi bruger også PostgreSQL som datalager. Og intet er hurtigere at "hælde" data ind i det end operatøren COPY Ikke endnu.

Men blot at "hælde" data er ikke rigtig vores teknologi. For hvis du har cirka 50 anmodninger i sekundet på hundrede servere, vil dette generere 100-150 GB logfiler om dagen. Derfor var vi nødt til forsigtigt at "skære" basen.

For det første gjorde vi det opdeling efter dag, fordi der stort set ikke er nogen, der interesserer sig for sammenhængen mellem dage. Hvilken forskel gør det, hvad du havde i går, hvis du i aften rullede en ny version af applikationen ud - og allerede nogle nye statistikker.

For det andet lærte vi (blev tvunget) meget, meget hurtig at skrive ved hjælp af COPY. Altså ikke kun COPYfordi han er hurtigere end INSERT, og endnu hurtigere.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Det tredje punkt - jeg var nødt til det opgive henholdsvis triggere og fremmednøgler. Det vil sige, at vi slet ikke har nogen referencemæssig integritet. For hvis du har en tabel, der har et par FK'er, og du siger i databasestrukturen, at "her er en logpost, som f.eks. refereres af FK til en gruppe af poster," så når du indsætter den, vil PostgreSQL har ikke andet tilbage end hvordan man tager det og gør det ærligt SELECT 1 FROM master_fk1_table WHERE ... med den identifikator, som du forsøger at indsætte - bare for at kontrollere, at denne post er til stede der, at du ikke "brækker" denne fremmednøgle med din indsættelse.

I stedet for én post til måltabellen og dens indekser, får vi den ekstra fordel ved at læse fra alle de tabeller, den refererer til. Men vi har slet ikke brug for dette - vores opgave er at optage så meget som muligt og så hurtigt som muligt med den mindste belastning. Så FK - ned!

Det næste punkt er aggregering og hashing. I første omgang implementerede vi dem i databasen - det er trods alt praktisk at straks, når der kommer en post, gøre det på en form for tablet "plus en" lige i aftrækkeren. Nå, det er praktisk, men det samme dårlige - du indsætter en post, men er tvunget til at læse og skrive noget andet fra en anden tabel. Desuden læser og skriver du ikke kun, du gør det også hver gang.

Forestil dig nu, at du har en tabel, hvor du blot tæller antallet af anmodninger, der er gået gennem en bestemt vært: +1, +1, +1, ..., +1. Og du har i princippet ikke brug for dette - det er alt muligt sum i hukommelsen på samleren og send til databasen på én gang +10.

Ja, i tilfælde af nogle problemer kan din logiske integritet "falde fra hinanden", men dette er et næsten urealistisk tilfælde - fordi du har en normal server, den har et batteri i controlleren, du har en transaktionslog, en log på filsystem... Generelt er det ikke det værd. Det tab af produktivitet, du får ved at køre triggere/FK, er ikke den udgift værd, du pådrager dig.

Det er det samme med hashing. En bestemt anmodning flyver til dig, du beregner en bestemt identifikator ud fra den i databasen, skriver den til databasen og fortæller den så til alle. Alt er fint, indtil der på optagelsestidspunktet kommer en anden person til dig, som vil optage det samme - og du bliver blokeret, og det er allerede slemt. Derfor, hvis du kan overføre genereringen af ​​nogle ID'er til klienten (i forhold til databasen), er det bedre at gøre dette.

Det var bare perfekt for os at bruge MD5 fra teksten - forespørgsel, plan, skabelon,... Vi beregner det på samlersiden, og "hælder" det færdige ID ind i databasen. Længden af ​​MD5 og den daglige partitionering giver os mulighed for ikke at bekymre os om mulige kollisioner.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Men for at optage alt dette hurtigt, var vi nødt til at ændre selve optagelsesproceduren.

Hvordan plejer du at skrive data? Vi har en form for datasæt, vi opdeler det i flere tabeller, og så KOPIERER det - først til den første, så ind i den anden, ind i den tredje... Det er ubelejligt, fordi vi ser ud til at skrive en datastrøm i tre trin sekventielt. Ubehagelig. Kan det gøres hurtigere? Kan!

For at gøre dette er det nok bare at nedbryde disse strømme parallelt med hinanden. Det viser sig, at vi har fejl, anmodninger, skabeloner, blokeringer, ... flyver i separate tråde - og vi skriver det hele parallelt. Nok til dette holde en COPY-kanal konstant åben for hver enkelt måltabel.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Altså hos samleren der er altid en strøm, hvor jeg kan skrive de data, jeg har brug for. Men så databasen ser disse data, og nogen ikke går i stå og venter på, at disse data bliver skrevet, KOPIEN skal afbrydes med visse intervaller. For os var den mest effektive periode omkring 100ms - vi lukker den og åbner den straks igen til samme bord. Og hvis vi ikke har nok af ét flow under nogle peaks, så laver vi pooling op til en vis grænse.

Derudover fandt vi ud af, at for en sådan belastningsprofil er enhver aggregering, når poster indsamles i partier, ond. Klassisk ondskab er INSERT ... VALUES og yderligere 1000 poster. For på det tidspunkt har du et skrivepeak på mediet, og alle andre, der prøver at skrive noget til disken, venter.

For at slippe af med sådanne anomalier skal du simpelthen ikke samle noget, buffer slet ikke. Og hvis buffering til disk forekommer (heldigvis giver Stream API'en i Node.js dig mulighed for at finde ud af det) - udsæt denne forbindelse. Når du modtager en begivenhed, at den er gratis igen, så skriv til den fra den akkumulerede kø. Og mens der er travlt, så tag den næste ledige fra poolen og skriv til den.

Før vi introducerede denne tilgang til dataoptagelse, havde vi cirka 4K skriveoperationer, og på denne måde reducerede vi belastningen med 4 gange. Nu er de vokset yderligere 6 gange på grund af nye overvågede databaser - op til 100MB/s. Og nu gemmer vi logfiler for de sidste 3 måneder i et volumen på omkring 10-15TB, i håb om, at enhver udvikler på bare tre måneder vil være i stand til at løse ethvert problem.

Vi forstår problemerne

Men blot at indsamle alle disse data er godt, nyttigt, relevant, men ikke nok - det skal forstås. Fordi det er millioner af forskellige planer om dagen.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Men millioner er uoverskuelige, vi skal først gøre "mindre". Og først og fremmest skal du beslutte, hvordan du vil organisere denne "mindre" ting.

Vi har identificeret tre hovedpunkter:

  • der har sendt denne anmodning
    Det vil sige, fra hvilken applikation "kom" den: webgrænseflade, backend, betalingssystem eller noget andet.
  • где det skete
    På hvilken specifik server? For hvis du har flere servere under en applikation, og pludselig "går dum" (fordi "disken er rådden", "hukommelse lækket", et andet problem), så skal du specifikt adressere serveren.
  • som problemet viste sig på den ene eller anden måde

For at forstå "hvem" har sendt os en anmodning, bruger vi et standardværktøj - indstilling af en sessionsvariabel: SET application_name = '{bl-host}:{bl-method}'; — vi sender navnet på den forretningslogikvært, som anmodningen kommer fra, og navnet på den metode eller applikation, der startede den.

Efter at vi har passeret "ejeren" af anmodningen, skal den udsendes til loggen - til dette konfigurerer vi variablen log_line_prefix = ' %m [%p:%v] [%d] %r %a'. For de interesserede, måske se i manualenhvad betyder det hele. Det viser sig, at vi ser i loggen:

  • tid
  • proces- og transaktionsidentifikatorer
  • databasenavn
  • IP på den person, der sendte denne anmodning
  • og metodenavn

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Så indså vi, at det ikke er særlig interessant at se på sammenhængen for en anmodning mellem forskellige servere. Det er ikke ofte, du har en situation, hvor én ansøgning skruer lige meget op her og der. Men selvom det er det samme, så se på en af ​​disse servere.

Så her er snittet "én server - en dag" det viste sig at være nok for os til enhver analyse.

Det første analytiske afsnit er det samme "prøve" - en forkortet form for præsentation af planen, renset for alle numeriske indikatorer. Det andet snit er applikationen eller metoden, og det tredje snit er den specifikke planknude, der gav os problemer.

Da vi gik fra specifikke instanser til skabeloner, fik vi to fordele på én gang:

  • multipel reduktion i antallet af objekter til analyse
    Vi skal ikke længere analysere problemet ved hjælp af tusindvis af forespørgsler eller planer, men ved dusinvis af skabeloner.
  • tidslinje
    Det vil sige, ved at opsummere "fakta" inden for et bestemt afsnit, kan du vise deres udseende i løbet af dagen. Og her kan man forstå, at hvis man har et eller andet mønster, der fx sker én gang i timen, men det skal ske én gang om dagen, så skal man tænke over, hvad der gik galt – hvem der forårsagede det og hvorfor, måske skulle det være her burde ikke. Dette er en anden ikke-numerisk, rent visuel analysemetode.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

De resterende metoder er baseret på de indikatorer, som vi uddrager fra planen: hvor mange gange et sådant mønster opstod, den samlede og gennemsnitlige tid, hvor meget data der blev læst fra disken, og hvor meget fra hukommelsen...

Fordi du for eksempel kommer til analysesiden for værten, se - noget begynder at læse for meget på disken. Disken på serveren kan ikke klare det – hvem læser fra den?

Og du kan sortere efter en hvilken som helst kolonne og beslutte, hvad du vil beskæftige dig med lige nu - belastningen på processoren eller disken, eller det samlede antal anmodninger... Vi sorterede det, så på de "øverste", rettede det og udrullet en ny version af applikationen.
[videoforedrag]

Og med det samme kan du se forskellige applikationer, der kommer med den samme skabelon fra en anmodning som SELECT * FROM users WHERE login = 'Vasya'. Frontend, backend, behandling... Og du undrer dig over, hvorfor behandling ville læse brugeren, hvis han ikke interagerer med ham.

Den modsatte måde er straks at se fra applikationen, hvad den gør. For eksempel er frontenden dette, dette, dette og dette en gang i timen (tidslinjen hjælper). Og spørgsmålet melder sig straks: det ser ud til, at det ikke er frontendens opgave at gøre noget en gang i timen...

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Efter nogen tid indså vi, at vi manglede samlet statistik efter planknudepunkter. Vi isolerede fra planerne kun de noder, der gør noget med dataene i selve tabellerne (læs/skriv dem efter indeks eller ej). Faktisk er der kun tilføjet et aspekt i forhold til det forrige billede - hvor mange poster bragte denne node os?, og hvor mange der blev kasseret (rækker fjernet med filter).

Du har ikke et passende indeks på pladen, du laver en forespørgsel til det, det flyver forbi indekset, falder ind i Seq Scan... du har filtreret alle poster fra undtagen én. Hvorfor har du brug for 100 millioner filtrerede poster om dagen? Er det ikke bedre at rulle indekset op?

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Efter at have analyseret alle planerne node for node, indså vi, at der er nogle typiske strukturer i planerne, som med stor sandsynlighed vil se mistænkelige ud. Og det ville være rart at fortælle udvikleren: "Ven, her læser du først efter indeks, sorterer og skærer derefter af" - som regel er der en post.

Alle, der skrev forespørgsler, har sandsynligvis stødt på dette mønster: "Giv mig den sidste ordre for Vasya, dens dato." Og hvis du ikke har et indeks efter dato, eller der ikke er nogen dato i det indeks, du brugte, så vil du træde på nøjagtig samme "rive" .

Men vi ved, at dette er en "rake" - så hvorfor ikke straks fortælle udvikleren, hvad han skal gøre. Derfor, når vi åbner en plan nu, ser vores udvikler straks et smukt billede med tips, hvor de straks fortæller ham: "Du har problemer her og der, men de er løst på den og den måde."

Som følge heraf er mængden af ​​erfaring, der var nødvendig for at løse problemer i begyndelsen og nu, faldet markant. Det er den slags værktøj, vi har.

Masseoptimering af PostgreSQL-forespørgsler. Kirill Borovikov (Tensor)

Kilde: www.habr.com

Tilføj en kommentar