
V moderných CPU je veľa jadier. Rony sa aplikácie zasielali požiadavky na databázy paralelne. Ak ide o reportovú požiadavku na množstvo riadkov v tabuľke, vykoná sa rýchlejšie, keď sú zapojené viaceré CPU, a v PostgreSQL je to možné od verzie 9.6.
Trvalo 3 roky, aby sa implementovala funkcia paralelných požiadaviek — museli sme prepísať kód v rôznych fázach vykonávania požiadaviek. V PostgreSQL 9.6 sa objavila infraštruktúra pre ďalšie zlepšenie kódu. V nasledujúcich verziách sa aj iné typy požiadaviek vykonávajú paralelne.
Obmedzenia
- Nezapojujte paralelné spracovanie, ak sú všetky jadrá už obsadené, inak budú ostatné požiadavky spomaľovať.
- Najdôležitejšie je, že paralelné spracovanie s vysokými hodnotami WORK_MEM použije veľa pamäte — každé hašovanie alebo triedenie zaberá pamäť v objeme work_mem.
- OLTP požiadavky s nízkou latenciou nemožno urýchliť paralelným vykonávaním. A ak požiadavka vracia jeden riadok, paralelné spracovanie ju iba spomalí.
- Developeri radi používajú benchmark TPC-H. Možno máte podobné požiadavky na ideálne paralelné spracovanie.
- Iba SELECT požiadavky bez predikátneho uzamknutia sú vykonávané paralelne.
- Niekedy je správna indexácia lepšia ako sekvenčné skenovanie tabuľky v paralelnom režime.
- Pozastavenie požiadaviek a kurzory nie sú podporované.
- Okno a agregátne funkcie usporiadaných množín nie sú paralelné.
- V pracovnej záťaži vstup-nozadu nič nezískavate.
- Paralelné algoritmy triedenia neexistujú. Ale požiadavky s triedením môžu byť vykonávané paralelne v niektorých aspektoch.
- Nahraďte CTE (WITH …) vnoreným SELECT, aby ste zapojili paralelné spracovanie.
- Obaly externých dát zatiaľ nepodporujú paralelné spracovanie (a mohli by!).
- FULL OUTER JOIN nie je podporovaný.
- max_rows zakazuje paralelné spracovanie.
- Ak je vo vašej požiadavke funkcia, ktorá nie je označená ako PARALLEL SAFE, bude jednoprvková.
- Úroveň izolácie transakcie SERIALIZABLE zakazuje paralelné spracovanie.
Testovacia environ
Developeri PostgreSQL sa snažili skrátiť čas odozvy požiadaviek benchmarku TPC-H. Stiahnite si benchmark a . Toto nie je oficiálne použitie benchmarku TPC-H — nie je na porovnávanie databáz alebo hardvéru.
- Stiahnite TPC-H_Tools_v2.17.3.zip (alebo novšiu verziu) .
- Premenovať makefile.suite na Makefile a zmeniť, ako je uvedené tu: . Skontrolujte kód príkazom make.
- Generujte dáta:
./dbgen -s 10vytvorí databázu s kapacitou 23 GB. To bude stačiť, aby ste videli rozdiel v výkonnosti paralelných a neparalelných dotazov. - Konvertujte súbory
tblvcsv s forased. - Klonujte repozitár
pg_tpcha skopírujte súborycsvvpg_tpch/dss/data. - Vytvorte dotazy príkazom
qgen. - Načítajte dáta do databázy príkazom
./tpch.sh.
Paralelné sekvenčné skenovanie
Môže byť rýchlejšie nie kvôli paralelnému čítaniu, ale preto, že dáta sú rozložené na viacerých CPU jadrách. V moderných OS sú súbory dát PostgreSQL dobre cache-ované. S predvídavým čítaním môžete z úložiska získať viac blokov, ako požaduje démon PG. Preto nie je výkon dotazu obmedzený I/O disku. Spotrebúva to cykly CPU na:
- čítanie riadkov jeden po druhom zo strán tabuľky;
- porovnávanie hodnôt riadkov a podmienok
WHERE.
Vykonajme jednoduchý dotaz select:
tpch=# explain analyze select l_quantity as sum_qty from lineitem where l_shipdate <='1998-12-01' - interval '105' day;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
Seq Scan on lineitem (cost=0.00..1964772.00 rows=58856235 width=5) (akt. čas=0.014..16951.669 riadkov=58839715 slučiek=1)
Filter: (l_shipdate <='1998-08-18 00:00:00'::timestamp bez časovej zóny)
Riadky odstránené filtrom: 1146337
Čas plánovania: 0.203 ms
Čas vykonania: 19035.100 msSekvenčné skenovanie poskytuje príliš veľa riadkov bez agregácie, takže dotaz sa vykonáva jedným CPU jadrom.
Ak pridáme SUM(), je vidieť, že dva pracovné procesy pomôžu urýchliť dotaz:
explain analyze select sum(l_quantity) as sum_qty from lineitem where l_shipdate Gather (cost=1589701.91..1589702.12 rows=2 width=32) (akt. čas=8553.241..8555.067 riadkov=3 slučiek=1)
Naplánovaní pracovníci: 2
Spustené pracovníci: 2
-> Partial Aggregate (cost=1588701.91..1588701.92 rows=1 width=32) (akt. čas=8547.546..8547.546 riadkov=1 slučiek=3)
-> Paralelné sekvenčné skenovanie na lineitem (cost=0.00..1527393.33 rows=24523431 width=5) (akt. čas=0.038..5998.417 riadkov=19613238 slučiek=3)
Filter: (l_shipdate <='1998-08-18 00:00:00'::timestamp bez časovej zóny)
Riadky odstránené filtrom: 382112
Čas plánovania: 0.241 ms
Čas vykonania: 8555.131 msParalelná agregácia
Uzol „Paralelné sekvenčné skenovanie“ produkuje riadky pre čiastočnú agregáciu. Uzol „Čiastočná agregácia“ týmto riadkom odoberá SUM(). Na konci sa súčet SUM z každého pracovného procesu zhromažďuje uzlom „Zoskupenie“.
Konečný výsledok sa počíta uzlom „Finalize Aggregate“. Ak máte vlastné agregácie, nezabudnite ich označiť ako „parallel safe“.
Počet pracovných procesov
Počet pracovných procesov je možné zvýšiť bez reštartovania servera:
explain analyze select sum(l_quantity) as sum_qty from lineitem where l_shipdate Gather (cost=1589701.91..1589702.12 rows=2 width=32) (akt. čas=8553.241..8555.067 riadkov=3 slučiek=1)
Naplánovaní pracovníci: 2
Spustené pracovníci: 2
-> Partial Aggregate (cost=1588701.91..1588701.92 rows=1 width=32) (akt. čas=8547.546..8547.546 riadkov=1 slučiek=3)
-> Paralelné sekvenčné skenovanie na lineitem (cost=0.00..1527393.33 rows=24523431 width=5) (akt. čas=0.038..5998.417 riadkov=19613238 slučiek=3)
Filter: (l_shipdate <='1998-08-18 00:00:00'::timestamp bez časovej zóny)
Riadky odstránené filtrom: 382112
Čas plánovania: 0.241 ms
Čas vykonania: 8555.131 msČo sa tu deje? Počet procesov sa zvýšil dvojnásobne a dopyt sa stal len 1,6599-krát rýchlejším. Výpočty sú zaujímavé. Mali sme 2 pracovné procesy a 1 lídra. Po zmene sa to zmenilo na 4+1.
Naše maximálne zrýchlenie vďaka paralelnému spracovaniu: 5/3 = 1,66(6) krát.
Ako to funguje?
Procesy
Vykonanie dopytu vždy začína od vedúceho procesu. Lídrova robí všetku neprenášateľnú a časť prenosnej práce. Ostatné procesy, ktoré vykonávajú rovnaké dopyty, sa nazývajú pracovné procesy. Paralelné spracovanie využíva infraštruktúru (od verzie 9.4). Keďže iné časti PostgreSQL používajú procesy a nie vlákna, dopyt s 3 pracovnými procesmi mohol byť 4-krát rýchlejší ako tradičné spracovanie.
Interakcia
Pracovné procesy komunikujú s lídrom prostredníctvom fronty správ (na báze zdieľanej pamäte). Každý proces má 2 fronty: na chyby a na tuple.
Koľko pracovných procesov je potrebných?
Minimálne obmedzenie je stanovené parametrom . Potom vykonávateľ dopytov berie pracovné procesy z poolu, obmedzeného parametrom . Posledné obmedzenie je , teda celkový počet pozadí procesov.
Ak sa nepodarí získať pracovný proces, spracovanie bude jednoprosesorové.
Planner dopytov môže skrátiť pracovné procesy v závislosti od veľkosti tabuľky alebo indexu. Na to existujú parametre a .
set min_parallel_table_scan_size='8MB'
8MB tabuľka => 1 pracovník
24MB tabuľka => 2 pracovníci
72MB tabuľka => 3 pracovníci
x => log(x / min_parallel_table_scan_size) / log(3) + 1 pracovníkKaždýkrát, keď je tabuľka 3-krát väčšia ako min_parallel_(index|table)_scan_size, Postgres pridá pracovný proces. Počet pracovných procesov nie je založený na nákladoch. Cyklické závislosti komplikujú zložitú implementáciu. Namiesto toho sa planner riadi jednoduchými pravidlami.
V praxi tieto pravidlá nie sú vždy vhodné pre produkciu, takže je možné zmeniť počet pracovných procesov pre konkrétnu tabuľku: ALTER TABLE … SET (parallel_workers = N).
Prečo sa paralelné spracovanie nepoužíva?
Okrem dlhého zoznamu obmedzení sú tu aj kontroly nákladov:
— aby sa predišlo paralelnému spracovaniu krátkych dopytov. Tento parametre odhaduje čas na prípravu pamäte, spustenie procesu a počiatočnú výmenu údajov.
: komunikácia lídra s pracovníkmi môže trvať úmerne k počtu kôp od pracovných procesov. Tento parameter počíta náklady na výmenu údajov.
Spojenia vnorených cyklov — Nested Loop Join
PostgreSQL 9.6+ môže vykonávať vnorené cykly paralelne — to je jednoduchá operácia.
explain (costs off) select c_custkey, count(o_orderkey)
from customer left outer join orders on
c_custkey = o_custkey and o_comment not like '%specialposits%'
group by c_custkey;
QUERY PLAN
--------------------------------------------------------------------------------------
Finalize GroupAggregate
Group Key: customer.c_custkey
-> Gather Merge
Workers Planned: 4
-> Partial GroupAggregate
Group Key: customer.c_custkey
-> Nested Loop Left Join
-> Parallel Index Only Scan using customer_pkey on customer
-> Index Scan using idx_orders_custkey on orders
Index Cond: (customer.c_custkey = o_custkey)
Filter: ((o_comment)::text !~~ '%specialposits%'::text)Zber sa uskutočňuje na poslednom kroku, takže Nested Loop Left Join — je to paralelná operácia. Parallel Index Only Scan sa objavil až vo verzii 10. Funguje podobne ako paralelné sekvenčné skenovanie. Podmienka c_custkey = o_custkey čítal jeden poriadok pre každý riadok zákazníka. Takže nie je paralelný.
Hash Join — Hash Join
Každý pracovný proces vytvára svoju hash tabuľku do PostgreSQL 11. A ak je týchto procesov viac ako štyri, výkon sa nezvýši. V novej verzii je hash tabuľka spoločná. Každý pracovný proces môže využiť WORK_MEM na vytvorenie hash tabuľky.
vyber
l_shipmode,
súčet(prípad
keď o_orderpriority = '1-URGENT'
alebo o_orderpriority = '2-HIGH'
potom 1
inak 0
koniec) ako high_line_count,
súčet(prípad
keď o_orderpriority '1-URGENT'
a o_orderpriority '2-HIGH'
potom 1
inak 0
koniec) ako low_line_count
z
orders,
lineitem
kde
o_orderkey = l_orderkey
a l_shipmode v ('MAIL', 'AIR')
a l_commitdate < l_receiptdate
a l_shipdate = dátum '1996-01-01'
a l_receiptdate < dátum '1996-01-01' + interval '1' rok
grupovať podľa
l_shipmode
poradie podľa
l_shipmode
LIMIT 1;
PLÁN DOTAZU
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (náklady=1964755.66..1964961.44 riadkov=1 šírka=27) (skutočný čas=7579.592..7922.997 riadkov=1 smyčiek=1)
-> Finalizovať GroupAggregate (náklady=1964755.66..1966196.11 riadkov=7 šírka=27) (skutočný čas=7579.590..7579.591 riadkov=1 smyčiek=1)
Skupinový kľúč: lineitem.l_shipmode
-> Zbieranie zlúčením (náklady=1964755.66..1966195.83 riadkov=28 šírka=27) (skutočný čas=7559.593..7922.319 riadkov=6 smyčiek=1)
Zamestnanci plánovaní: 4
Zamestnanci naštartovaní: 4
-> Čiastočný GroupAggregate (náklady=1963755.61..1965192.44 riadkov=7 šírka=27) (skutočný čas=7548.103..7564.592 riadkov=2 smyčiek=5)
Skupinový kľúč: lineitem.l_shipmode
-> Triedenie (náklady=1963755.61..1963935.20 riadkov=71838 šírka=27) (skutočný čas=7530.280..7539.688 riadkov=62519 smyčiek=5)
Kľúč triedenia: lineitem.l_shipmode
Metóda triedenia: externé zlúčenie Disk: 2304kB
Zamestnanec 0: Metóda triedenia: externé zlúčenie Disk: 2064kB
Zamestnanec 1: Metóda triedenia: externé zlúčenie Disk: 2384kB
Zamestnanec 2: Metóda triedenia: externé zlúčenie Disk: 2264kB
Zamestnanec 3: Metóda triedenia: externé zlúčenie Disk: 2336kB
-> Paralelný hash join (náklady=382571.01..1957960.99 riadkov=71838 šírka=27) (skutočný čas=7036.917..7499.692 riadkov=62519 smyčiek=5)
Hash Cond: (lineitem.l_orderkey = orders.o_orderkey)
-> Paralelné sekvenčné skenovanie na lineitem (náklady=0.00..1552386.40 riadkov=71838 šírka=19) (skutočný čas=0.583..4901.063 riadkov=62519 smyčiek=5)
Filter: ((l_shipmode = ĽUBOVOLNÉ ('{MAIL,AIR}'::bpchar[])) A (l_commitdate < l_receiptdate) A (l_shipdate = '1996-01-01'::dátum) A (l_receiptdate < '1997-01-01 00:00:00'::timestamp bez časovej zóny))
Riadky odstránené filtrom: 11934691
-> Paralelný hash (náklady=313722.45..313722.45 riadkov=3750045 šírka=20) (skutočný čas=2011.518..2011.518 riadkov=3000000 smyčiek=5)
Pamäťové použitie: 3840kB
-> Paralelné sekvenčné skenovanie na orders (náklady=0.00..313722.45 riadkov=3750045 šírka=20) (skutočný čas=0.029..995.948 riadkov=3000000 smyčiek=5)
Čas plánovania: 0.977 ms
Čas vykonávania: 7923.770 msDotaz 12 z TPC-H ukazuje paralelné hash pripojenie. Každý pracovný proces sa podieľa na vytvorení spoločnej hash tabuľky.
Spojenie zlučením — Merge Join
Spojenie zlučením je vo svojej podstate neparalelné. Nebojte sa, ak je to posledná fáza dotazu, stále môže byť vykonávaná paralelne.
-- Dotaz 2 z TPC-H
vysvetliť (náklady off) vyberte s_acctbal, s_name, n_name, p_partkey, p_mfgr, s_address, s_phone, s_comment
z part, supplier, partsupp, nation, region
kde
p_partkey = ps_partkey
a s_suppkey = ps_suppkey
a p_size = 36
a p_type ako '%BRASS'
a s_nationkey = n_nationkey
a n_regionkey = r_regionkey
a r_name = 'AMERICA'
a ps_supplycost = (
vyberte
min(ps_supplycost)
z partsupp, supplier, nation, region
kde
p_partkey = ps_partkey
a s_suppkey = ps_suppkey
a s_nationkey = n_nationkey
a n_regionkey = r_regionkey
a r_name = 'AMERICA'
)
poradie podľa s_acctbal zostupne, n_name, s_name, p_partkey
LIMIT 100;
PLÁN DOTAZU
----------------------------------------------------------------------------------------------------------
Limit
-> Triediť
Kľúč triedenia: supplier.s_acctbal DESC, nation.n_name, supplier.s_name, part.p_partkey
-> Merge Join
Merge Cond: (part.p_partkey = partsupp.ps_partkey)
Join Filter: (partsupp.ps_supplycost = (SubPlan 1))
-> Gather Merge
Plánovaní pracujúci: 4
-> Paralelný indexový sken pomocou <strong>part_pkey</strong> na časti
Filter: (((p_type)::text ~~ '%BRASS'::text) A (p_size = 36))
-> Materializovať
-> Triediť
Kľúč triedenia: partsupp.ps_partkey
-> Vnorená slučka
-> Vnorená slučka
Join Filter: (nation.n_regionkey = region.r_regionkey)
-> Sekvenčný sken na region
Filter: (r_name = 'AMERICA'::bpchar)
-> Hash Join
Hash Cond: (supplier.s_nationkey = nation.n_nationkey)
-> Sekvenčný sken na supplier
-> Hash
-> Sekvenčný sken na nation
-> Index Scan pomocou idx_partsupp_suppkey na partsupp
Index Cond: (ps_suppkey = supplier.s_suppkey)
SubPlan 1
-> Agregovať
-> Vnorená slučka
Join Filter: (nation_1.n_regionkey = region_1.r_regionkey)
-> Sekvenčný sken na region region_1
Filter: (r_name = 'AMERICA'::bpchar)
-> Vnorená slučka
-> Vnorená slučka
-> Index Scan pomocou idx_partsupp_partkey na partsupp partsupp_1
Index Cond: (part.p_partkey = ps_partkey)
-> Index Scan pomocou supplier_pkey na supplier supplier_1
Index Cond: (s_suppkey = partsupp_1.ps_suppkey)
-> Index Scan pomocou nation_pkey na nation nation_1
Index Cond: (n_nationkey = supplier_1.s_nationkey)Noda „Merge Join“ sa nachádza nad „Gather Merge“. Takže zlučenie nepoužíva paralelnú prácu. Ale noda „Parallel Index Scan“ stále pomáha so segmentom. part_pkey.
Spojenie podľa sekcií
V PostgreSQL 11 je predvolene vypnuté: má veľmi nákladné plánovanie. Tabuľky so podobným sekcionovaním je možné spájať sekciu za sekciou. Tak Postgres použije menšie hash tabuľky. Každé spojenie sekcií môže byť paralelné.
tpch=# set enable_partitionwise_join=t;
tpch=# explain (costs off) select * from prt1 t1, prt2 t2
where t1.a = t2.b and t1.b = 0 and t2.b between 0 and 10000;
PLÁN DOTAZU
---------------------------------------------------
Append
-> Hash Join
Hash Cond: (t2.b = t1.a)
-> Seq Scan on prt2_p1 t2
Filter: ((b >= 0) AND (b <= 10000))
-> Hash
-> Seq Scan on prt1_p1 t1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p2 t2_1
Filter: ((b >= 0) AND (b <= 10000))
-> Hash
-> Seq Scan on prt1_p2 t1_1
Filter: (b = 0)
tpch=# set parallel_setup_cost = 1;
tpch=# set parallel_tuple_cost = 0.01;
tpch=# explain (costs off) select * from prt1 t1, prt2 t2
where t1.a = t2.b and t1.b = 0 and t2.b between 0 and 10000;
PLÁN DOTAZU
-----------------------------------------------------------
Gather
Workers Planned: 4
-> Paralelný Append
-> Paralelný Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Paralelný Seq Scan on prt2_p2 t2_1
Filter: ((b >= 0) AND (b <= 10000))
-> Paralelný Hash
-> Paralelný Seq Scan on prt1_p2 t1_1
Filter: (b = 0)
-> Paralelný Hash Join
Hash Cond: (t2.b = t1.a)
-> Paralelný Seq Scan on prt2_p1 t2
Filter: ((b >= 0) AND (b <= 10000))
-> Paralelný Hash
-> Paralelný Seq Scan on prt1_p1 t1
Filter: (b = 0)Hlavné je, že spojenie podľa sekcií môže byť paralelné, len ak sú tieto sekcie dostatočne veľké.
Paralelné doplnenie — Parallel Append
môže byť použité namiesto rôznych blokov v rôznych pracovných procesoch. Zvyčajne ide o dotazy UNION ALL. Nevýhodou je menší paralelizmus, pretože každý pracovný proces spracováva len 1 dotaz.
Tu sú spustené 2 pracovné procesy, hoci sú zapnuté 4.
tpch=# explain (costs off) select sum(l_quantity) as sum_qty from lineitem where l_shipdate <= date '1998-12-01' - interval '105' day union all select sum(l_quantity) as sum_qty from lineitem where l_shipdate Parallel Append
-> Aggregate
-> Seq Scan on lineitem
Filter: (l_shipdate Aggregate
-> Seq Scan on lineitem lineitem_1
Filter: (l_shipdate <= '1998-08-18 00:00:00'::timestamp without time zone)Najdôležitejšie premenné
- WORK_MEM obmedzuje objem pamäte pre každý proces, nielen pre dopyty: work_mem procesy pripojenia = veľmi veľa pamäte.
- — koľko pracovných procesov vykonávajúca úloha použije na paralelné spracovanie z plánu.
- — prispôsobuje celkový počet pracovných procesov k počtu jadier CPU na serveri.
- — to isté, ale pre paralelné pracovné procesy.
Zhrnutie
Od verzie 9.6 môže paralelné spracovanie výrazne zlepšiť výkon zložitých dopytov, ktoré skenujú veľa riadkov alebo indexov. V PostgreSQL 10 je paralelné spracovanie predvolene zapnuté. Nezabudnite ho vypnúť na serveroch s veľkým zaťažením OLTP. Sekvenčné skeny alebo skeny indexov spotrebúvajú veľmi veľa zdrojov. Ak nevykonávate report na celú sadu údajov, môžete dopyty spraviť efektívnejšími jednoducho pridaním chýbajúcich indexov alebo použitím správneho členenia.
Odkazy
Zdroj: habr.com
