PostgreSQL Query Profiler: Kako uskladiti plan i upit

Mnogi koji već koriste objasni.tensor.ru - naša usluga vizualizacije plana PostgreSQL možda nije svjesna jedne od svojih supermoći - da pretvori teško čitljiv dio serverskog dnevnika...

PostgreSQL Query Profiler: Kako uskladiti plan i upit
… u lijepo dizajniran upit s kontekstualnim savjetima za relevantne čvorove plana:

PostgreSQL Query Profiler: Kako uskladiti plan i upit
U ovom transkriptu drugog dijela njegovog izvještaj na PGConf.Russia 2020 Reći ću vam kako smo to uspjeli.

Transkript prvog dijela, posvećenog tipičnim problemima performansi upita i njihovim rješenjima, možete pronaći u članku "Recepti za bolesne SQL upite".



Prvo, hajde da uradimo bojenje - i više nećemo bojati plan, već smo ga ofarbali, već je lepo i razumljivo, ali zahtev.

Činilo nam se da zahtjev izvučen iz dnevnika sa neformatiranim „listom“ izgleda vrlo ružno i stoga nezgodno.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Pogotovo kada programeri "zalijepe" tijelo zahtjeva u kod (ovo je, naravno, anti-uzorak, ali se dešava) u jedan red. Užas!

Hajde da to nacrtamo nekako ljepše.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

A ako to možemo lijepo nacrtati, odnosno rastaviti i sastaviti tijelo zahtjeva nazad, onda možemo "prikačiti" nagoveštaj svakom objektu ovog zahtjeva - šta se dogodilo u odgovarajućoj tački plana.

Stablo sintakse upita

Da biste to učinili, upit se prvo mora raščlaniti.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Jer, imamo jezgro sistema radi na NodeJS, onda smo napravili modul za to, možete pronađite ga na githubu. U stvari, ovo su proširena "vezivanja" za unutrašnjost samog PostgreSQL parsera. To jest, gramatika je jednostavno binarno kompajlirana i veze za nju pravi NodeJS. Za osnovu smo uzeli tuđe module - tu nema velike tajne.

Tijelo ulaznog zahtjeva hranimo našoj funkciji - na izlazu dobijamo raščlanjeno sintaktičko stablo u obliku JSON objekta.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Sada možemo proći kroz ovo stablo u suprotnom smjeru i sastaviti zahtjev sa uvlakama, bojama, formatiranjem koje želimo. Ne, ovo nije podesivo, ali smo mislili da bi to bilo zgodno.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Mapiranje čvorova upita i plana

Sada da vidimo kako možemo kombinirati plan koji smo analizirali u prvom koraku i upit koji smo analizirali u drugom.

Uzmimo jednostavan primjer - imamo zahtjev koji formira CTE i čita ga dvaput. On pravi takav plan.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

CTE

Ako pažljivo pogledate, to prije 12. verzije (ili počevši od nje s ključnom riječi MATERIALIZED) formiranje CTE je bezuslovna prepreka za planer.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

A to znači, ako negdje u zahtjevu vidimo generiranje CTE-a, a negdje u planu čvor CTE, onda se ovi čvorovi jedinstveno "bore" među sobom, možemo ih odmah kombinirati.

Zadatak "sa zvjezdicom"Napomena: CTE-ovi mogu biti ugniježđeni.
PostgreSQL Query Profiler: Kako uskladiti plan i upit
Ima ih vrlo slabo ugniježđenih, pa čak i istog imena. Na primjer, možete unutra CTE A napraviti CTE X, i na istom nivou iznutra CTE B uradi ponovo CTE X:

WITH A AS (
  WITH X AS (...)
  SELECT ...
)
, B AS (
  WITH X AS (...)
  SELECT ...
)
...

Kada upoređujete, ovo morate razumjeti. Vrlo je teško to shvatiti "očima" - čak i kada se vidi plan, čak i kada se vidi tijelo zahtjeva. Ako je vaša CTE generacija složena, ugniježđena, zahtjevi su veliki, onda je potpuno nesvjesno.

UNION

Ako imamo ključnu riječ u zahtjevu UNION [ALL] (operator spajanja dva uzorka), tada na planu odgovara bilo kojem čvoru Append, ili nešto Recursive Union.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

ono što je "iznad" UNION - ovo je prvo dijete našeg čvora, koji je "odozdo" - drugi. Ako prođe UNION onda smo "zalijepili" nekoliko blokova odjednom Append-čvor će i dalje imati samo jedno, ali neće imati dvoje djece, već mnogo - redom kako idu, redom:

  (...) -- #1
UNION ALL
  (...) -- #2
UNION ALL
  (...) -- #3

Append
  -> ... #1
  -> ... #2
  -> ... #3

Zadatak "sa zvjezdicom": unutar generacije rekurzivnog preuzimanja (WITH RECURSIVE) također može biti više od jednog UNION. Ali samo poslednji blok posle poslednjeg je uvek rekurzivan UNION. Sve iznad je jedno ali drugačije UNION:

WITH RECURSIVE T AS(
  (...) -- #1
UNION ALL
  (...) -- #2, тут кончается генерация стартового состояния рекурсии
UNION ALL
  (...) -- #3, только этот блок рекурсивный и может содержать обращение к T
)
...

Takve primjere također treba moći "nalijepiti". U ovom primjeru to vidimo UNION-segmenti u našem zahtjevu su bili 3 komada. Shodno tome, jedan UNION odgovara Append-čvor, a drugi - Recursive Union.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Čitanje-pisanje podataka

Sve, postavljeno, sada znamo koji dio zahtjeva odgovara kojem dijelu plana. I u ovim komadima možemo lako i prirodno pronaći one objekte koji su „čitljivi“.

Sa stanovišta upita, ne znamo da li je ovo tabela ili CTE, ali su označeni istim čvorom RangeVar. A u "čitljivom" planu, ovo je također prilično ograničen skup čvorova:

  • Seq Scan on [tbl]
  • Bitmap Heap Scan on [tbl]
  • Index [Only] Scan [Backward] using [idx] on [tbl]
  • CTE Scan on [cte]
  • Insert/Update/Delete on [tbl]

Znamo strukturu plana i zahtjeva, znamo korespondenciju blokova, znamo imena objekata - pravimo nedvosmisleno poređenje.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Opet zadatak "sa zvjezdicom". Uzimamo zahtjev, izvršavamo ga, nemamo pseudonime - samo ga dvaput čitamo iz jednog CTE-a.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Pogledajmo plan - u čemu je problem? Zašto smo imali pseudonim? Mi to nismo naručili. Odakle mu toliki "broj"?

PostgreSQL ga sam dodaje. Samo to treba da shvatite upravo takav alias za nas, za potrebe poređenja sa planom, to nema nikakvog smisla, jednostavno se dodaje ovdje. Ne obraćajmo pažnju na njega.

Drugi zadatak "sa zvjezdicom": ako čitamo iz particionirane tablice, onda ćemo dobiti čvor Append ili Merge Append, koji će se sastojati od velikog broja "djece", a od kojih će svako biti po neko Scan'om iz tabele-sekcije: Seq Scan, Bitmap Heap Scan ili Index Scan. Ali, u svakom slučaju, ova „djeca“ neće biti složeni upiti - po tome se ovi čvorovi mogu razlikovati od Append at UNION.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Razumijemo i takve čvorove, skupljamo ih "na jednu hrpu" i kažemo: "sve što čitate iz megatable-a nalazi se ovdje i niz drvo".

"Jednostavni" čvorovi za prikupljanje podataka

PostgreSQL Query Profiler: Kako uskladiti plan i upit

Values Scan u planu odgovara VALUES u zahtjevu.

Result je zahtjev bez FROM kao SELECT 1. Ili kada imate lažan izraz WHERE-block (tada se pojavljuje atribut One-Time Filter):

EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- или 0 = 1

Result  (cost=0.00..0.00 rows=0 width=230) (actual time=0.000..0.000 rows=0 loops=1)
  One-Time Filter: false

Function Scan "Mapyatsya" na istoimenom SRF-u.

Ali s ugniježđenim upitima sve je složenije - nažalost, oni se ne pretvaraju uvijek u InitPlan/SubPlan. Ponekad se pretvore u ... Join ili ... Anti Join, pogotovo kada napišete nešto slično WHERE NOT EXISTS .... I tu nije uvijek moguće kombinirati - u tekstu plana nema operatora koji odgovaraju čvorovima plana.

Opet zadatak "sa zvjezdicom": neki VALUES u zahtjevu. U ovom slučaju iu planu dobit ćete nekoliko čvorova Values Scan.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Sufiksi "broja" pomoći će da se razlikuju jedni od drugih - dodaju se točno onim redoslijedom kojim odgovaraju VALUES-blokovi u toku zahtjeva od vrha do dna.

Obrada podataka

Čini se da je sve u našem zahtjevu sređeno - samo Limit.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Ali ovdje je sve jednostavno - takvi čvorovi kao Limit, Sort, Aggregate, WindowAgg, Unique Oni „mapiraju“ jedan-na-jedan sa odgovarajućim operaterima u zahtjevu, ako su tamo. Nema "zvezda" i nema poteškoća.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

JOIN

Poteškoće nastaju kada želimo da kombinujemo JOIN između sebe. To nije uvijek moguće, ali je moguće.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Sa tačke gledišta analizatora upita, imamo čvor JoinExpr, koja ima tačno dvoje djece - lijevo i desno. To je, odnosno, ono što je "iznad" vašeg JOIN-a i ono što je napisano "ispod" u upitu.

A sa stanovišta plana, ovo su dva potomka nekih * Loop/* Join-čvor. Nested Loop, Hash Anti Join...je tako nešto.

Upotrijebimo jednostavnu logiku: ako imamo tablice A i B koje se međusobno "pridružuju" u planu, onda bi u upitu mogle biti locirane ili A-JOIN-B, ili B-JOIN-A. Pokušajmo ovako kombinirati, pokušati kombinirati na suprotan način, i tako sve dok ne ponestane takvih parova.

Uzmite naše stablo sintakse, uzmite naš plan, pogledajte ih... ne izgleda tako!
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Precrtajmo ga u obliku grafikona - o, nešto je već postalo slično nečemu!
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Primijetimo da imamo čvorove koji imaju djecu B i C u isto vrijeme – nije nam bitno kojim redoslijedom. Kombinirajmo ih i okrenimo sliku čvora.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Pogledajmo ponovo. Sada imamo čvorove sa djecom A i parove (B + C) - kompatibilne s njima.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Odlično! Ispostavilo se da smo mi ovo dvoje JOIN iz upita sa čvorovima plana su uspješno kombinovani.

Nažalost, ovaj problem nije uvijek riješen.
PostgreSQL Query Profiler: Kako uskladiti plan i upit

Na primjer, ako je zahtjev A JOIN B JOIN C, a u planu su prije svega spojeni “ekstremni” čvorovi A i C. Ali tog operatora u upitu nema, nemamo šta da istaknemo, nemamo za šta vezati nagoveštaj. Isto sa "zarezom" kada pišete A, B.

Ali, u većini slučajeva, skoro svi čvorovi uspevaju da se „odvezu“ i na vreme dobiju takvo profilisanje na levoj strani – doslovno, kao u Google Chrome-u, kada analizirate JavaScript kod. Možete vidjeti koliko dugo je svaki red i svaki izraz "izvršen".
PostgreSQL Query Profiler: Kako uskladiti plan i upit

A kako bismo vam olakšali korištenje svega ovoga, napravili smo skladište arhiva, gdje možete sačuvati, a zatim pronaći svoje planove zajedno sa povezanim upitima ili podijeliti vezu s nekim.

Ako samo treba da nečitljiv zahtjev dovedete u adekvatan oblik, koristite naš "normalizator".

PostgreSQL Query Profiler: Kako uskladiti plan i upit

izvor: www.habr.com

Dodajte komentar