Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Izvješće predstavlja neke pristupe koji omogućuju pratiti izvedbu SQL upita kada ih ima milijune dnevno, a tu su i stotine nadziranih PostgreSQL poslužitelja.

Koja nam tehnička rješenja omogućuju učinkovitu obradu tolike količine informacija i kako to olakšava život običnog programera?


Tko je zainteresiran? analiza specifičnih problema i razne tehnike optimizacije SQL upite i rješavanje tipičnih DBA problema u PostgreSQL-u - također možete pročitati niz članaka na ovu temu.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)
Moje ime je Kirill Borovikov, predstavljam Tvrtka Tensor. Naime, specijalizirao sam se za rad s bazama podataka u našoj tvrtki.

Danas ću vam reći kako optimiziramo upite, kada ne trebate "razdvojiti" izvedbu jednog upita, već rješavati problem masovno. Kad postoje milijuni zahtjeva, a vi trebate pronaći neke pristupi rješenju ovaj veliki problem.

Općenito, Tensor za milijun naših klijenata jest VLSI je naša aplikacija: korporativna društvena mreža, rješenja za video komunikaciju, za interni i eksterni protok dokumenata, računovodstveni sustavi za računovodstvo i skladišta,... Odnosno takav “megakombinat” za integrirano upravljanje poslovanjem, u kojem postoji više od 100 različitih interni projekti.

Kako bismo osigurali da svi oni normalno rade i razvijaju se, imamo 10 razvojnih centara u cijeloj zemlji, a u njima ih ima više 1000 programera.

Radimo s PostgreSQL-om od 2008. i akumulirali smo veliku količinu onoga što obrađujemo - klijentski podaci, statistički, analitički, podaci iz vanjskih informacijskih sustava - više od 400TB. Samo u produkciji je oko 250 poslužitelja, a ukupno je oko 1000 poslužitelja baza podataka koje pratimo.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

SQL je deklarativni jezik. Ne opisujete "kako" bi nešto trebalo funkcionirati, već "što" želite postići. DBMS zna bolje kako napraviti JOIN - kako povezati vaše tablice, koje uvjete postaviti, što će proći kroz indeks, što neće...

Neki DBMS-ovi prihvaćaju savjete: "Ne, poveži ove dvije tablice u takav i takav red", ali PostgreSQL to ne može učiniti. Ovo je svjesno stajalište vodećih programera: "Radije ćemo završiti optimizaciju upita nego dopustiti programerima da koriste neke vrste savjeta."

No, usprkos činjenici da PostgreSQL ne dopušta "vanjskoj" da kontrolira sam sebe, savršeno dopušta vidjeti što se događa u njemukada pokrenete svoj upit i gdje ima problema.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Općenito, s kojim se klasičnim problemima programer [za DBA] obično suočava? “Ovdje smo ispunili zahtjev, i kod nas je sve sporo, sve visi, nešto se događa... Neka nevolja!”

Razlozi su gotovo uvijek isti:

  • neučinkovit algoritam upita
    Programer: "Sada mu dajem 10 tablica u SQL-u putem JOIN-a..." - i očekuje da će njegovi uvjeti biti čudesno učinkovito "odvezani" i da će sve brzo dobiti. Ali čuda se ne događaju, a svaki sustav s takvom varijabilnošću (10 tablica u jednom FROM) uvijek daje neku vrstu pogreške. [članak]
  • zastarjela statistika
    Ova je točka vrlo relevantna posebno za PostgreSQL, kada ste "izlili" veliki skup podataka na poslužitelj, napravili zahtjev i on "sexcanits" vaš tablet. Jer jučer je u njemu bilo 10 zapisa, a danas ih ima 10 milijuna, ali PostgreSQL toga još nije svjestan i moramo mu to reći. [članak]
  • "čep" na resursima
    Instalirali ste veliku i jako opterećenu bazu podataka na slab poslužitelj koji nema dovoljno diska, memorije ili performansi procesora. I to je sve... Negdje postoji plafon izvedbe iznad kojeg se više ne može skočiti.
  • blokiranje
    Ovo je teška točka, ali oni su najrelevantniji za razne modificirajuće upite (INSERT, UPDATE, DELETE) - ovo je posebna velika tema.

Dobijanje plana

...A za sve ostalo mi trebam plan! Moramo vidjeti što se događa unutar poslužitelja.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Plan izvršenja upita za PostgreSQL stablo je algoritma izvršenja upita u tekstualnom prikazu. Upravo se algoritam, analizom planera, pokazao najučinkovitijim.

Svaki čvor stabla je operacija: dohvaćanje podataka iz tablice ili indeksa, izgradnja bitmape, spajanje dviju tablica, spajanje, križanje ili isključivanje odabira. Izvršavanje upita uključuje hodanje kroz čvorove ovog stabla.

Da biste dobili plan upita, najlakši način je izvršiti naredbu EXPLAIN. Dobiti sa svim stvarnim atributima, to jest, stvarno izvršiti upit na bazi - EXPLAIN (ANALYZE, BUFFERS) SELECT ....

Loš dio: kada ga pokrenete, događa se "ovdje i sada", pa je prikladan samo za lokalno uklanjanje pogrešaka. Ako uzmete visoko opterećen poslužitelj koji je pod jakim protokom promjena podataka, i vidite: “Oh! Ovdje imamo sporo izvršenjeXia zahtjev." Prije pola sata, sat vremena - dok ste pokretali i preuzimali ovaj zahtjev iz zapisa, vraćali ga na poslužitelj, cijeli skup podataka i statistika su se promijenili. Pokrenete ga za otklanjanje pogrešaka - i radi brzo! I ne možete shvatiti zašto, zašto to je polako.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Kako bi shvatili što se točno dogodilo u trenutku kada je zahtjev izvršen na serveru, napisali su pametni ljudi auto_explain modul. Prisutan je u gotovo svim najčešćim PostgreSQL distribucijama, a može se jednostavno aktivirati u konfiguracijskoj datoteci.

Ako shvati da neki zahtjev traje dulje od ograničenja koje ste mu rekli, shvati “snimku” plana ovog zahtjeva i zapisuje ih zajedno u dnevnik.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Čini se da je sada sve u redu, idemo do klade i vidimo tamo... [tekst krpica]. Ali ne možemo reći ništa o tome, osim činjenice da je izvrstan plan jer je za njegovo izvršenje trebalo 11 ms.

Čini se da je sve u redu - ali ništa nije jasno što se zapravo dogodilo. Osim općeg vremena, zapravo ne vidimo ništa. Jer gledanje takvog "janjeta" običnog teksta općenito nije vizualno.

Ali čak i ako nije očito, čak i ako je nezgodno, postoje temeljniji problemi:

  • Čvor označava zbroj resursa cijelog podstabla ispod njega. Odnosno, ne možete samo saznati koliko je vremena potrošeno na to konkretno skeniranje indeksa ako ispod njega postoji neki ugniježđeni uvjet. Moramo dinamički gledati da vidimo postoje li "djeca" i uvjetne varijable, CTE-ovi unutra - i sve to oduzeti "u našim mislima".
  • Druga točka: vrijeme koje je naznačeno na čvoru je vrijeme izvođenja jednog čvora. Ako je ovaj čvor izvršen kao rezultat, na primjer, petlje kroz zapise tablice nekoliko puta, tada se broj petlji—ciklusa ovog čvora—povećava u planu. Ali samo atomsko vrijeme izvršenja ostaje isto u smislu plana. Odnosno, da biste razumjeli koliko je dugo ovaj čvor ukupno izveden, morate pomnožiti jednu stvar s drugom - opet, "u svojoj glavi".

U takvim situacijama shvatite “Tko je najslabija karika?” skoro nemoguće. Stoga čak i sami programeri pišu u "priručniku" da “Razumijevanje plana je umjetnost koja se mora naučiti, iskusiti...”.

