Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Rapporten presenterer noen tilnærminger som tillater overvåke ytelsen til SQL-spørringer når det er millioner av dem per dag, og det er hundrevis av overvåkede PostgreSQL-servere.

Hvilke tekniske løsninger gjør at vi effektivt kan behandle en slik mengde informasjon, og hvordan gjør dette livet til en vanlig utvikler enklere?


Hvem er interessert? analyse av spesifikke problemer og ulike optimaliseringsteknikker SQL-spørringer og løsning av typiske DBA-problemer i PostgreSQL - du kan også lese en serie artikler om dette emnet.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)
Jeg heter Kirill Borovikov, jeg representerer Tensor selskap. Konkret har jeg spesialisert meg på å jobbe med databaser i vårt selskap.

I dag skal jeg fortelle deg hvordan vi optimaliserer spørringer, når du ikke trenger å "plukke fra hverandre" ytelsen til en enkelt spørring, men løse problemet massevis. Når det er millioner av forespørsler, og du må finne noen tilnærminger til løsning dette store problemet.

Generelt er Tensor for en million av våre kunder VLSI er vår applikasjon: bedriftssosiale nettverk, løsninger for videokommunikasjon, for intern og ekstern dokumentflyt, regnskapssystemer for regnskap og varehus,... Det vil si en slik "mega-kombinasjon" for integrert virksomhetsstyring, der det er mer enn 100 forskjellige interne prosjekter.

For å sikre at de alle fungerer og utvikler seg normalt, har vi 10 utviklingssentre over hele landet, med flere i dem 1000 utviklere.

Vi har jobbet med PostgreSQL siden 2008 og har samlet en stor mengde av det vi behandler - klientdata, statistisk, analytisk, data fra eksterne informasjonssystemer - mer enn 400TB. Det er ca 250 servere i produksjon alene, og totalt er det ca 1000 databaseservere som vi overvåker.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

SQL er et deklarativt språk. Du beskriver ikke "hvordan" noe skal fungere, men "hva" du ønsker å oppnå. DBMS vet bedre hvordan man lager en JOIN - hvordan man kobler sammen tabellene dine, hvilke betingelser som skal pålegges, hva vil gå gjennom indeksen, hva vil ikke ...

Noen DBMS-er godtar hint: "Nei, koble disse to tabellene i en slik og en slik kø," men PostgreSQL kan ikke gjøre dette. Dette er den bevisste posisjonen til ledende utviklere: "Vi vil heller fullføre spørringsoptimereren enn å la utviklere bruke noen form for hint."

Men til tross for at PostgreSQL ikke lar "utsiden" kontrollere seg selv, tillater det perfekt se hva som skjer inni hamnår du kjører søket ditt, og hvor det har problemer.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Generelt, hvilke klassiske problemer kommer vanligvis en utvikler [til en DBA] med? «Her oppfylte vi forespørselen, og alt går sakte med oss, alt henger, noe skjer... En slags trøbbel!»

Årsakene er nesten alltid de samme:

  • ineffektiv spørringsalgoritme
    Utvikler: "Nå gir jeg ham 10 tabeller i SQL via JOIN..." - og forventer at forholdene hans på mirakuløst vis vil bli effektivt "ubundet" og han vil få alt raskt. Men mirakler skjer ikke, og ethvert system med slik variabilitet (10 tabeller i en FROM) gir alltid en slags feil. [artikkel]
  • irrelevant statistikk
    Dette punktet er veldig relevant spesielt for PostgreSQL, når du "hellte" et stort datasett på serveren, gjør en forespørsel, og det "sexcaniterer" nettbrettet ditt. For i går var det 10 poster i den, og i dag er det 10 millioner, men PostgreSQL er ennå ikke klar over dette, og vi må fortelle det om det. [artikkel]
  • "plugg" på ressurser
    Du har installert en stor og tungt lastet database på en svak server som ikke har nok disk-, minne- eller prosessorytelse. Og det er alt... Et sted er det et ytelsestak som du ikke lenger kan hoppe over.
  • blokkering
    Dette er et vanskelig punkt, men de er mest relevante for ulike endringsspørringer (INSERT, UPDATE, DELETE) - dette er et eget stort emne.

Får en plan

...Og for alt annet vi trenger en plan! Vi må se hva som skjer inne på serveren.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

En plan for utførelse av spørringer for PostgreSQL er et tre av spørringsutførelsesalgoritmen i tekstrepresentasjon. Det er nettopp algoritmen som, som et resultat av analyser fra planleggeren, ble funnet å være den mest effektive.

Hver trenode er en operasjon: hente data fra en tabell eller indeks, bygge en punktgrafikk, slå sammen to tabeller, slå sammen, krysse eller ekskludere utvalg. Å utføre en spørring innebærer å gå gjennom nodene til dette treet.

For å få spørringsplanen er den enkleste måten å utføre setningen EXPLAIN. For å få med alle virkelige attributter, det vil si å faktisk utføre en spørring på basen - EXPLAIN (ANALYZE, BUFFERS) SELECT ....

Den dårlige delen: når du kjører det, skjer det "her og nå", så det er kun egnet for lokal feilsøking. Hvis du tar en høyt lastet server som er under en sterk flyt av dataendringer, og du ser: "Oh! Her har vi en treg utførelseXia be om." For en halv time, en time siden - mens du kjørte og hentet denne forespørselen fra loggene, og førte den tilbake til serveren, endret hele datasettet og statistikken. Du kjører den for å feilsøke - og den kjører raskt! Og du kan ikke forstå hvorfor, hvorfor det var sakte.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

For å forstå hva som skjedde nøyaktig i øyeblikket da forespørselen ble utført på serveren, skrev smarte folk auto_explain-modul. Den finnes i nesten alle de vanligste PostgreSQL-distribusjonene, og kan ganske enkelt aktiveres i konfigurasjonsfilen.

Hvis den innser at en forespørsel kjører lenger enn grensen du sa til den, gjør den det "øyeblikksbilde" av planen for denne forespørselen og skriver dem sammen i loggen.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Alt ser ut til å være i orden nå, vi går til loggen og ser der... [tekstfotduk]. Men vi kan ikke si noe om det, annet enn det faktum at det er en utmerket plan fordi det tok 11 ms å utføre.

Alt ser ut til å være i orden - men ingenting er klart hva som faktisk skjedde. Bortsett fra den generelle tiden, ser vi egentlig ingenting. Fordi det vanligvis ikke er visuelt å se på et slikt «lam» med ren tekst.

Men selv om det ikke er åpenbart, selv om det er upraktisk, er det mer grunnleggende problemer:

  • Noden indikerer summen av ressursene til hele undertreet under ham. Det vil si at du ikke bare kan finne ut hvor mye tid som ble brukt på denne spesielle indeksskanningen hvis det er en nestet tilstand under den. Vi må dynamisk se for å se om det er "barn" og betingede variabler, CTE-er inni - og trekke fra alt dette "i våre sinn".
  • Andre punkt: tiden som er angitt på noden er enkelt node utførelsestid. Hvis denne noden ble utført som et resultat av for eksempel en sløyfe gjennom tabellposter flere ganger, øker antallet løkker – sykluser til denne noden – i planen. Men selve atomutførelsestiden forblir den samme når det gjelder plan. Det vil si, for å forstå hvor lenge denne noden ble utført totalt, må du multiplisere en ting med en annen - igjen, "i hodet ditt."

I slike situasjoner, forstå "Hvem er det svakeste leddet?" nesten umulig. Derfor skriver til og med utviklerne selv i "manualen" at "Å forstå en plan er en kunst som må læres, oppleve ...".

Men vi har 1000 utviklere, og du kan ikke formidle denne opplevelsen til hver av dem. Jeg, du, han vet, men noen der borte vet ikke lenger. Kanskje han vil lære, eller kanskje ikke, men han må jobbe nå - og hvor skulle han få denne erfaringen?

Planlegg visualisering

Derfor innså vi at for å håndtere disse problemene, trenger vi god visualisering av planen. [artikkel]

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Vi gikk først "gjennom markedet" - la oss se på Internett for å se hva som til og med eksisterer.

Men det viste seg at det er svært få relativt "levende" løsninger som er mer eller mindre i utvikling - bokstavelig talt bare én: explain.depesz.com av Hubert Lubaczewski. Når du skriver inn "feed"-feltet en tekstrepresentasjon av planen, viser den deg en tabell med de analyserte dataene:

  • nodens egen behandlingstid
  • total tid for hele undertreet
  • antall poster som ble hentet som var statistisk forventet
  • selve nodekroppen

Denne tjenesten har også muligheten til å dele et arkiv med lenker. Du kastet planen din inn og sa: "Hei, Vasya, her er en lenke, det er noe galt der."

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Men det er også små problemer.

For det første en enorm mengde "copy-paste". Du tar en bit av stokken, stikker den inn der, og igjen, og igjen.

Sekund, ingen analyse av mengden data som er lest — de samme bufferne som gir ut EXPLAIN (ANALYZE, BUFFERS), vi ser det ikke her. Han vet rett og slett ikke hvordan han skal demontere dem, forstå dem og jobbe med dem. Når du leser mye data og innser at du kanskje feilallokerer disken og minnebufferen, er denne informasjonen veldig viktig.

Det tredje negative punktet er den svært svake utviklingen av dette prosjektet. Forpliktelsene er veldig små, det er bra om det er en gang hver sjette måned, og koden er i Perl.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Men alt dette er "tekster", vi kunne på en eller annen måte leve med dette, men det er en ting som i stor grad vendte oss bort fra denne tjenesten. Dette er feil i analysen av Common Table Expression (CTE) og ulike dynamiske noder som InitPlan/SubPlan.

Hvis du tror på dette bildet, er den totale utførelsestiden for hver enkelt node større enn den totale utførelsestiden for hele forespørselen. Det er enkelt - generasjonstiden for denne CTE ble ikke trukket fra CTE Scan-noden. Derfor vet vi ikke lenger det riktige svaret på hvor lang tid selve CTE-skanningen tok.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Da skjønte vi at det var på tide å skrive vår egen – hurra! Hver utvikler sier: "Nå skal vi skrive vår egen, det blir superenkelt!"

Vi tok en stabel som er typisk for webtjenester: en kjerne basert på Node.js + Express, brukte Bootstrap og D3.js for vakre diagrammer. Og forventningene våre var fullt ut berettiget - vi mottok den første prototypen på 2 uker:

  • tilpasset planparser
    Det vil si at nå kan vi analysere enhver plan fra de som er generert av PostgreSQL.
  • korrekt analyse av dynamiske noder - CTE Scan, InitPlan, SubPlan
  • analyse av bufferdistribusjon - hvor datasider leses fra minnet, hvor fra den lokale cachen, hvor fra disken
  • fikk klarhet
    For ikke å "grave" alt dette i loggen, men for å se den "svakeste lenken" med en gang i bildet.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Vi har noe sånt som dette, med syntaksutheving inkludert. Men vanligvis jobber ikke utviklerne våre lenger med en fullstendig representasjon av planen, men med en kortere. Tross alt har vi allerede analysert alle tallene og kastet dem til venstre og høyre, og i midten forlot vi bare den første linjen, hva slags node det er: CTE Scan, CTE generasjon eller Seq Scan i henhold til et eller annet tegn.

Dette er den forkortede representasjonen vi kaller planmal.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Hva annet ville være praktisk? Det ville være praktisk å se hvilken andel av vår totale tid som er allokert til hvilken node - og bare "feste den" til siden Kake diagram.

Vi peker på noden og ser - det viser seg at Seq Scan tok mindre enn en fjerdedel av den totale tiden, og de resterende 3/4 ble tatt av CTE Scan. Skrekk! Dette er en liten merknad om "brannhastigheten" til CTE Scan hvis du aktivt bruker dem i spørsmålene dine. De er ikke veldig raske - de er dårligere selv til vanlig bordskanning. [artikkel] [artikkel]

Men vanligvis er slike diagrammer mer interessante, mer komplekse, når vi umiddelbart peker på et segment og ser for eksempel at mer enn halvparten av tiden noen Seq Scan "spist". Dessuten var det et slags filter inni, mange poster ble forkastet i henhold til det... Du kan direkte kaste dette bildet til utvikleren og si: "Vasya, alt er dårlig her for deg! Finn ut av det, se - noe er galt!"

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Naturligvis var det noen "raker" involvert.

Det første vi kom over var avrundingsproblemet. Tidspunktet for hver enkelt node i planen er angitt med en nøyaktighet på 1 μs. Og når antallet nodesykluser overstiger for eksempel 1000 - etter utførelse PostgreSQL delt "innenfor nøyaktighet", så får vi ved tilbakeregning den totale tiden "et sted mellom 0.95ms og 1.05ms". Når tellingen går til mikrosekunder, er det greit, men når det allerede er [milli]sekunder, må du ta hensyn til denne informasjonen når du "løser" ressurser til nodene til "hvem konsumerte hvor mye"-planen.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Det andre punktet, mer komplekst, er fordelingen av ressurser (disse bufferne) mellom dynamiske noder. Dette kostet oss de første 2 ukene av prototypen pluss ytterligere 4 uker.

Det er ganske lett å få denne typen problemer - vi gjør en CTE og skal visstnok lese noe i den. Faktisk er PostgreSQL "smart" og vil ikke lese noe direkte der. Så tar vi den første posten fra den, og til den den hundre og første fra samme CTE.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Vi ser på planen og forstår - det er rart, vi har 3 buffere (datasider) "konsumert" i Seq Scan, 1 til i CTE Scan, og 2 til i den andre CTE Scan. Det vil si at hvis vi bare summerer alt, får vi 6, men fra nettbrettet leser vi bare 3! CTE Scan leser ikke noe fra hvor som helst, men fungerer direkte med prosessminnet. Det vil si at noe er helt klart galt her!

Faktisk viser det seg at her er alle de 3 sidene med data som ble forespurt fra Seq Scan, først 1 ba om 1. CTE Scan, og deretter den 2. og 2 til ble lest for ham. Det vil si totalt 3 sider ble lest data, ikke 6.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Og dette bildet førte oss til forståelsen av at gjennomføringen av en plan ikke lenger er et tre, men bare en slags asyklisk graf. Og vi fikk et diagram som dette, slik at vi forstår "hva som kom fra hvor i utgangspunktet." Det vil si at her har vi laget en CTE fra pg_class, og spurt om den to ganger, og nesten all vår tid ble brukt på grenen da vi spurte om den 2. gang. Det er tydelig at det er mye dyrere å lese den 101. oppføringen enn å bare lese den 1. oppføringen fra nettbrettet.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Vi pustet ut en stund. De sa: «Nå, Neo, du vet kung fu! Nå er opplevelsen vår rett på skjermen din. Nå kan du bruke den." [artikkel]

Loggkonsolidering

Våre 1000 utviklere pustet lettet ut. Men vi forsto at vi bare har hundrevis av "combat"-servere, og all denne "copy-paste" fra utviklernes side er slett ikke praktisk. Vi innså at vi måtte samle den selv.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Generelt er det en standardmodul som kan samle inn statistikk, men den må også aktiveres i konfigurasjonen - denne modul pg_stat_statements. Men han passet ikke oss.

For det første tilordner den til de samme spørringene ved å bruke forskjellige skjemaer i samme database forskjellige QueryIds. Det vil si hvis du først gjør det SET search_path = '01'; SELECT * FROM user LIMIT 1;og da SET search_path = '02'; og samme forespørsel, vil statistikken til denne modulen ha forskjellige poster, og jeg vil ikke kunne samle generell statistikk spesifikt i sammenheng med denne forespørselsprofilen, uten å ta hensyn til ordningene.

Det andre punktet som hindret oss i å bruke det er mangel på planer. Det vil si at det ikke er noen plan, det er bare selve forespørselen. Vi ser hva som bremset opp, men vi forstår ikke hvorfor. Og her kommer vi tilbake til problemet med et datasett i rask endring.

Og det siste øyeblikket - mangel på "fakta". Det vil si at du ikke kan adressere en spesifikk forekomst av spørringskjøring - det er ingen, det er bare aggregert statistikk. Selv om det er mulig å jobbe med dette, er det bare veldig vanskelig.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Derfor bestemte vi oss for å kjempe mot copy-paste og begynte å skrive kollektor.

Samleren kobler seg til via SSH, etablerer en sikker forbindelse til serveren med databasen ved hjelp av et sertifikat, og tail -F "klamrer seg" til den i loggfilen. Så i denne økten vi får et komplett "speil" av hele loggfilen, som serveren genererer. Belastningen på selve serveren er minimal, fordi vi ikke analyserer noe der, vi speiler bare trafikken.

Siden vi allerede hadde begynt å skrive grensesnittet i Node.js, fortsatte vi å skrive samleren i det. Og denne teknologien har rettferdiggjort seg selv, fordi det er veldig praktisk å bruke JavaScript for å jobbe med svakt formaterte tekstdata, som er loggen. Og selve Node.js-infrastrukturen som en backend-plattform lar deg enkelt og bekvemt jobbe med nettverkstilkoblinger, og faktisk med alle datastrømmer.

Følgelig "strekker" vi to forbindelser: den første for å "lytte" til selve loggen og ta den til oss selv, og den andre for å spørre basen med jevne mellomrom. "Men loggen viser at skiltet med oid 123 er blokkert," men dette betyr ikke noe for utvikleren, og det ville være fint å spørre databasen, "Hva er OID = 123 likevel?" Og så spør vi med jevne mellomrom basen hva vi ennå ikke vet om oss selv.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

"Det er bare én ting du ikke tok i betraktning, det er en art av elefantlignende bier!..." Vi begynte å utvikle dette systemet da vi ønsket å overvåke 10 servere. Det mest kritiske i vår forståelse, hvor det oppsto noen problemer som var vanskelige å håndtere. Men i løpet av første kvartal fikk vi hundre til overvåking – fordi systemet fungerte, alle ville ha det, alle var komfortable.

Alt dette må legges sammen, dataflyten er stor og aktiv. Det vi overvåker, hva vi kan håndtere, er faktisk det vi bruker. Vi bruker også PostgreSQL som datalagring. Og ingenting er raskere å "helle" data inn i den enn operatøren COPY Ikke ennå.

Men det å "helle" data er egentlig ikke vår teknologi. For hvis du har omtrent 50 100 forespørsler per sekund på hundre servere, vil dette generere 150-XNUMX GB logger per dag. Derfor måtte vi forsiktig "kutte" basen.

For det første gjorde vi det oppdeling etter dag, fordi det stort sett ikke er noen som er interessert i sammenhengen mellom dager. Hvilken forskjell gjør det hva du hadde i går, hvis du i kveld rullet ut en ny versjon av applikasjonen - og allerede litt ny statistikk.

For det andre lærte vi (ble tvunget) veldig, veldig rask å skrive med COPY. Det vil si, ikke bare COPYfordi han er raskere enn INSERT, og enda raskere.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Det tredje punktet - jeg måtte forlate utløsere, henholdsvis, og fremmednøkler. Det vil si at vi ikke har noen referanseintegritet i det hele tatt. For hvis du har en tabell som har et par FK-er, og du sier i databasestrukturen at "her er en loggpost som er referert av FK, for eksempel til en gruppe poster," så når du setter den inn, PostgreSQL har ingenting igjen enn hvordan man tar det og gjør det ærlig SELECT 1 FROM master_fk1_table WHERE ... med identifikatoren du prøver å sette inn - bare for å sjekke at denne posten finnes der, at du ikke "bryter av" denne fremmednøkkelen med innsettingen.

I stedet for én post til måltabellen og dens indekser, får vi den ekstra fordelen av å lese fra alle tabellene den refererer til. Men vi trenger ikke dette i det hele tatt - vår oppgave er å registrere så mye som mulig og så raskt som mulig med minst mulig belastning. Så FK - ned!

Det neste punktet er aggregering og hashing. Til å begynne med implementerte vi dem i databasen - tross alt er det praktisk å umiddelbart, når en post kommer, gjøre det på en slags nettbrett "pluss en" rett i avtrekkeren. Vel, det er praktisk, men det samme dårlige - du setter inn en post, men blir tvunget til å lese og skrive noe annet fra en annen tabell. Dessuten leser og skriver du ikke bare, du gjør det også hver gang.

Tenk deg nå at du har en tabell der du ganske enkelt teller antall forespørsler som har gått gjennom en bestemt vert: +1, +1, +1, ..., +1. Og du trenger i prinsippet ikke dette - det er alt mulig sum i minnet på samleren og send til databasen på én gang +10.

Ja, i tilfelle noen problemer, kan din logiske integritet "falle fra hverandre", men dette er et nesten urealistisk tilfelle - fordi du har en vanlig server, den har et batteri i kontrolleren, du har en transaksjonslogg, en logg på filsystem... Generelt sett er det ikke verdt det. Tapet av produktivitet du får av å kjøre triggere/FK er ikke verdt utgiftene du pådrar deg.

Det er det samme med hashing. En viss forespørsel flyr til deg, du beregner en bestemt identifikator fra den i databasen, skriver den til databasen og forteller den til alle. Alt er bra helt til det på opptakstidspunktet kommer en annen person til deg som vil spille inn det samme - og du blir blokkert, og dette er allerede dårlig. Derfor, hvis du kan overføre genereringen av noen IDer til klienten (i forhold til databasen), er det bedre å gjøre dette.

Det var perfekt for oss å bruke MD5 fra teksten - forespørsel, plan, mal,... Vi beregner det på samlersiden, og "heller" den ferdiglagde IDen inn i databasen. Lengden på MD5 og daglig partisjonering lar oss ikke bekymre oss for mulige kollisjoner.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Men for å ta opp alt dette raskt, trengte vi å endre selve opptaksprosedyren.

Hvordan skriver du vanligvis data? Vi har en slags datasett, vi deler det inn i flere tabeller, og så KOPIERER det - først inn i den første, så inn i den andre, inn i den tredje... Det er upraktisk, fordi vi ser ut til å skrive en datastrøm i tre trinn sekvensielt. Ubehagelig. Kan det gjøres raskere? Kan!

For å gjøre dette er det nok bare å dekomponere disse strømmene parallelt med hverandre. Det viser seg at vi har feil, forespørsler, maler, blokkeringer, ... som flyr i separate tråder - og vi skriver det hele parallelt. Nok for dette hold en COPY-kanal konstant åpen for hver enkelt måltabell.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Altså hos samleren det er alltid en bekk, der jeg kan skrive dataene jeg trenger. Men slik at databasen ser disse dataene, og noen ikke blir sittende fast og venter på at disse dataene skal skrives, KOPIEN må avbrytes med visse intervaller. For oss var den mest effektive perioden omtrent 100 ms - vi lukker den og åpner den umiddelbart igjen til samme bord. Og hvis vi ikke har nok av én flyt under noen topper, så samler vi opp til en viss grense.

I tillegg fant vi ut at for en slik lastprofil er enhver aggregering, når poster samles i partier, ond. Klassisk ondskap er INSERT ... VALUES og ytterligere 1000 poster. For på det tidspunktet har du en skrivetopp på media, og alle andre som prøver å skrive noe til disken vil vente.

For å bli kvitt slike uregelmessigheter, bare ikke samle noe, ikke buffer i det hele tatt. Og hvis bufring til disk forekommer (heldigvis lar Stream API i Node.js deg finne ut av det) - utsett denne tilkoblingen. Når du mottar en hendelse om at den er gratis igjen, skriv til den fra den akkumulerte køen. Og mens det er travelt, ta den neste ledige fra bassenget og skriv til den.

Før vi introduserte denne tilnærmingen til dataregistrering, hadde vi omtrent 4K skriveoperasjoner, og på denne måten reduserte vi belastningen med 4 ganger. Nå har de vokst ytterligere 6 ganger på grunn av nye overvåkede databaser - opptil 100MB/s. Og nå lagrer vi logger for de siste 3 månedene i et volum på ca. 10-15 TB, i håp om at i løpet av bare tre måneder vil enhver utvikler være i stand til å løse ethvert problem.

Vi forstår problemene

Men bare å samle inn alle disse dataene er bra, nyttig, relevant, men ikke nok - det må forstås. Fordi dette er millioner av forskjellige planer per dag.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Men millioner er uhåndterlige, vi må først gjøre «mindre». Og først av alt må du bestemme hvordan du vil organisere denne "mindre" tingen.

Vi har identifisert tre hovedpunkter:

  • som sendte denne forespørselen
    Det vil si fra hvilken applikasjon "kom" den: webgrensesnitt, backend, betalingssystem eller noe annet.
  • der det skjedde
    På hvilken spesifikk server? For hvis du har flere servere under en applikasjon, og plutselig en "blir dum" (fordi "disken er råtten", "minnet lekket", et annet problem), så må du spesifikt adressere serveren.
  • som problemet manifesterte seg på en eller annen måte

For å forstå "hvem" som har sendt oss en forespørsel, bruker vi et standardverktøy - innstilling av en øktvariabel: SET application_name = '{bl-host}:{bl-method}'; — vi sender navnet på forretningslogikkverten som forespørselen kommer fra, og navnet på metoden eller applikasjonen som startet den.

Etter at vi har passert "eieren" av forespørselen, må den sendes til loggen - for dette konfigurerer vi variabelen log_line_prefix = ' %m [%p:%v] [%d] %r %a'. For de interesserte, kanskje se i manualenhva betyr det hele. Det viser seg at vi ser i loggen:

  • tid
  • prosess- og transaksjonsidentifikatorer
  • databasenavn
  • IP-adressen til personen som sendte denne forespørselen
  • og metodenavn

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Da innså vi at det ikke er særlig interessant å se på korrelasjonen for én forespørsel mellom ulike servere. Det er ikke ofte du har en situasjon der en applikasjon går like mye opp her og der. Men selv om det er det samme, se på noen av disse serverne.

Så her er snittet "én server - en dag" det viste seg å være nok for oss for enhver analyse.

Den første analytiske delen er den samme "prøve" - en forkortet form for presentasjon av planen, fjernet for alle numeriske indikatorer. Det andre kuttet er applikasjonen eller metoden, og det tredje kuttet er den spesifikke plannoden som forårsaket problemer for oss.

Da vi gikk fra spesifikke forekomster til maler, fikk vi to fordeler samtidig:

  • multippel reduksjon i antall objekter for analyse
    Vi må analysere problemet ikke lenger med tusenvis av forespørsler eller planer, men med dusinvis av maler.
  • tidslinje
    Det vil si at ved å oppsummere "fakta" i en bestemt seksjon, kan du vise utseendet deres i løpet av dagen. Og her kan du forstå at hvis du har et slags mønster som skjer, for eksempel en gang i timen, men det skal skje en gang om dagen, bør du tenke på hva som gikk galt - hvem som forårsaket det og hvorfor, kanskje det burde være her burde ikke. Dette er en annen ikke-numerisk, rent visuell, analysemetode.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

De resterende metodene er basert på indikatorene som vi trekker ut fra planen: hvor mange ganger et slikt mønster skjedde, total og gjennomsnittlig tid, hvor mye data som ble lest fra disken, og hvor mye fra minnet ...

Fordi du for eksempel kommer til analysesiden for verten, se - noe begynner å lese for mye på disken. Disken på serveren takler det ikke – hvem leser fra den?

Og du kan sortere etter hvilken som helst kolonne og bestemme hva du vil forholde deg til akkurat nå - belastningen på prosessoren eller disken, eller det totale antallet forespørsler ... Vi sorterte det, så på de "øverste", fikset det og rullet ut en ny versjon av applikasjonen.
[videoforelesning]

Og umiddelbart kan du se forskjellige applikasjoner som kommer med samme mal fra en forespørsel som SELECT * FROM users WHERE login = 'Vasya'. Frontend, backend, prosessering... Og du lurer på hvorfor behandling vil lese brukeren hvis han ikke samhandler med ham.

Den motsatte måten er å umiddelbart se fra applikasjonen hva den gjør. For eksempel er frontend dette, dette, dette, og dette en gang i timen (tidslinjen hjelper). Og spørsmålet melder seg umiddelbart: det virker som om det ikke er frontendens jobb å gjøre noe en gang i timen...

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Etter en tid innså vi at vi manglet samlet statistikk etter plannoder. Vi isolerte fra planene bare de nodene som gjør noe med dataene til selve tabellene (les/skriv dem etter indeks eller ikke). Faktisk er bare ett aspekt lagt til i forhold til det forrige bildet - hvor mange poster ga denne noden oss?, og hvor mange som ble forkastet (rader fjernet av filter).

Du har ikke en passende indeks på platen, du sender en forespørsel til den, den flyr forbi indeksen, faller inn i Seq Scan... du har filtrert ut alle postene unntatt én. Hvorfor trenger du 100 millioner filtrerte poster per dag? Er det ikke bedre å rulle opp indeksen?

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Etter å ha analysert alle planene node for node, innså vi at det er noen typiske strukturer i planene som med stor sannsynlighet vil se mistenkelige ut. Og det ville være fint å fortelle utvikleren: "Venn, her leser du først etter indeks, så sorterer du og klipper deretter av" - som regel er det en post.

Alle som skrev spørringer har sannsynligvis støtt på dette mønsteret: "Gi meg den siste ordren for Vasya, dens dato." Og hvis du ikke har en indeks etter dato, eller det ikke er noen dato i indeksen du brukte, vil du tråkke på nøyaktig samme "rake".

Men vi vet at dette er en "rake" - så hvorfor ikke umiddelbart fortelle utvikleren hva han bør gjøre. Følgelig, når vi åpner en plan nå, ser utvikleren vår umiddelbart et vakkert bilde med tips, der de umiddelbart forteller ham: "Du har problemer her og der, men de er løst på denne måten og den måten."

Som et resultat har mengden erfaring som var nødvendig for å løse problemer i begynnelsen og nå falt betydelig. Dette er et slikt verktøy vi har.

Masseoptimalisering av PostgreSQL-spørringer. Kirill Borovikov (tensor)

Kilde: www.habr.com

Legg til en kommentar