Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Rapportet presenterar vissa angreppssĂ€tt som gör det möjligt att övervaka prestandan för SQL-frĂ„gor, nĂ€r det handlar om miljoner per dag, och kontrollerade PostgreSQL-servrar — hundratals.

Vilka tekniska lösningar gör det möjligt för oss att effektivt hantera sÄdan volym information, och hur underlÀttar detta livet för vanliga utvecklare.

Spela video

För vem Àr analys av specifika problem och olika optimeringstekniker av SQL-frÄgor och lösningar pÄ typiska DBA-uppgifter i PostgreSQL - kan ocksÄ bekanta sig med en serie artiklar om detta Àmne.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)
Jag heter Kirill Borovikov och representerar företaget "Tensor".Specifikt specialiserar jag mig pÄ arbete med databaser i vÄrt företag.

Idag ska jag berÀtta för er hur vi arbetar med optimering av frÄgor, nÀr ni inte behöver "grÀva" i prestandan hos en enda frÄga, utan lösa problemet massivt. NÀr det handlar om miljoner frÄgor och ni behöver hitta nÄgra angreppssÀtt för att lösa detta stora problem.

Generellt sett Ă€r "Tensor" för vĂ„ra en miljon kunder Sbis - vĂ„r applikation:ett företags sociala nĂ€tverk, lösningar för videokommunikation, för intern och extern dokumenthantering, redovisningssystem för bokföring och lagring,
 SĂ„ det Ă€r en sĂ„dan "megakombinering" för komplett affĂ€rshantering med mer Ă€n 100 olika interna projekt.

För att allt ska fungera normalt och utvecklas har vi 10 utvecklingscenter runt om i landet, med mer Àn 1000 utvecklare..

Vi har arbetat med PostgreSQL sedan 2008 och samlat pÄ oss en stor mÀngd information som vi hanterar - detta inkluderar kunddata, statistiska, analytiska och data frÄn externa informationssystem - mer Àn 400 TB.Bara i "produktion" Àr det cirka 250 servrar, och totalt har vi runt 1000 DB-servrar som vi övervakar.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

SQL Àr ett deklarativt sprÄk. Du beskriver inte "hur" nÄgot ska fungera, utan "vad" du vill fÄ.

DB-systemet vet bĂ€ttre hur man gör JOIN - hur man kopplar samman dina tabeller, vilka villkor man ska tillĂ€mpa, vad som gĂ„r genom indexet och vad som inte gör det


Men trots att PostgreSQL inte tillÄter extern hantering, ger det en utmÀrkt möjlighet att se vad som hÀnder internt, nÀr du utför din förfrÄgan, och var problemen uppstÄr.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Generellt sett, vilka klassiska problem stöter utvecklaren [mot DBA] vanligtvis pÄ? "Vi har utfört en förfrÄgan, och det Àr vÀldigt lÄngsamt, allt hÀnger, nÄgot pÄgÄr... Vilken katastrof!"

Orsakerna Àr nÀstan alltid de samma:

  • ineffektiv förfrĂ„gningsalgoritm
    Utvecklaren: "Nu har jag i SQL 10 tabeller genom JOIN..." — och förvĂ€ntar sig att villkoren pĂ„ mirakulöst sĂ€tt ska lösas effektivt, sĂ„ att han fĂ„r det snabbt. Men underverk förekommer inte, och vilket system som helst med sĂ„dan variabilitet (10 tabeller i ett FROM) ger alltid en viss felmarginal. [artikeln]
  • ointressant statistik
    Detta ögonblick Ă€r sĂ€rskilt relevant för PostgreSQL, nĂ€r du har laddat en stor dataset pĂ„ servern, gör en förfrĂ„gan — men den "sekspelar" i tabellen. Eftersom den igĂ„r hade 10 poster, men idag 10 miljoner, men PostgreSQL vet fortfarande inte om det, och du behöver pĂ„minna den om det. [artikeln]
  • "flaskhals" pĂ„ resurser
    Du har installerat en stor och tung databas pÄ en svag server, som saknar diskutrymme, minne, och processorkraft. Och allt... Det finns en grÀns för prestanda, över vilken du inte kan hoppa.
  • lĂ„ser
    Detta Ă€r en komplicerad frĂ„ga, men den Ă€r mest relevant för olika modifierande förfrĂ„gningar (INSERT, UPDATE, DELETE) — ett separat stort Ă€mne.

Att fÄ en plan


 Och för allt annat behöver vi en plan! Vi behöver se vad som hĂ€nder inuti servern.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Exekveringsplanen för en förfrÄgan i PostgreSQL Àr ett trÀd av algoritmer i textformat. Det Àr just den algoritm som analysatorn har funnit vara mest effektiv vid planering.

Varje nod i trÀdet Àr en operation: hÀmta data frÄn tabellen eller indexet, bygga en bitkarta, förena tvÄ tabeller, kombinera, korsa eller exkludera urval. Exekveringen av förfrÄgan innebÀr en genomgÄng av dessa noders trÀd.

För att fĂ„ förfrĂ„gningsplanen Ă€r det enklaste sĂ€ttet att köra kommandot EXPLAIN. För att fĂ„ med alla verkliga attribut, det vill sĂ€ga att faktiskt exekvera förfrĂ„gan pĂ„ databasen — EXPLAIN (ANALYZE, BUFFERS) SELECT ....

DĂ„lig situation: nĂ€r du utför den sker det "hĂ€r och nu", sĂ„ det passar bara för lokal felsökning. Om du tar en högbelastad server som stĂ„r under ett starkt flöde av datĂ€mnen, och ser: "Aj! HĂ€r har vi en lĂ„ngsam frĂ„ga.Det var för en halvtimme, en timme sedan — medan du sprang och hĂ€mtade den hĂ€r frĂ„gan frĂ„n loggarna, förde den tillbaka till servern, sĂ„ har hela ditt dataset och statistik förĂ€ndrats. Du utför den för att felsöka — och den körs snabbt! Och du kan inte förstĂ„ varför, varför den Ă€r lĂ„ngsam. var För att förstĂ„ vad som hĂ€nde exakt i det ögonblicket nĂ€r frĂ„gan kördes pĂ„ servern, har smarta mĂ€nniskor skrivit

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

modulen auto_explain. Den finns praktiskt taget i alla de mest populÀra distributionerna av PostgreSQL, och kan enkelt aktiveras i konfigurationsfilen.Om den förstÄr att en frÄga körs lÀngre Àn den grÀns du angav, tar den en

"snapshot" av planen för denna frĂ„ga och skriver dem tillsammans i loggen. Det ser ut som allt Ă€r bra, vi gĂ„r in i loggen och ser dĂ€r
 [portions text]. Men vi kan inte sĂ€ga nĂ„got om det, förutom att det Ă€r en utmĂ€rkt plan eftersom det kördes pĂ„ 11 ms..

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Det verkar som allt Ă€r bra — men inget Ă€r klart, vad som faktiskt hĂ€nde. Förutom den totala tiden ser vi inte mycket mer. För att titta pĂ„ en sĂ„dan "platta" med ren text Ă€r helt ogenomskinligt.

Men Àven om det Àr ogenomskinligt, lÄt sÀga obekvÀmt, sÄ finns det mer grundlÀggande problem:

I noden anges

  • summan av resurserna för hela undertrĂ€det under den. SĂ„ledes kan vi inte enkelt ta reda pĂ„ hur mycket tid som spenderades pĂ„ detta specifika Index Scan — om det finns nĂ„got inbĂ€ddat villkor under det. Vi mĂ„ste dynamiskt se om det finns nĂ„gra "barn" och villkorsvariabler, CTE — och rĂ€kna ut allt detta "i huvudet". Den andra punkten: tiden som anges i noden Ă€r
  • tiden för en engĂ„ngsutförande av noden. Om denna nod kördes som ett resultat av till exempel en loop över rader i tabellen flera gĂ„nger, ökar antalet loops — iterationer av denna nod i planen. Men sjĂ€lva tiden för atomĂ€r utförande förblir den samma i planen. SĂ„ för att förstĂ„ hur lĂ€nge denna nod faktiskt kördes totalt, mĂ„ste man multiplicera det ena med det andra — Ă„terigen "i huvudet".. Om denna nod har körts som ett resultat av, till exempel, en loop genom tabellposter flera gĂ„nger, ökar antalet loops för denna nod i planen. Men sjĂ€lva tiden för atomĂ€r körning förblir oförĂ€ndrad i planen. Det innebĂ€r att för att förstĂ„ hur lĂ€nge denna nod faktiskt har körts totalt, mĂ„ste man multiplicera ena med den andra — Ă„terigen "i huvudet".

Under these circumstances, understanding 'Who is the weakest link?' is practically impossible. Therefore, even the developers themselves state in the 'manual' that ‘Understanding the plan is an art that needs to be learned, experience
’.

But we have 1000 developers, and you can't pass this experience into each of their minds. I, you, he — know, but someone over there — does not. Maybe he'll learn, maybe he won't, but he needs to work right now — so where can he gain this experience?

Visualization of the plan

So we realized — to tackle these problems, we need good visualization of the plan. [article]

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

We initially went 'to the market' — let's search the internet for what exists.

But it turned out that regarding 'live' solutions that are somewhat developed, there are very few — literally, one: explain.depesz.com by Hubert Lubaczewski. You input a textual representation of the plan, and it shows you a table with the parsed data:

  • the node’s own execution time
  • the total time across the entire subtree
  • the number of entries that were retrieved, and what was statistically expected
  • the body of the node itself

This service also has the ability to share a link archive. You upload your plan there and say: 'Hey, Vasya, here’s the link, something is off there.'

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

But there are some minor issues.

Firstly, a huge amount of 'copy-paste'. You take a piece of the log, stick it there, and again, and again.

Secondly, there is no analysis of the amount of data read — those same buffers that output EXPLAIN (ANALYZE, BUFFERS), we do not see here. It simply can't parse, understand, and work with them. When you read a lot of data and realize that you might be incorrectly 'laying out' on disk and memory cache, this information is very important.

The third negative point — very weak development of this project. Commits are very minor, good if they happen once every six months, and the code is in Perl.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

But that’s all 'lyric', we could somehow live with that, but there's one thing that strongly turned us away from this service. It’s the errors in the analysis of Common Table Expressions (CTE) and various dynamic nodes like InitPlan/SubPlan.

If we believe this image, the total execution time of each individual node is greater than the total execution time of the entire query. It's simple — the time for the CTE Scan node did not deduct the time taken to generate this CTEDĂ€rför vet vi inte lĂ€ngre det rĂ€tta svaret pĂ„ hur lĂ€nge sjĂ€lva CTE-skanningen tog.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

HĂ€r insĂ„g vi att det var dags att skriva vĂ„rt eget – hurra! Varje utvecklare sĂ€ger: "Nu ska vi skriva vĂ„rt eget, det kommer att bli superenkelt!"

Vi tog en typisk stack för webbtjĂ€nster: kĂ€rna pĂ„ Node.js + Express, lade till Bootstrap och för de fina diagrammen – D3.js. Och vĂ„ra förvĂ€ntningar uppfylldes helt – vi fick den första prototypen pĂ„ 2 veckor:

  • egen parser för planen
    Det vill sÀga att vi nu kan bryta ner vilken som helst av de planer som genereras av PostgreSQL.
  • korrekt analys av dynamiska noder – CTE Scan, InitPlan, SubPlan
  • analys av bufferdistribution – var datorsidorna lĂ€ses frĂ„n minnet, var de lĂ€ses frĂ„n lokal cache, var de lĂ€ses frĂ„n disk
  • vi fick överskĂ„dlighet
    För att inte behöva "grÀva" i loggarna, utan se det "svagaste lÀnken" direkt pÄ bilden.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Vi fick ungefĂ€r en sĂ„dan bild – direkt med syntaktisk markering. Men vanligtvis arbetar vĂ„ra utvecklare inte med hela planen, utan med en kortare version. Vi har redan parsat alla siffror och kastat dem Ă„t sidan, och kvar mitt i har vi bara den första raden som beskriver noden: CTE Scan, CTE-generering eller Seq Scan frĂ„n nĂ„gon tabell.

Den hÀr förkortade presentationen kallar vi mall för planen.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Vad skulle ytterligare vara bra? Det skulle vara bra att se vilken andel av den totala tiden som fördelas pĂ„ varje nod – och helt enkelt "klistra" det pĂ„ sidan tĂ„rtdiagram.

Vi hovrar över noden och ser – det visar sig att Seq Scan tog mindre Ă€n en fjĂ€rdedel av all tid, medan de Ă„terstĂ„ende 3/4 utgjordes av CTE Scan. Ousch! Detta lilla pĂ„pekande handlar om "hastigheten" hos CTE Scan, om du anvĂ€nder dem aktivt i dina förfrĂ„gningar. De Ă€r inte sĂ€rskilt snabba - de förlorar till och med mot vanlig tabellskanning. [article] [article]

Men vanligtvis Ă€r sĂ„dana diagram mer intressanta och mer komplexa, nĂ€r vi hoverar över segmentet, och ser till exempel att mer Ă€n hĂ€lften av all tid "Ă„t" en viss Seq Scan. Dessutom fanns det ett Filter dĂ€r inne, mĂ€ngder av poster kastades bort
 Den hĂ€r bilden kan man helt enkelt ge till utvecklaren och sĂ€ga: "Vanya, du har verkligen problem hĂ€r! Ta reda pĂ„ vad som Ă€r fel!"

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

SjÀlvklart kom vi inte undan "fallgropar".

Det första vi stötte pĂ„ var avrundningsproblemet. Tiden för varje nod i planen anges med noggrannhet upp till 1ÎŒs. NĂ€r antalet cykler för noden överskrider, till exempel, 1000 — efter att PostgreSQL har delat "med noggrannhet upp till", fĂ„r vi en total tid "nĂ„gonstans mellan 0,95 ms och 1,05 ms". NĂ€r det handlar om mikrosekunder Ă€r det okej, men nĂ€r vi kommer upp i [millisekunder] mĂ„ste vi beakta den informationen nĂ€r vi "avvecklar" resurserna per nod i planen "vem som anvĂ€nde vad".

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Den andra punkten, som Àr mer komplex, Àr fördelningen av resurser (de dÀr buffrarna) över dynamiska noder. Detta kostade oss ytterligare 4 veckor under de första 2 veckorna med prototypen.

Det Ă€r ganska enkelt att fĂ„ sĂ„dana problem — vi skapar en CTE och lĂ€ser pĂ„ nĂ„got sĂ€tt frĂ„n den. I verkligheten Ă€r PostgreSQL "smart" och kommer inte att lĂ€sa nĂ„got direkt dĂ€r. Sedan tar vi den första posten frĂ„n den och gĂ„r tillbaka till den hundra första frĂ„n samma CTE.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Vi tittar pĂ„ planen och inser — konstigt, vi hade 3 buffrar (datablock) som "anvĂ€ndes" i Seq Scan, ytterligare 1 i CTE Scan, och 2 till i den andra CTE Scan. SĂ„ om vi bara summerar fĂ„r vi 6, men frĂ„n tabellen lĂ€ste vi bara 3! CTE Scan lĂ€ser ju ingenting, utan arbetar direkt med processens minne. SĂ„ hĂ€r Ă€r det uppenbart nĂ„gonting som inte stĂ€mmer!

I sjÀlva verket visar det sig att de 3 datablock som begÀrdes frÄn Seq Scan först bad 1:a CTE Scan, och sedan 2:a, och han fick ytterligare 2. SÄ totalt lÀstes 3 datablock, inte 6.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Och den bilden ledde oss till förstÄelsen av att exekveringen av planen inte lÀngre Àr ett trÀd, utan helt enkelt en akyklisk graf. Och vi fick en sÄdan schematisk bild för att förstÄ "vad som kom frÄn var". HÀr skapade vi en CTE frÄn pg_class och bad om den tvÄ gÄnger, och nÀstan all vÄr tid gick Ät till att frÄga den en andra gÄng. Det Àr uppenbart att det Àr mycket dyrare att lÀsa den hundra första posten Àn den första frÄn tabellen.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Vi andades ut ett tag. Vi sa: "Nu, Neo, du vet kung fu! Nu finns vÄr erfarenhet direkt pÄ din skÀrm. Nu kan du anvÀnda den." [article]

Konsolidering av loggar

VÄra 1000 utvecklare andades lÀttat ut. Men vi förstod att vi bara hade hundratals "aktiva" servrar, och all denna "copy-paste" frÄn utvecklarna var verkligen inte praktiskt. Vi insÄg att vi var tvungna att samla detta sjÀlva.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Det finns faktiskt en standardmodul som kan samla statistik, men den behöver ocksĂ„ aktiveras i konfigurationen — det Ă€r modulen pg_stat_statements. Men den passade inte för oss.

För det första tilldelar den olika QueryId för samma förfrÄgningar pÄ olika scheman inom samma databas. olika QueryId. Det betyder att om man först gör SET search_path = '01'; SELECT * FROM user LIMIT 1;, och sedan SET search_path = '02'; och en liknande förfrÄgan, sÄ kommer det att finnas olika poster i statistiken för denna modul, och jag kan inte samla sammanlagd statistik just för den hÀr förfrÄgningsprofilen, utan att ta hÀnsyn till scheman.

Den andra punkten som hindrade oss frĂ„n att anvĂ€nda den Ă€r bristen pĂ„ planer. AlltsĂ„ finns det ingen plan — bara sjĂ€lva förfrĂ„gan. Vi ser vad som orsakade bromsen, men förstĂ„r inte varför. Och hĂ€r kommer vi tillbaka till problemet med en snabbt förĂ€ndrad datasets.

Och den sista punkten — bristen pĂ„ "fakta". Det gĂ„r alltsĂ„ inte att adressera en specifik instans av en förfrĂ„gan — den finns inte, endast aggregerad statistik. Med det kan man Ă€ven arbeta, men det Ă€r mycket svĂ„rt.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

DÀrför bestÀmde vi oss för att kÀmpa emot "copy-paste" och började skriva en kollektortjÀnst..

Kollektorn ansluter via SSH, "upprÀttar" en sÀker förbindelse till servern med databasen med hjÀlp av ett certifikat och tail -F "ansluter" sig till loggfilen. PÄ sÄ sÀtt fÄr vi en fullstÀndig "spegel" av hela loggfilen , som servern genererar. Belastningen pÄ servern Àr minimal eftersom vi inte parserar nÄgot dÀr, utan bara speglar trafiken.Eftersom vi redan började skriva ett grÀnssnitt i Node.js, fortsatte vi att skriva kollektorn pÄ samma plattform. Och den hÀr teknologin har visat sig vara effektiv, eftersom det Àr mycket praktiskt att anvÀnda JavaScript för att arbeta med svagt formaterade textdata, som loggar. SjÀlv infrastrukturen i Node.js som backend-plattform gör det enkelt och smidigt att arbeta med nÀtverksanslutningar och generellt med datastreams.

Eftersom vi redan har börjat skriva grÀnssnittet pÄ Node.js, fortsatte vi Àven att skriva samlaren pÄ den. Och denna teknologi har visat sig vara effektiv, eftersom det Àr mycket bekvÀmt att arbeta med svagt strukturerade textdata, som loggar, med JavaScript. SjÀlva infrastrukturen Node.js som backend-plattform gör det enkelt och smidigt att hantera nÀtverksanslutningar och överhuvudtaget med olika datastreams.

DÀrför "drar" vi tvÄ anslutningar: en för att "lyssna" pÄ sjÀlva loggen och hÀmta den, och en annan för att regelbundet frÄga databasen. "Det kom ett meddelande i loggen att tabellen med oid 123 Àr blockerat", men för utvecklaren sÀger detta ingenting, och det skulle vara bra att frÄga databasen "Vad innebÀr egentligen OID = 123?" SÄ vi frÄgar regelbundet databasen om sÄdant vi fortfarande inte kÀnner till.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

"Det finns en sak du inte beaktade, det finns en typ av elefantliknande bin!" Vi började utveckla detta system nĂ€r vi ville övervaka 10 servrar. De mest kritiska enligt vĂ„r uppfattning, dĂ€r problem uppstod som var svĂ„ra att hantera. Men under det första kvartalet fick vi övervaka hundra — för systemet "funkade", alla ville ha det, alla tyckte det var praktiskt.

Allt detta behöver samlas, datamĂ€ngden Ă€r stor och aktiv. Egentligen monitorerar vi det vi kan hantera — det Ă€r vad vi anvĂ€nder. Vi anvĂ€nder ocksĂ„ PostgreSQL som datalager. Det finns inget snabbare sĂ€tt att "fyll i" data till det Ă€n operatören COPY det finns inte Ă€n.

Men bara att "fyll i" data Àr inte riktigt vÄr teknik. För om du har hundra servrar som hanterar ungefÀr 50k förfrÄgningar per sekund, genererar det 100-150GB loggar per dag. DÀrför var vi tvungna att noggrant "bearbeta" databasen.

För det första gjorde vi partitionering per dag, för i stort sett Ă€r ingen intresserad av korrelationen mellan dagarna. Vad spelar det för roll vad du hade igĂ„r om du pĂ„ natten slĂ€ppte en ny version av applikationen — och det finns redan ny statistik.

För det andra lĂ€rde vi oss (vi var tvungna) att skriva mycket, mycket snabbt med hjĂ€lp av COPY. SĂ„ det handlar inte bara om COPY, för det Ă€r snabbare Ă€n INSÄTT, utan Ă€nnu snabbare.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Tredje punkten — vi blev tvungna att avstĂ„ frĂ„n triggers och dĂ€rmed frĂ„n Foreign Keys. SĂ„ vi har ingen referensintegritet alls. För om du har en tabell med ett par FK, och du sĂ€ger i databasstrukturen att "den hĂ€r posten i loggen refererar via FK, till exempel till en grupp poster", sĂ„ har PostgreSQL inget annat kvar Ă€n att Ă€rligt utföra SELECT 1 FROM master_fk1_table WHERE ... med det identifierare som du försöker infoga — bara för att kontrollera att den posten finns dĂ€r, sĂ„ att du inte "bryter" denna Foreign Key med din insĂ€ttning.

Vi fĂ„r istĂ€llet för en post i mĂ„l-tabellen och dess index, dessutom lĂ€sningar frĂ„n alla tabeller den refererar till. Det behöver vi inte alls – vĂ„r uppgift Ă€r att skriva sĂ„ mycket som möjligt och sĂ„ snabbt som möjligt med minsta möjliga belastning. SĂ„ bort med FK!

NĂ€sta punkt – aggregering och hashning. Ursprungligen hade vi det implementerat i databasen – det Ă€r ju praktiskt nĂ€r en post kommer in att direkt göra det i nĂ„gon tabell. «plus ett» direkt i triggarna. Bra, praktiskt, men dĂ„ligt av samma anledning – du lĂ€gger in en post och tvingas lĂ€sa och skriva nĂ„got frĂ„n en annan tabell. Dessutom, inte bara lĂ€sa och skriva – du mĂ„ste göra det varje gĂ„ng.

Och tĂ€nk nu er att ni har en tabell dĂ€r ni helt enkelt rĂ€knar antalet förfrĂ„gningar som passerat en specifik vĂ€rd: +1, +1, +1, ..., +1. Men det behöver ni i princip inte – allt detta kan summeras i minnet pĂ„ samlaren och skickas till databasen pĂ„ en gĂ„ng. +10.

Ja, om det skulle uppstĂ„ nĂ„got problem kan er logiska integritet "kollapsa", men det Ă€r nĂ€stan ett omöjligt scenario – för ni har en fungerande server, med ett batteri i kontrollern, en transaktionslogg, en logg pĂ„ filsystemet
 Generellt, det Ă€r inte vĂ€rt det. Den prestandaförlust ni fĂ„r som följd av att anvĂ€nda triggar/FK, de kostnader ni Ă„drar er, Ă€r inte vĂ€rt det.

Detsamma gĂ€ller hashning. En förfrĂ„gan kommer till er, ni berĂ€knar ett identifierare i databasen, skriver till databasen och sĂ€ger sedan det till alla. Allt Ă€r bra, sĂ„ lĂ€nge ingen annan mer vill skriva samma sak precis i det ögonblicket – och dĂ„ fĂ„r ni en blockering, vilket Ă€r dĂ„ligt. SĂ„ om ni kan flytta genereringen av vissa ID:n till klienten (relativt databasen), gör hellre det.

Det passade oss perfekt att anvĂ€nda MD5 av texten – förfrĂ„gan, planen, mallen,
 Vi berĂ€knar den pĂ„ samlarens sida och skriver redan det fĂ€rdiga ID:t till databasen. LĂ€ngden pĂ„ MD5 och daglig partitionering gör att vi inte behöver oroa oss för möjliga kollisioner.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Men för att skriva allt detta snabbt behövde vi modifiera sjÀlva skrivproceduren.

Hur skriver man vanligtvis data? Vi har en dataset, som vi delar upp i flera tabeller, och sedan gör vi COPY – först till den första, sedan den andra, den tredje
 Det Ă€r omstĂ€ndligt, eftersom vi tycks skriva en datastream i tre steg i följd. Inte sĂ„ bra. Kan vi göra det snabbare? Ja!

För att göra det rĂ€cker det att dela upp dessa strömmar parallellt med varandra. Det innebĂ€r att vi har fel, förfrĂ„gningar, mallar, lĂ„s
 som flyger i separata strömmar, och vi skriver allt parallellt. För detta behöver vi bara ha en COPY-kanal öppet hela tiden för varje separat mĂ„l-tabell..

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Det betyder att insamlaren alltid har en ström, dit jag kan skriva de nödvĂ€ndiga data. Men för att databasen ska kunna se dessa data, och för att ingen ska fastna i lĂ„s, och vĂ€nta pĂ„ att data ska skrivas, mĂ„ste COPY avbrytas med viss periodicitet. För oss visade sig en period pĂ„ cirka 100 ms vara den mest effektiva – vi stĂ€nger och öppnar direkt igen pĂ„ samma tabell. Och om vi inte har tillrĂ€ckligt med strömmar under vissa toppar, dĂ„ gör vi polling upp till en viss grĂ€ns.

Dessutom har vi kommit fram till att för en sĂ„dan belastningsprofil Ă€r varje aggregation, nĂ€r poster samlas i paket – ett ont. Klassisk ondska Ă€r INSERT ... VALUES och vidare 1000 poster. För i det ögonblicket uppstĂ„r en skrivtopp pĂ„ lagringsmediet, och alla andra som försöker skriva till disken kommer att vĂ€nta.

För att bli av med sĂ„dana anomalier, aggregera ingenting, anvĂ€nd ingen buffring alls. Och om buffring till disk Ă€ndĂ„ uppstĂ„r (lyckligtvis tillĂ„ter Stream API i Node.js oss att ta reda pĂ„ detta) – skjuta upp den anslutningen. NĂ€r hĂ€ndelsen kommer att den Ă€r fri igen – skriv dĂ„ frĂ„n den ackumulerade kön. Och sĂ„ lĂ€nge den Ă€r upptagen – ta nĂ€sta tillgĂ€ngliga frĂ„n poolen och skriv till den.

Innan vi implementerade detta sĂ€tt att skriva data hade vi cirka 4K write ops, och pĂ„ detta sĂ€tt minskade vi belastningen med 4 gĂ„nger. Nu har vi ökat ytterligare 6 gĂ„nger tack vare nya observerade databaser – upp till 100MB/s. Och nu sparar vi loggar för de senaste 3 mĂ„naderna i en volym av cirka 10-15TB, i hopp om att en utvecklare kan lösa nĂ„got problem under tre mĂ„nader.

Vi förstÄr problemen

Men att bara samla alla dessa data Àr bra, anvÀndbart och relevant, men otillrÀckligt - de mÄste förstÄs. För det handlar om miljontals olika planer per dag.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Men miljontals Àr okontrollerat, man mÄste först göra "mindre". Och frÀmst mÄste man avgöra hur detta "mindre" ska organiseras.

Vi har identifierat tre nyckelpunkter:

  • vem skickade denna begĂ€ran
    Det vill sÀga frÄn vilken applikation den "kommit": webgrÀnssnitt, backend, betalningssystem eller nÄgot annat.
  • w det intrĂ€ffade
    PÄ vilken specifik server. För om du har flera servrar för en applikation, och plötsligt en "hackar" (för att "disken har kraschat", "minnet lÀcker", nÄgon annan olycka), mÄste man konkret adressera sig till servern.
  • som problemet faktiskt upptrĂ€dde i den eller den planen

För att förstĂ„ "vem" som skickade oss begĂ€ran, anvĂ€nder vi ett standardverktyg - att stĂ€lla in en sessionsvariabel: SET application_name = '{bl-host}:{bl-method}'; — vi registrerar namnet pĂ„ vĂ€rden av affĂ€rslogik, frĂ„n vilket begĂ€ran kommer, och namnet pĂ„ metoden eller applikationen som initierade den.

Efter att vi har överfört "vÀrdena" av begÀran, mÄste de loggas - för detta konfigurerar vi variabeln log_line_prefix = ' %m [%p:%v] [%d] %r %a'. Den som Àr intresserad kan titta i manualen, vad allt detta betyder. SÄ vi ser i loggen:

  • tid
  • process- och transaktionsidentifierare
  • databasnamn
  • IP för den som skickade denna begĂ€ran
  • och metodnamn

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Vidare insÄg vi att det inte Àr sÀrskilt intressant att se korrelationen frÄn en begÀran mellan olika servrar. Det Àr sÀllan en situation dÀr en applikation misslyckas pÄ exakt samma sÀtt hÀr och dÀr. Men Àven om det Àr likadant - titta pÄ nÄgon av dessa servrar.

SÄ det skÀrningen "en server - en dag" visade sig vara tillrÀcklig för varje analys.

Det första analytiska snittet - Ă€r just den "mall" – en förkortad form av att presentera planen, rensad frĂ„n alla numeriska indikatorer. Den andra skĂ€rningen - applikationen eller metoden, och den tredje - Ă€r den specifika noden av planen som orsakade oss problem.

NÀr vi gick frÄn specifika instanser till mallar, fick vi genast tvÄ fördelar:

  • en betydande minskning av antalet objekt för analys
    Behövs inte analysera problem utifrÄn tusentals begÀrningar eller planer, utan frÄn tiotals mallar.
  • tidslinje
    SĂ„, genom att sammanfatta "fakta" inom ett visst perspektiv kan man visa deras förekomst under dagen. HĂ€r kan du förstĂ„ att om ett visst mönster intrĂ€ffar, till exempel en gĂ„ng i timmen nĂ€r det borde vara en gĂ„ng per dag, sĂ„ bör du fundera pĂ„ vad som har gĂ„tt fel — vem som har anropat det och varför, kanske det inte ens ska finnas hĂ€r. Detta Ă€r ytterligare ett icke-numeriskt, rent visuellt sĂ€tt att analysera.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

De andra sÀtten baseras pÄ de indikatorer som vi extraherar frÄn planen: hur mÄnga gÄnger ett visst mönster har intrÀffat, den totala och genomsnittliga tiden, hur mycket data som har lÀsts frÄn disken, och hur mycket frÄn minnet...

För nĂ€r du till exempel gĂ„r till sidan för analys av vĂ€rden, ser du — nĂ„gonting har börjat lĂ€sa för mycket frĂ„n disken. Disken pĂ„ servern klarar inte av det — men vem lĂ€ser frĂ„n den?

Och du kan sortera efter vilken kolumn som helst och bestĂ€mma vad du direkt ska agera pĂ„ — belastningen pĂ„ CPU:n eller disken, eller det totala antalet förfrĂ„gningar... Sorterat, tittat pĂ„ "topparna", fixat — rullat ut en ny version av applikationen.
[videoförelÀsning]

Och plötsligt kan du se olika applikationer som anvÀnder samma mönster frÄn en förfrÄgan av typen SELECT * FROM users WHERE login = 'Vasya'. Frontend, backend, processering... Och du börjar fundera pÄ varför processeringen skulle lÀsa anvÀndaren om den inte interagerar med den.

Den omvĂ€nda metoden — direkt se vad applikationen gör. Till exempel, frontend — det hĂ€r, det hĂ€r, och det hĂ€r, och dessutom det hĂ€r en gĂ„ng i timmen (som tidslinjen hjĂ€lper till med). Och plötsligt uppstĂ„r frĂ„gan — det verkar ju inte vara frontens roll att göra nĂ„got en gĂ„ng i timmen...

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Efter en tid insĂ„g vi att vi saknade aggregerad statistik ur perspektivet av planeknoden. Vi isolerade endast de noder frĂ„n planerna som gör nĂ„got med data i sjĂ€lva tabellerna (lĂ€ser/skriver dem baserat pĂ„ index eller inte). I princip lĂ€ggs endast ett aspekt till den tidigare bilden — hur mĂ„nga poster denna nod har gett oss, och hur mĂ„nga den har avvisat (Rows Removed by Filter).

Du har inget lÀmpligt index pÄ tabellen, du gör en förfrÄgan till den, den flyger förbi indexet, faller ner i Seq Scan... du filtrerar bort alla poster utom en. Varför skulle du ha 100 miljoner filtrerade poster pÄ ett dygn, Àr det inte bÀttre att skapa ett index?

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

Genom att analysera alla planer för noder, insĂ„g vi att det finns vissa typiska strukturer i planerna som med stor sannolikhet verkar misstĂ€nksamma. Det skulle vara bra om utvecklaren fick en uppmaning: "Kompis, hĂ€r börjar du med att lĂ€sa via indexet, sedan sorterar du, och till sist beskĂ€r du" – som regel finns det bara en post dĂ€r.

Alla som har gjort förfrÄgningar med ett sÄdant mönster har sÀkerligen stött pÄ följande: "Ge mig den senaste bestÀllningen frÄn Vasa, datumet." Och om du inte har ett index pÄ datumet, eller i det anvÀnda indexet inte finns nÄgot datum, sÄ kommer du att snubbla just pÄ sÄdana "attraplattor."

Men vi vet ju att det Ă€r "attraplattor" — sĂ„ varför inte direkt ge utvecklaren en hint om vad han bör göra? DĂ€rför, nĂ€r vĂ„r utvecklare nu öppnar planen, ser han direkt en vacker bild med tips dĂ€r han fĂ„r veta: "Du har problem hĂ€r och hĂ€r, och de kan lösas sĂ„ hĂ€r och sĂ„ hĂ€r."

Som ett resultat har mÀngden erfarenhet som var nödvÀndig för att lösa problem i början och nu minskat avsevÀrt. SÄ hÀr har vi fÄtt fram ett sÄdant verktyg.

Massoptimering av frÄgor i PostgreSQL. Kirill Borovikov (Tensor)

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster