PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Mnohí, ktorí už využívajú explain.tensor.ru — našu službu vizualizácie plánov PostgreSQL, možno nevedia o jednej z jej super schopností — prevádzať ťažko čitateľný úryvok z logu servera...

PostgreSQL Query Profiler: ako zlúčiť plán a dopyt
… na pekne naformulovaný dotaz s kontextovými nápovedami k príslušným uzlom plánu:

PostgreSQL Query Profiler: ako zlúčiť plán a dopyt
V tomto rozšírení druhej časti svojho prednášky na PGConf.Russia 2020 poviem, ako sa nám to podarilo.

S prepisom prvej časti, venujúcej sa typickým problémom výkonu dotazov a ich riešeniam, sa môžete oboznámiť v článku „Recepty pre choré SQL dotazy“.


Prehrajte video

Najprv sa zameriame na farebné zvýraznenie — a farebne zvýrazníme už nie plán, ten sme už ozdobili, je pre nás už pekný a zrozumiteľný, ale dotaz.

Zdalo sa nám, že takto neformátovaná „plachta“ vytiahnutá z logu vyzerá veľmi škaredo a preto — je nepríjemná.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Obzvlášť, keď vývojári v kóde „lepia“ telo dotazu (to je samozrejme antipattern, ale stáva sa to) do jedného riadku. Hrozný pohľad!

Poďme to nakresliť nejak krajšie.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

A ak to vieme nakresliť pekne, teda rozobrať a znovu poskladať telo dotazu, potom môžeme každý objekt tohto dotazu „pripojiť“ nápovedu — čo sa udialo v príslušnom bode plánu.

Syntaxové strom dotazu

Aby sme to dosiahli, musíme najprv dotaz rozobrať.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Keďže naše jádro systému beží na NodeJS, vytvorili sme pre neho modul, môžete nájsť ho na GitHub. V skutočnosti, to je rozšírené „bindingy“ k vnútornostiam parseru samotného PostgreSQL. Teda jednoducho binárne skompilovaná gramatika s bindingmi zo strany NodeJS. Vzali sme za základ cudzie moduly — nie je tu žiadne veľké tajomstvo.

Predložíme telo dotazu ako vstup našej funkcii — na výstupe dostaneme rozobraté syntaxové strom vo forme JSON objektu.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Teraz po tomto strome môžeme prebehnúť zpätne a poskladať dotaz s tými odsadenia, farebným zvýraznením a formátovaním, ktoré chceme. Nie, nie je to konfigurovateľné, ale zdalo sa nám, že týmto spôsobom to bude pohodlné.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Mappiovanie uzlov dotazu a plánu

Teraz sa pozrieme, ako môžeme spojiť plán, ktorý sme rozobrali v prvom kroku, a dotaz, ktorý sme rozobrali v druhom.

Zoberme si jednoduchý príklad — máme dopyt, ktorý vytvára CTE a dvakrát z nej číta. Generuje takýto plán.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

CTE

Ak sa na to pozrieme pozorne, pred 12. verziou (alebo od nej s kľúčovým slovom MATERIALIZED) vytváranie CTE predstavuje bezpodmienečnú prekážku pre plánovača.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

To znamená, že ak niekde v dopyte vidíme generáciu CTE a niekde v pláne uzol CTE, tieto uzly sú si navzájom 'rovné', môžeme ich hneď spojiť.

Úloha so hviezdičkou: CTE môžu byť vnorené.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt
Môžu byť veľmi zle vnorené, a dokonca rovnomenné. Napríklad, môžete vo vnútri CTE A urobiť CTE X, a na tej istej úrovni vo vnútri CTE B urobiť opäť CTE X:

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

Pri porovnávaní by ste to mali chápať. Rozumieť tomu 'očami' — aj vidieť plán, aj vidieť telo dopytu — je veľmi ťažké. Ak máte zložitú, vnorenú generáciu CTE, veľké dopyty — potom sa to úplne stáva nevedomým.

operátor podporujú

Ak máme v dopyte kľúčové slovo UNION [ALL] (operátor spojenia dvoch výberov), potom v pláne mu zodpovedá buď uzol Pridať, alebo nejaký Rekurzívna únia.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

To, čo 'navrchu' nad operátor podporujú — je prvý potomok nášho uzla, čo 'dole' — druhý. Ak sú cez operátor podporujú nás 'prilepené' niekoľko blokov naraz, potom Pridať-uzol bude stále len jeden, ale detí bude nie dve, ale veľa — podľa poradia, ako prichádzajú:

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

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

Úloha so hviezdičkou: vo vnútri generovania rekurzívneho výberu (WITH RECURSIVE) môže byť tiež viac než jeden operátor podporujú. Ale vždy rekurzívny je len posledný blok po poslednom operátor podporujú. Všetko, čo je vyššie — je jeden, ale iný operátor podporujú:

WITH RECURSIVE T AS(
  (...) -- #1
UNION ALL
  (...) -- #2, tu končí generácia štartovacieho stavu rekurzie
UNION ALL
  (...) -- #3, len tento blok je rekurzívny a môže obsahovať odvolanie na T
)
...

Takéto príklady treba tiež vedieť 'prilepovať'. Tu v tomto príklade vidíme, že operátor podporujú-segmentov v našom dopyte bolo 3 kusy. Preto jednému operátor podporujú zodpovedá Pridať-uzlu, a druhému — Rekurzívna únia.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Čítanie-zapisovanie dát

Všetko, rozložili sme, teraz vieme, ktorý kúsok dopytu zodpovedá ktorému kúsku plánu. A v týchto kúskoch môžeme ľahko a bez námahy nájsť tie objekty, ktoré 'sa čítajú'.

Z pohľadu dopytu nevieme — či je to tabuľka alebo CTE, ale označujú sa rovnakým uzlom Rozsahová premenná. A v pláne „číta sa“ — to je tiež dosť obmedzená sada uzlov:

  • Sekvenčné skenovanie na [tbl]
  • Bitmapová hromadná kontrola na [tbl]
  • Index [Only] Scan [Backward] using [idx] on [tbl]
  • CTE skenovanie na [cte]
  • Vložiť/Aktualizácia/Delete na [tbl]

Štruktúru plánu a dopytu poznáme, zhodu blokov poznáme, mená objektov poznáme — robíme jednoznačné zaradenie.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Opäť raz úloha „so hviezdičkou“. Berieme dopyt, vykonávame, nemáme žiadne aliasy — jednoducho dvakrát čítame z jednej CTE.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Pozrime sa do plánu — čo sa deje? Prečo sa nám alias objavil? Nepýtali sme sa na neho. Odkiaľ je taký „numerický“?

PostgreSQL ho pridáva samo. Musíme len chápať, že presne taký alias pre nás na účely zaradenia s plánom nemá žiadny význam, je tu jednoducho pridaný. Nevenujme mu pozornosť.

Druhá úloha „so hviezdičkou“: ak ideme čítať zo sekcionovanej tabuľky, dostaneme uzol Pridať alebo Zlúčiť Pripojiť, ktorý sa bude skladať z veľkého množstva „detí“, z ktorých každé bude nejakým Scan‘om z tabuľky-sekcie: Sekvenčné skenovanie, Bitmapová hromadná kontrola alebo Index Scan. Ale v každom prípade tieto „deti“ nebudú zložitými dopytmi — takto sa dajú rozlíšiť tieto uzly od Pridať pri operátor podporujú.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Takéto uzly tiež chápeme, zbierame ich „do jednej kopy“ a hovoríme: "všetko, čo si čítal z megatable — to je tu a smerom nadol po strome".

„Jednoduché“ uzly získavania dát

PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Skenovanie hodnôt v pláne zodpovedajú HODNOTY v dopyte.

Výsledok — je to dopyt bez FROM akoby SELECT 1. Alebo keď máte vopred falošný výraz v WHERE-bloku (vtedy vzniká atribút One-Time Filter):

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

