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

Mnohí, ktorí už používajú explain.tensor.ru — našu službu vizualizácie plánov PostgreSQL, si možno nie sú vedomí jednej z jej super schopností — premieňať ťažko čitateľný kus logu servera…

PostgreSQL Query Profiler: ako zlúčiť plán a dopyt
… na pekne formátovaný dotaz s kontextovými nápovedami pre príslušné uzly plánu:

PostgreSQL Query Profiler: ako zlúčiť plán a dopyt
V tejto interpretácii druhej časti svojej prednášky na PGConf.Russia 2020 povediem, ako sa nám to podarilo.

S prepisom prvej časti, ktorá sa týka typických problémov výkonnosti dotazov a ich riešení, sa môžete zoznámiť v článku „Recepty pre choré SQL-dotazy“.


Prehrajte video

Najprv sa zameriame na farebnenie — a farebnenie budeme robiť už nie plánu, ten sme už vyfarbili, je pre nás už pekný a zrozumiteľný, ale dotazu.

Zdalo sa nám, že takto neformátovaná „plachta“ vytrhnutá z logu dotazu vyzerá veľmi nepekné a preto — nepohodlne.
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) do jedného riadku. Hrôza!

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

A ak by sme to mohli nakresliť pekne, t.j. rozobrať a opäť zložiť telo dotazu, tak by sme potom mohli k každému objektu tohto dotazu „pripevniť“ nápovedu — čo sa dialo v príslušnom bode plánu.

Syntaktické strom dotazu

Aby sme to urobili, dotaz treba najprv rozobrať.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Keďže, u nás jadro systému pracuje na NodeJS, tak sme urobili k nemu maličký modul, môžete nájsť ho na GitHub. Naozaj, toto sú rozšírené „bindingy“ k vnútornostiam parsera samotného PostgreSQL. T.j. jednoducho binárne skompilovaná gramatika a k nej sú urobené bindingy zo strany NodeJS. Vzali sme za základ cudzie moduly — tu nie je veľké tajomstvo.

Krmiame telo dotazu na vstup našej funkcie — na výstupe dostaneme rozobraté syntaktické strom v podobe JSON-objektu.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Teraz sa po tomto strome dá prebehnúť naspäť a poskladať dotaz s tými odsadenými, farebnením, formátovaním, ktoré chceme. Nie, to nie je nastaviteľné, ale zdalo sa nám, že práve takto to bude pohodlné.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Priradenie uzlov dotazu a plánu

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

Zoberme si jednoduchý príklad — máme dotaz, 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 pozriete pozorne, pred 12. verziou (alebo začínajúc s ňou s kľúčovým slovom MATERIALIZED) tvorba CTE je nepochybnou prekážkou pre plánovač.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

A, to znamená, že ak niekde v dotaze vidíme generáciu CTE a niekde v pláne uzol CTE, tak tieto uzly sa určite "bijú", môžeme ich hneď zlúčiť.

Úloha s 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 rovnomenne. 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í to musíte chápať. Pochopiť to "očami" — aj keď vidíte plán, aj keď vidíte telo dotazu — je veľmi ťažké. Ak máte zložitú, vnorenú generáciu CTE, veľké dotazy — potom to je úplne nevedomé.

operátor podporujú

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

To, čo "zhora" nad operátor podporujú — to je prvý potomok nášho uzla, čo "zdola" — druhý. Ak cez operátor podporujú máme "prilepené" niekoľko blokov naraz, tak Pridať-uzol bude stále len jeden, ale detí bude mať nie dva, ale mnoho — po poradí, ako idú, zodpovedajúco:

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

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

Úloha s hviezdičkou: vo vnútri generácie rekurzívneho výberu (WITH RECURSIVE) môže byť tiež viac ako jeden. operátor podporujú. Ale vždy rekurzívny je iba posledný blok po poslednom operátor podporujú. Všetko, čo je hore — to je jeden, ale iný operátor podporujú:

WITH RECURSIVE T AS(
  (...) -- #1
UNION ALL
  (...) -- #2, tu sa končí generácia počiatočného stavu rekurzie
UNION ALL
  (...) -- #3, iba tento blok je rekurzívny a môže obsahovať odkaz na T
)
...

Takéto príklady musíte vedieť "rozlepovať". Tu v tomto príklade vidíme, že operátor podporujú-segmentov v našom dotaze bolo 3 kusy. Zodpovedajúce, 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-zápis údajov

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

Z pohľadu dotazu nevieme — či ide o tabuľku alebo CTE, ale označujú sa rovnakým uzlom. Rozsahová premenná. A v pláne „čítať“ — to tiež predstavuje dosť obmedzenú skupinu 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, zaradenie blokov poznáme, mená objektov poznáme — robíme jednoznačné priradenie.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

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

Pozeráme do plánu — čo je zle? Prečo nám vyskočil alias? Nežiadali sme ho. Odkiaľ sa vzal taký „číslený“?

PostgreSQL ho pridáva samo. Treba si len uvedomiť, že taký alias pre nás na účely priradenia k plánu nemá žiadny zmysel, bol tu jednoducho pridaný. Nevenujme mu pozornosť.

Druhá úloha „so hviezdičkou“: ak čítame zo sekciou rozdelené tabuľky, dostaneme uzol Pridať alebo Zlúčiť Pripojiť, ktorý bude obsahovať veľké množstvo „detí“, a každé z nich bude nejaké 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 tieto uzly dajú rozlíšiť 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 jednoho balíka“ a hovoríme: "všetko, čo si čítal z megatable — to je tu a nižšie v strome".

„Jednoduché“ uzly získavania dát

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

Skenovanie hodnôt v pláne zodpovedá HODNOTY v dopyte.

Výsledok — to je dopyt bez FROM akoby SELECT 1. Alebo keď máte zakaždým nepravdivý výraz v WHERE-bloku (potom 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 riadkov=0 šírka=230) (skutočný čas=0.000..0.000 riadkov=0 slučiek=1)
  One-Time Filter: false

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

Ale so zloženými dopytmi je to ťažšie — žiaľ, neprevádzajú sa vždy na Základný plán/SubPlán. Niekedy sa prevádzajú na ... Join alebo ... Anti Join, najmä keď napíšete niečo ako WHERE NOT EXISTS .... A tam nie je vždy možné ich kombinovať — v texte plánu nie sú zodpovedajúce uzlom plánu operátorov.

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

Rozlíšiť ich navzájom pomôžu „číslené“ prípony — pridávajú sa presne v poradí nájdenia zodpovedajúcich HODNOTY-blokov počas dopytu zhora nadol.

Spracovanie údajov

Akoby sme všetko v našom dopyte rozobrali — ostáva 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-za-jedným 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ď sa chceme skombinovať JOIN navzájom. Urobiť to nie je vždy možné, ale dá sa.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

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

A z pohľadu plánu sú to dva potomkov nejakej * Cyklus/* Pripojiť-uzla. Vnorená slučka, Hash Anti Join,… — niečo také.

Použijeme jednoduchú logiku: ak máme tabuľky A a B, ktoré „sa spoja“ v pláne, mohli by byť v dotaze umiestnené buď A-JOIN-B, alebo B-JOIN-A. Skúsime to kombinovať takto, skúsime to otočiť a takto pokračujeme, kým sa také páry neskončia.

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

Prekreslíme to ako grafy — o, už to začína vyzerať ako niečo!
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Dáva nám pozor, že máme uzly, ktoré majú zároveň deti B a C — nezáleží nám na poradí. Skombinujeme ich a otočíme kresbu 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) — skombinujeme aj tie.
PostgreSQL Query Profiler: ako zlúčiť plán a dopyt

Skvelé! To znamená, že sme úspešne skombinovali tieto dva JOIN z dotazu s uzlami plánu.

Ž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 najprv spojili „extrémne“ uzly A a C. A v dotaze nie je taký operátor, nemáme čo zvýrazniť, nie je k čomu pripojiť návod. To isté platí pre „čiarku“, keď píšete A, B.

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

A aby ste sa s tým všetkým ľahšie používali, vytvorili sme úložisko archívu, kde si môžete uložiť a neskôr nájsť svoje plány spolu s priradenými dotazmi alebo sa s niekým podeliť o odkaz.

Ak potrebujete len premeniť nečitateľný dotaz na primeranú podobu, 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