Ali imamo 1000 programera i ne možete prenijeti ovo iskustvo svakom od njih. Ja, ti, on zna, ali netko tamo više ne zna. Možda će naučiti, a možda i ne, ali sada treba raditi - a gdje bi stekao to iskustvo?

Planirajte vizualizaciju

Stoga smo shvatili da za rješavanje ovih problema trebamo dobra vizualizacija plana. [članak]

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Prvo smo prošli “tržište” – pogledajmo na internetu što uopće postoji.

Ali pokazalo se da postoji vrlo malo relativno "živih" rješenja koja se više ili manje razvijaju - doslovno, samo jedno: objasni.depesz.com autora Huberta Lubaczewskog. Kada u polje "feed" unesete tekstualni prikaz plana, on vam prikazuje tablicu s raščlanjenim podacima:

  • vlastito vrijeme obrade čvora
  • ukupno vrijeme za cijelo podstablo
  • broj zapisa koji su dohvaćeni, a koji su statistički očekivani
  • samo tijelo čvora

Ova usluga također ima mogućnost dijeljenja arhive poveznica. Ubacio si svoj plan i rekao: "Hej, Vasja, evo linka, tu nešto nije u redu."

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Ali ima i malih problema.

Prvo, ogromna količina "copy-paste". Uzmeš komad cjepanice, zabiješ ga unutra, i opet, i opet.

Drugo, nema analize količine pročitanih podataka — isti međuspremnici koji izlaze EXPLAIN (ANALYZE, BUFFERS), ne vidimo ga ovdje. Jednostavno ih ne zna rastaviti, razumjeti i raditi s njima. Kada čitate puno podataka i shvatite da možda pogrešno raspoređujete predmemoriju diska i memorije, ova informacija je vrlo važna.

Treća negativna točka je vrlo slab razvoj ovog projekta. Obvezivanja su vrlo mala, dobro je jednom u šest mjeseci, a kod je u Perlu.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Ali ovo je sve “stihovi”, mogli bismo nekako živjeti s tim, ali ima jedna stvar koja nas je jako odbila od ove usluge. To su pogreške u analizi Common Table Expression (CTE) i raznih dinamičkih čvorova poput InitPlan/SubPlan.

Ako vjerujete ovoj slici, onda je ukupno vrijeme izvršenja svakog pojedinačnog čvora veće od ukupnog vremena izvršenja cijelog zahtjeva. Jednostavno je - vrijeme generiranja ovog CTE-a nije oduzeto od čvora CTE Scan. Stoga više ne znamo točan odgovor koliko je trajao sam CTE pregled.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Onda smo shvatili da je vrijeme da napišemo svoje - hura! Svaki programer kaže: "Sada ćemo napisati svoje, bit će super jednostavno!"

Uzeli smo hrpu tipičnu za web usluge: jezgru temeljenu na Node.js + Express, koristili Bootstrap i D3.js za prekrasne dijagrame. I naša očekivanja su u potpunosti opravdana - prvi prototip smo dobili za 2 tjedna:

  • analizator prilagođenog plana
    Odnosno, sada možemo analizirati bilo koji plan od onih koje je generirao PostgreSQL.
  • ispravna analiza dinamičkih čvorova - CTE skeniranje, InitPlan, SubPlan
  • analiza raspodjele pufera - gdje se stranice podataka čitaju iz memorije, gdje iz lokalnog cachea, gdje s diska
  • dobio jasnoću
    Da ne “kopam” sve ovo po logu, nego da odmah na slici vidim “najslabiju kariku”.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Dobili smo nešto poput ovoga, s uključenim isticanjem sintakse. No obično naši programeri više ne rade s cjelovitim prikazom plana, već s kraćim. Uostalom, sve brojeve smo već raščlanili i pobacali lijevo-desno, a u sredini smo ostavili samo prvi red, kakav je to čvor: CTE Scan, CTE generacija ili Seq Scan prema nekom predznaku.

Ovo je skraćeni prikaz koji nazivamo predložak plana.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Što bi drugo bilo zgodno? Bilo bi zgodno vidjeti koji je udio našeg ukupnog vremena dodijeljen kojem čvoru - i jednostavno ga "zalijepiti" sa strane kružni graf.

Pokazujemo na čvor i vidimo - ispada da je Seq Scan uzeo manje od četvrtine ukupnog vremena, a preostale 3/4 je uzeo CTE Scan. Užas! Ovo je mala napomena o "brzini paljbe" CTE skeniranja ako ih aktivno koristite u svojim upitima. Nisu baš brzi - inferiorni su čak i običnom skeniranju tablice. [članak] [članak]

Ali obično su takvi dijagrami zanimljiviji, složeniji, kada odmah pokažemo na segment i vidimo, na primjer, da je više od polovice vremena neki Seq Scan “pojeo”. Štoviše, unutra je bila neka vrsta filtra, prema njemu je odbačeno mnogo zapisa... Možete izravno baciti ovu sliku programeru i reći: “Vasya, ovdje ti je sve loše! Shvati, vidi - nešto nije u redu!”

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Naravno, tu su bile i neke "grablje".

Prvo na što smo naišli je problem zaokruživanja. Vrijeme svakog pojedinačnog čvora u planu je naznačeno s točnošću od 1 μs. A kada broj ciklusa čvora prijeđe, na primjer, 1000 - nakon izvođenja PostgreSQL podijelio "unutar točnosti", tada pri povratnom izračunu dobivamo ukupno vrijeme "negdje između 0.95 ms i 1.05 ms". Kada broj ide na mikrosekunde, to je u redu, ali kada su već [mili]sekunde, morate uzeti u obzir ovu informaciju kada "odvezujete" resurse na čvorove plana "tko je koliko potrošio".

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Druga točka, složenija, je distribucija resursa (tih međuspremnika) među dinamičkim čvorovima. To nas je koštalo prva 2 tjedna prototipa plus još 4 tjedna.

Prilično je lako dobiti ovakav problem - napravimo CTE i navodno nešto pročitamo u njemu. Zapravo, PostgreSQL je "pametan" i neće ništa čitati izravno tamo. Zatim iz njega uzmemo prvi zapis, a njemu sto i prvi iz istog CTE-a.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Gledamo plan i razumijemo - čudno je, imamo 3 međuspremnika (podatkovne stranice) "potrošena" u Seq Scanu, još 1 u CTE skeniranju i još 2 u drugom CTE skeniranju. Odnosno, ako sve jednostavno zbrojimo, dobit ćemo 6, ali s tableta čitamo samo 3! CTE Scan ne čita ništa niotkuda, već radi izravno s memorijom procesa. Odnosno, tu očito nešto nije u redu!

Zapravo, ispada da su ovdje sve te 3 stranice podataka koje su tražene od Seq Scan-a, prvo 1 tražio 1. CTE Scan, pa 2. i pročitane su mu još 2. Odnosno ukupno Pročitane su 3 stranice, a ne 6.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

I ova nas je slika dovela do razumijevanja da izvršenje plana više nije stablo, već jednostavno neka vrsta acikličkog grafa. I dobili smo ovakav dijagram, tako da razumijemo "što je uopće došlo odakle." To jest, ovdje smo kreirali CTE iz pg_class i tražili ga dva puta, a gotovo sve naše vrijeme je potrošeno na granu kada smo ga tražili drugi put. Jasno je da je čitanje 2. unosa puno skuplje nego samo čitanje 101. unosa s tableta.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Izdisali smo neko vrijeme. Rekli su: “Sad, Neo, znaš kung fu! Sada je naše iskustvo na vašem ekranu. Sada ga možete koristiti." [članak]

Konsolidacija dnevnika

Naših 1000 programera odahnulo je. Ali shvatili smo da imamo samo stotine "borbenih" poslužitelja, a sve ovo "copy-paste" od strane programera nije nimalo zgodno. Shvatili smo da to moramo sami skupljati.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Općenito, postoji standardni modul koji može prikupljati statistiku, ali ga također treba aktivirati u konfiguraciji - ovo modul pg_stat_statements. Ali nije nam odgovarao.

Prvo, dodjeljuje istim upitima koristeći različite sheme unutar iste baze podataka različiti ID-ovi upita. To jest, ako prvo to učinite SET search_path = '01'; SELECT * FROM user LIMIT 1;, i onda SET search_path = '02'; i isti zahtjev, tada će statistika ovog modula imati različite zapise i neću moći prikupiti opću statistiku posebno u kontekstu ovog profila zahtjeva, bez uzimanja u obzir shema.

Druga točka koja nas je spriječila u korištenju je nedostatak planova. Odnosno, nema plana, postoji samo sam zahtjev. Vidimo što je usporavalo, ali ne razumijemo zašto. I ovdje se vraćamo na problem skupa podataka koji se brzo mijenja.

I posljednji trenutak - nedostatak "činjenica". Odnosno, ne možete adresirati određenu instancu izvršenja upita - nema je, postoji samo agregirana statistika. Iako je moguće raditi s ovim, samo je vrlo teško.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Stoga smo se odlučili boriti protiv copy-pastea i počeli pisati kolekcionar.

Kolektor se povezuje putem SSH-a, uspostavlja sigurnu vezu s poslužiteljem s bazom podataka pomoću certifikata i tail -F “prilijepi” za njega u datoteci dnevnika. Dakle, u ovoj sesiji dobivamo potpuni “mirror” cijele log datoteke, koje poslužitelj generira. Opterećenje samog poslužitelja je minimalno, jer tamo ništa ne parsiramo, samo zrcalimo promet.

Budući da smo već počeli pisati sučelje u Node.js, nastavili smo pisati kolektor u njemu. I ova se tehnologija opravdala jer je vrlo zgodno koristiti JavaScript za rad sa slabo formatiranim tekstualnim podacima, što je dnevnik. A sama infrastruktura Node.js kao pozadinska platforma omogućuje vam jednostavan i praktičan rad s mrežnim vezama, i doista s bilo kojim tokovima podataka.

U skladu s tim, "razvlačimo" dvije veze: prvu da "slušamo" sam zapisnik i uzimamo ga sebi, a drugu da povremeno tražimo bazu. "Ali zapisnik pokazuje da je znak s oid 123 blokiran", ali to programeru ništa ne znači i bilo bi lijepo pitati bazu podataka: "Što je uopće OID = 123?" I tako povremeno pitamo bazu ono što još ne znamo o sebi.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

“Samo jednu stvar niste uzeli u obzir, postoji jedna vrsta pčela sličnih slonovima!..” Ovaj sustav smo počeli razvijati kada smo htjeli nadzirati 10 servera. Najkritičnije u našem razumijevanju, gdje su se pojavili neki problemi s kojima se bilo teško nositi. Ali tijekom prvog tromjesečja dobili smo stotku za praćenje - jer je sustav funkcionirao, svi su ga htjeli, svima je bilo ugodno.

Sve to treba zbrojiti, protok podataka je velik i aktivan. Zapravo, ono što pratimo, s čime se možemo nositi, to je ono što koristimo. Također koristimo PostgreSQL kao pohranu podataka. I ništa brže ne "ulijeva" podatke u njega od operatera COPY Ne još.

Ali jednostavno "sipanje" podataka zapravo nije naša tehnologija. Jer ako imate otprilike 50 tisuća zahtjeva u sekundi na sto poslužitelja, to će generirati 100-150 GB zapisa dnevno. Stoga smo morali pažljivo "rezati" bazu.

Prvo, jesmo podjela po danu, jer, uglavnom, nikoga ne zanima korelacija između dana. Kakva je razlika što ste imali jučer ako ste večeras izbacili novu verziju aplikacije - i već neke nove statistike.

Drugo, naučili smo (bili prisiljeni) vrlo, vrlo brzo za pisanje COPY. Odnosno, ne samo COPYjer on je brži od INSERT, pa čak i brže.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Treća točka – morao sam napuštanje okidača, odnosno stranih ključeva. Odnosno, nemamo nikakav referencijalni integritet. Jer ako imate tablicu koja ima par FK-ova, au strukturi baze podataka kažete da je "ovdje zapis dnevnika koji je referenciran FK-om, na primjer, na grupu zapisa," tada kada ga umetnete, PostgreSQL ne preostaje ništa drugo nego kako to uzeti i učiniti pošteno SELECT 1 FROM master_fk1_table WHERE ... s identifikatorom koji pokušavate umetnuti - samo da provjerite je li taj zapis tamo prisutan, da ne “prekinete” ovaj strani ključ svojim umetanjem.

Umjesto jednog zapisa u ciljnu tablicu i njezine indekse, dobivamo dodatnu korist čitanja iz svih tablica na koje se odnosi. Ali to nam uopće ne treba - naš je zadatak snimiti što više i što je brže moguće uz najmanje opterećenje. Dakle FK - dolje!

Sljedeća točka je agregacija i raspršivanje. U početku smo ih implementirali u bazu podataka - uostalom, zgodno je odmah, kada stigne zapis, učiniti to u nekoj vrsti tableta "plus jedan" točno u okidač. Pa, zgodno je, ali ista loša stvar - umetnete jedan zapis, ali ste prisiljeni čitati i pisati nešto drugo iz druge tablice. Štoviše, ne samo da čitate i pišete, to također činite svaki put.

Sada zamislite da imate tablicu u kojoj jednostavno brojite broj zahtjeva koji su prošli kroz određeni host: +1, +1, +1, ..., +1. A vama, u principu, ovo ne treba - sve je moguće zbroj u memoriji na kolektoru i poslati u bazu podataka u jednom potezu +10.

Da, u slučaju nekih problema može vam se "raspasti" logički integritet, ali to je gotovo nerealan slučaj - jer imate normalan server, ima bateriju u kontroleru, imate dnevnik transakcija, dnevnik na datotečni sustav... Općenito, ne isplati se. Gubitak produktivnosti koji dobivate zbog pokretanja okidača/FK nije vrijedan troškova koji imate.

Isto je i s raspršivanjem. Doleti vam određeni zahtjev, iz njega izračunate određeni identifikator u bazi podataka, upišete ga u bazu i onda to svima kažete. Sve je u redu dok vam u trenutku snimanja ne dođe druga osoba koja želi snimiti istu stvar - i budete blokirani, a to je već loše. Stoga, ako možete prenijeti generiranje nekih ID-ova na klijenta (u odnosu na bazu podataka), bolje je to učiniti.

Jednostavno nam je bilo savršeno koristiti MD5 iz teksta - zahtjev, plan, predložak,... To izračunavamo na kolektorskoj strani, a gotov ID “ulijemo” u bazu. Duljina MD5 i dnevno dijeljenje omogućuju nam da ne brinemo o mogućim kolizijama.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Ali da bismo sve to brzo snimili, morali smo modificirati samu proceduru snimanja.

Kako obično pišete podatke? Imamo nekakav skup podataka, podijelimo ga u nekoliko tablica, pa KOPIRAMO - prvo u prvu, pa u drugu, u treću... Nezgodno je, jer kao da pišemo jedan tok podataka u tri koraka sekvencijalno. Neugodan. Može li se brže? Limenka!

Da biste to učinili, dovoljno je samo rastaviti te tokove paralelno jedan s drugim. Ispada da imamo greške, zahtjeve, šablone, blokiranja, ... koji lete u zasebnim nitima - i sve to pišemo paralelno. Dosta za ovo držite COPY kanal stalno otvorenim za svaku pojedinačnu ciljnu tablicu.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Odnosno kod kolektora uvijek postoji potok, u koji mogu upisati podatke koji su mi potrebni. Ali tako da baza podataka vidi te podatke i da netko ne zaglavi čekajući da se ti podaci zapišu, KOPIRANJE se mora prekidati u određenim intervalima. Za nas je najučinkovitiji period bio oko 100ms - zatvorimo ga i odmah otvorimo na istoj tablici. A ako nemamo dovoljno jednog protoka tijekom nekih vrhunaca, tada radimo udruživanje do određene granice.

Dodatno, saznali smo da je za takav profil opterećenja svako združivanje, kada se zapisi prikupljaju u serijama, zlo. Klasično zlo je INSERT ... VALUES i dalje 1000 zapisa. Jer u tom trenutku imate vrhunac pisanja na mediju, a svi ostali koji pokušavaju nešto zapisati na disk će čekati.

Da biste se riješili takvih anomalija, jednostavno nemojte ništa skupljati, nemoj uopće puferirati. A ako dođe do spremanja u međuspremnik na disk (srećom, Stream API u Node.js omogućuje vam da to saznate) - odgodite ovu vezu. Kada primite događaj da je ponovno slobodan, pišite mu iz akumuliranog reda čekanja. I dok je zauzet, uzmi sljedeći slobodan iz bazena i piši mu.

Prije uvođenja ovakvog pristupa snimanju podataka, imali smo otprilike 4K pisanja, te smo na taj način smanjili opterećenje za 4 puta. Sada su porasli još 6 puta zahvaljujući novim nadziranim bazama podataka - do 100MB/s. Sada pohranjujemo zapise za zadnja 3 mjeseca u volumenu od oko 10-15 TB, nadajući se da će u samo tri mjeseca bilo koji programer moći riješiti bilo koji problem.

Razumijemo probleme

Ali jednostavno prikupljanje svih ovih podataka je dobro, korisno, relevantno, ali nije dovoljno – to treba razumjeti. Jer to su milijuni različitih planova dnevno.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Ali milijunima se ne može upravljati, prvo moramo napraviti “manje”. I, prije svega, morate odlučiti kako ćete organizirati ovu "manju" stvar.

Identificirali smo tri ključne točke:

  • koji poslao ovaj zahtjev
    Odnosno iz koje aplikacije je “stigao”: web sučelja, backenda, sustava plaćanja ili nečeg drugog.
  • gdje dogodilo se
    Na kojem konkretnom poslužitelju? Jer ako imate nekoliko servera pod jednom aplikacijom, pa odjednom jedan “zaglupi” (jer je “disk pokvaren”, “curi memorija”, neki drugi problem), onda se trebate posebno pozabaviti serverom.
  • kao problem se očitovao na ovaj ili onaj način

Da bismo razumjeli "tko" nam je poslao zahtjev, koristimo standardni alat - postavljanje varijable sesije: SET application_name = '{bl-host}:{bl-method}'; — šaljemo naziv hosta poslovne logike s kojeg dolazi zahtjev i naziv metode ili aplikacije koja ga je pokrenula.

Nakon što smo proslijedili "vlasnika" zahtjeva, on se mora ispisati u dnevnik - za to konfiguriramo varijablu log_line_prefix = ' %m [%p:%v] [%d] %r %a'. Za zainteresirane, možda pogledajte u priručnikušto to sve znači. Ispada da vidimo u dnevniku:

  • vrijeme
  • identifikatori procesa i transakcija
  • ime baze podataka
  • IP osobe koja je poslala ovaj zahtjev
  • i naziv metode

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Tada smo shvatili da nije baš zanimljivo gledati korelaciju za jedan zahtjev između različitih poslužitelja. Ne događa se često da imate situaciju da vam jedna aplikacija podjednako zajebe tu i tamo. Ali čak i ako je isti, pogledajte bilo koji od ovih poslužitelja.

Dakle, ovdje je rez "jedan server - jedan dan" pokazalo se da nam je dovoljno za bilo kakvu analizu.

Prvi analitički dio je isti "uzorak" - skraćeni oblik prikaza plana očišćen od svih brojčanih pokazatelja. Drugi rez je primjena ili metoda, a treći rez je specifični čvor plana koji nam je uzrokovao probleme.

Kada smo prešli sa specifičnih instanci na predloške, dobili smo dvije prednosti odjednom:

  • višestruko smanjenje broja objekata za analizu
    Problem više ne moramo analizirati na temelju tisuća upita ili planova, već na temelju desetaka predložaka.
  • Vremenska Crta
    Odnosno, sažimanjem “činjenica” unutar određenog odjeljka možete prikazati njihov izgled tijekom dana. I ovdje možete razumjeti da ako imate nekakav obrazac koji se događa, na primjer, jednom u satu, ali bi se trebao dogoditi jednom dnevno, trebali biste razmisliti o tome što je pošlo po zlu - tko je to uzrokovao i zašto, možda bi to trebalo biti ovdje ne bi trebalo. Ovo je još jedna nenumerička, čisto vizualna metoda analize.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Preostale metode temelje se na pokazateljima koje izdvajamo iz plana: koliko puta se takav uzorak pojavio, ukupno i prosječno vrijeme, koliko je podataka pročitano s diska, a koliko iz memorije...

Jer, na primjer, dođete na analitičku stranicu za host, vidite - nešto počinje čitati previše na disku. Disk na poslužitelju to ne može podnijeti - tko čita s njega?

A možete poredati po bilo kojem stupcu i odlučiti s čime ćete se trenutno baviti - opterećenjem procesora ili diska, ili ukupnim brojem zahtjeva... Poredali smo, pogledali one “top”, popravili i izbacio novu verziju aplikacije.
[video predavanje]

I odmah možete vidjeti različite aplikacije koje dolaze s istim predloškom iz zahtjeva poput SELECT * FROM users WHERE login = 'Vasya'. Frontend, backend, obrada... I pitate se zašto bi obrada čitala korisnika ako on s njim ne komunicira.

Suprotan način je da iz aplikacije odmah vidite što radi. Na primjer, sučelje je ovo, ovo, ovo i ovo jednom na sat (vremenska linija pomaže). I odmah se postavlja pitanje: izgleda da nije posao frontenda da nešto radi jednom na sat...

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Nakon nekog vremena shvatili smo da nam nedostaje agregata statistika po čvorovima plana. Iz planova smo izolirali samo one čvorove koji rade nešto s podacima samih tablica (čitaju/pišu ih po indeksu ili ne). Zapravo, samo je jedan aspekt dodan u odnosu na prethodnu sliku - koliko nam je zapisa donio ovaj čvor?, i koliko ih je odbačeno (redci uklonjeni filtrom).

Nemate odgovarajući indeks na pločici, uputite mu zahtjev, proleti pored indeksa, upadne u Seq Scan... isfiltrirali ste sve zapise osim jednog. Zašto vam treba 100 milijuna filtriranih zapisa dnevno? Nije li bolje smotati indeks?

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Nakon što smo analizirali sve planove čvor po čvor, shvatili smo da postoje neke tipične strukture u planovima koje će vrlo vjerojatno izgledati sumnjivo. I bilo bi lijepo reći programeru: "Prijatelju, ovdje prvo čitate po indeksu, zatim sortirate, a zatim odsiječete" - u pravilu postoji jedan zapis.

Svatko tko je pisao upite vjerojatno se susreo s ovim obrascem: "Daj mi posljednju narudžbu za Vasju, njen datum." A ako nemate indeks po datumu ili nema datuma u indeksu koji ste koristili, tada ćete stati na potpuno iste “grablje” .

Ali znamo da je ovo "grablje" - pa zašto ne odmah reći programeru što treba učiniti. Shodno tome, naš developer sada kad otvori plan, odmah vidi lijepu sliku sa savjetima, gdje mu odmah kažu: “Imaš problema tu i tamo, ali oni se rješavaju ovako i onako.”

Kao rezultat toga, količina iskustva koja je bila potrebna za rješavanje problema na početku i sada je značajno opala. Ovo je vrsta alata koju imamo.

Skupna optimizacija PostgreSQL upita. Kirill Borovikov (Tenzor)

Izvor: www.habr.com

Dodajte komentar