Výsledok (náklady=0.00..0.00 riadky=0 šírka=230) (skutočný čas=0.000..0.000 riadky=0 slučky=1)
  One-Time Filter: false

Funkcia skenovania „mapujú“ sa na rovnomenované SRF.

Ale s vnorenými dopytmi je to zložitejšie — žiaľ, nie vždy sa menia na Základný plán/SubPlán. Niekedy sa menia na ... Join alebo ... Anti Join, najmä keď píšete niečo ako WHERE NOT EXISTS .... A tam kombinovať sa nie vždy dá — v texte plánu nie sú zodpovedajúce uzlom plánu operátori.

Opäť raz úloha „so hviezdičkou“: niekoľko HODNOTY v dopyte. V tomto prípade a v pláne dostanete niekoľko uzlov Skenovanie hodnôt.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Rozlíšiť ich jeden od druhého pomôžu „číslicové“ prídavky — pridáva sa presne v poradí, v akom sa nájdu zodpovedajúce HODNOTY-bloky popri dopyte zhora nadol.

Spracovanie údajov

Akoby všetko v našom dopyte rozobrali — ostal len Obmedzenie.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Ale tu je to jednoduché — také uzly ako Obmedzenie, Tried, Agregovať, WindowAgg, Unique „mapujú“ sa jeden na jeden na zodpovedajúce operátory v dopyte, ak tam sú. Tu nie sú žiadne „hviezdičky“ a zložitosti.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

JOIN

Zložitosti vznikajú, keď chceme skombinovať JOIN medzi sebou. To nie je vždy možné, ale dá sa to.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Z pohľadu parsera dotazu máme uzol PripojiťExpr, ktorý má presne dve potomkov — ľavý a pravý. To je, podľa toho, čo je „nad“ vaším JOIN a to, čo je „pod“ ním v dotaze.

A z pohľadu plánu sú to dve potomkovia nejakého * Cyklus/* Pripojiť-uzla. Zabudovaný cyklus, Hash Anti Join,… — niečo také.

Vyjdeme z jednoduché logiky: ak máme tabuľky A a B, ktoré sa „jounujú“ medzi sebou v pláne, môžu byť v dotaze usporiadané buď A-JOIN-B, alebo B-JOIN-A. Pokúsime sa ich zlúčiť tak, pokúsime sa ich zlúčiť naopak a takto, kým sa tieto páry neskončia.

Vezmeme naše syntaktické strom, vezmeme náš plán, pozrieme sa na ne… nevyzerá to!
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Prekreslime v podobe grafov — oh, už to vyzerá ako niečo!
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Poďme si všimnúť, že máme uzly, ktoré majú súčasne deti B a C — nezáleží nám na poradí. Zlúčime ich a otočíme obrázok uzla.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Pozrime sa znova. Teraz máme uzly s deťmi A a párom (B + C) — zlúčime aj ich.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Výborne! Vychádza to tak, že sme tie dve JOIN z dotazu s uzlami plánu úspešne spojili.

Žiaľ, táto úloha nie je vždy riešiteľná.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Napríklad, ak v dotaze A JOIN B JOIN C, a v pláne sa v prvom rade spojili „okrajové“ uzly A a C. A v dotaze nie je taký operátor, nemáme čo zvýrazniť, nemáme sa na čo pripojiť. To isté s „čiarkou“, keď píšete A, B.

No, vo väčšine prípadov sa takmer všetky uzly dajú „rozviazať“ a získať takýto profilovanie naľavo podľa času — doslova, ako v Google Chrome, keď analyzujete kód na JavaScripte. Vidíte, koľko času každé riadok a každý operátor „bežali“.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

A aby vám to všetko bolo pohodlnejšie, vytvorili sme úložisko archívu, kde si môžete uložiť a potom nájsť svoje plány spolu s asociovanými dotazmi alebo zdieľať niekomu odkaz.

Ak potrebujete len uviesť nečitateľný dotaz do adekvátneho stavu, využite náš „normalizátor“.

PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster