
I moderna CPU:er finns det mÄnga kÀrnor. Under flera Är har applikationer skickat förfrÄgningar till databaser parallellt. Om det Àr en rapportförfrÄgan om mÄnga rader i en tabell utförs den snabbare nÀr flera CPU:er Àr involverade, och i PostgreSQL Àr detta möjligt sedan version 9.6.
Det tog tre Ă„r att implementera funktionen för parallella förfrĂ„gningar â koden behövde skrivas om vid olika steg i förfrĂ„gningarnas utförande. PostgreSQL 9.6 introducerade infrastrukturen för att ytterligare förbĂ€ttra koden. I senare versioner utförs Ă€ven andra typer av förfrĂ„gningar parallellt.
BegrÀnsningar
- Aktivera inte parallell exekvering om alla kÀrnor redan Àr upptagna, annars kommer andra förfrÄgningar att bromsa.
- Det viktigaste Ă€r att parallell behandling med höga values WORK_MEM anvĂ€nder mycket minne â varje hash-förbindelse eller sortering tar minne motsvarande work_mem.
- OLTP-förfrÄgningar med lÄg latens kan inte öka hastigheten med parallell exekvering. Om en förfrÄgan returnerar en rad kommer den parallella behandlingen bara att sakta ner den.
- Utvecklare Àlskar att anvÀnda TPC-H-benchmarken. Kanske har du liknande förfrÄgningar för perfekt parallell exekvering.
- Endast SELECT-förfrÄgningar utan predikantlÄsning utförs parallellt.
- Ibland kan rÀtt indexering vara bÀttre Àn sekventiell skanning av tabellen i parallell lÀge.
- Stopp av förfrÄgningar och markörer stöds inte.
- Fönsterfunktioner och aggregerade funktioner av ordnade uppsÀttningar Àr inte parallella.
- Du vinner inget i in- och utdataarbetsbelastning.
- Det finns inga parallella sorteringsalgoritmer. Men förfrÄgningar med sorteringar kan exekveras parallellt i vissa aspekter.
- ErsĂ€tt CTE (WITH âŠ) med en inbĂ€ddad SELECT för att inkludera parallell bearbetning.
- Wraps för tredjepartsdata stöder fortfarande inte parallell bearbetning (men det skulle kunna!)
- FULL OUTER JOIN stöds inte.
- max_rows inaktiverar parallell bearbetning.
- Om en förfrÄgan innehÄller en funktion som inte Àr markerad som PARALLEL SAFE kommer den att vara en-trÄdad.
- TransaktionsisoleringsnivÄn SERIALIZABLE inaktiverar parallell bearbetning.
Testmiljö
PostgreSQL-utvecklarna försökte minska svarstiden för TPC-H-benchmarkfĂ„gor. Ladda ner benchmarken och . Detta Ă€r ett icke-officiellt anvĂ€ndande av TPC-H-benchmarken â inte för att jĂ€mföra databaser eller hĂ„rdvara.
- Ladda ner TPC-H_Tools_v2.17.3.zip (eller nyare version) .
- Ăndra namnet pĂ„ makefile.suite till Makefile och som beskrivs hĂ€r: . Kompilera koden med kommandot make.
- Generera data:
./dbgen -s 10skapar en databas pÄ 23 GB. Det rÀcker för att se skillnaden i prestanda mellan parallella och icke-parallella förfrÄgningar. - Konvertera filer
tblpÄcsv till förochsed. - Klona repositoriet
pg_tpchoch kopiera filernacsvpÄpg_tpch/dss/data. - Skapa förfrÄgningar med kommandot
qgen. - Ladda upp data i databasen med kommandot
./tpch.sh.
Parallell sekventiell skanning
Det kan vara snabbare, inte för att data lÀses parallellt, utan eftersom data sprids över mÄnga CPU-kÀrnor. I moderna operativsystem cachas PostgreSQL-datafiler vÀl. Med förhandslÀsning kan man hÀmta fler block frÄn lagringen Àn vad PG-processen begÀr. DÀrför begrÀnsas förfrÄgningsprestandan inte av diskens I/O. Den anvÀnder CPU-cykler för att:
- lÀsa rader en och en frÄn tabellens sidor;
- jÀmföra vÀrden och villkor för rader.
DĂR.
LÄt oss utföra en enkel förfrÄgning select:
tpch=# explain analyze select l_quantity as sum_qty from lineitem where l_shipdate <= date '1998-12-01' - interval '105' day;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
Seq Scan on lineitem (cost=0.00..1964772.00 rows=58856235 width=5) (actual time=0.014..16951.669 rows=58839715 loops=1)
Filter: (l_shipdate <= '1998-08-18 00:00:00'::timestamp without time zone)
Rows Removed by Filter: 1146337
Planning Time: 0.203 ms
Execution Time: 19035.100 msSekventiell skanning ger för mÄnga rader utan aggregering, sÄ förfrÄgan körs med en enda CPU-kÀrna.
Om man lÀgger till SUM(), syns det att tvÄ arbetstrÄdar hjÀlper till att pÄskynda förfrÄgan:
explain analyze select sum(l_quantity) as sum_qty from lineitem where l_shipdate Gather (cost=1589701.91..1589702.12 rows=2 width=32) (actual time=8553.241..8555.067 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=1588701.91..1588701.92 rows=1 width=32) (actual time=8547.546..8547.546 rows=1 loops=3)
-> Parallel Seq Scan on lineitem (cost=0.00..1527393.33 rows=24523431 width=5) (actual time=0.038..5998.417 rows=19613238 loops=3)
Filter: (l_shipdate <= '1998-08-18 00:00:00'::timestamp without time zone)
Rows Removed by Filter: 382112
Planning Time: 0.241 ms
Execution Time: 8555.131 msParallell aggregering
Noden «Parallel Seq Scan» producerar rader för partiell aggregering. Noden «Partial Aggregate» filtrerar dessa rader med hjÀlp av SUM(). I slutet sammanstÀlls SUM-rÀknaren frÄn varje arbetstrÄd av noden «Gather».
Det slutgiltiga resultatet berÀknas av noden "Finalize Aggregate". Om du har egna aggregeringsfunktioner, glöm inte att mÀrka dem som "parallel safe".
Antal arbetsprocesser
Antalet arbetsprocesser kan ökas utan att starta om servern:
explain analyze select sum(l_quantity) as sum_qty from lineitem where l_shipdate Gather (cost=1589701.91..1589702.12 rows=2 width=32) (actual time=8553.241..8555.067 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=1588701.91..1588701.92 rows=1 width=32) (actual time=8547.546..8547.546 rows=1 loops=3)
-> Parallel Seq Scan on lineitem (cost=0.00..1527393.33 rows=24523431 width=5) (actual time=0.038..5998.417 rows=19613238 loops=3)
Filter: (l_shipdate <= '1998-08-18 00:00:00'::timestamp without time zone)
Rows Removed by Filter: 382112
Planning Time: 0.241 ms
Execution Time: 8555.131 msVad hÀnder hÀr? Antalet arbetsprocesser har blivit dubbelt sÄ mÄnga, men förfrÄgan blev bara 1,6599 gÄnger snabbare. BerÀkningarna Àr intressanta. Vi hade 2 arbetsprocesser och 1 ledare. Efter förÀndringen blev det 4+1.
VÄr maximala hastighetsökning frÄn parallell bearbetning: 5/3 = 1,66(6) gÄnger.
Hur fungerar det?
Processer
Utförandet av en förfrÄgan börjar alltid med ledarprocessen. Ledaren gör allt icke-parallellt och en del av den parallella bearbetningen. Andra processer som utför samma förfrÄgningar kallas arbetsprocesser. Parallell bearbetning anvÀnder infrastrukturen (frÄn version 9.4). Eftersom andra delar av PostgreSQL anvÀnder processer istÀllet för trÄdar, kunde en förfrÄgan med 3 arbetsprocesser vara 4 gÄnger snabbare Àn traditionell bearbetning.
Interaktion
Arbetsprocesserna kommunicerar med ledaren via ett meddelandekö (baserat pÄ delad minne). Varje process har 2 köer: för fel och för tupler.
Hur mÄnga arbetsprocesser behövs?
Det minimi begrÀnsning som stÀlls in av parametern . Sedan hÀmtar frÄgeexecutorn arbetsprocesser frÄn poolen, begrÀnsad av parametern . Den sista begrÀnsningen Àr , det vill sÀga det totala antalet bakgrundsprocesser.
Om det inte gÄr att tilldela en arbetsprocess, kommer bearbetningen att vara enprocessig.
FrÄgeplaneraren kan minska arbetsprocesser beroende pÄ storleken av tabellen eller indexet. Det finns parametrar för detta: och .
set min_parallel_table_scan_size='8MB'
8MB-tabell => 1 arbetare
24MB-tabell => 2 arbetare
72MB-tabell => 3 arbetare
x => log(x / min_parallel_table_scan_size) / log(3) + 1 arbetareVarje gÄng tabellen Àr tre gÄnger större Àn min_parallel_(index|table)_scan_size, lÀgger Postgres till en arbetsprocess. Antalet arbetsprocesser baseras inte pÄ kostnader. Cykelberoendet gör komplexa implementationer svÄra. IstÀllet anvÀnder planeraren enkla regler.
I praktiken Àr dessa regler inte alltid lÀmpliga för produktion, sÄ man kan Àndra antalet arbetsprocesser för en specifik tabell: ALTER TABLE ⊠SET (parallel_workers = N).
Varför anvÀnds inte parallell bearbetning?
Förutom den lÄnga listan över begrÀnsningar finns det Àven kostnadskontroller:
â för att undvika parallell bearbetning av korta förfrĂ„gningar. Denna parameter uppskattar tiden för att förbereda minnet, starta processen och initialt utbyta data.
: ledarens kommunikation med arbetarna kan förlÀngas proportionellt mot antalet tuples frÄn arbetande processer. Denna parameter berÀknar kostnaderna för datautbyte.
NĂ€stlad loopsammanfogning â Nested Loop Join
PostgreSQL 9.6+ kan utföra nĂ€stlade loopar parallellt â det Ă€r en enkel operation.
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)Samlingsprocessen sker i det sista steget, sÄ Nested Loop Left Join Àr en parallell operation. Parallel Index Only Scan introducerades först i version 10. Det fungerar pÄ samma sÀtt som ett parallellt sekventiellt scan. Villkoret c_custkey = o_custkey lÀser en order för varje kundrad. SÄ det Àr inte parallellt.
Hash-sammanfogning â Hash Join
Varje arbetsprocess skapar sin egen hash-tabell fram till PostgreSQL 11. Och om det finns fler Àn fyra sÄdana processer ökar inte prestandan. I den nya versionen Àr hash-tabellen gemensam. Varje arbetsprocess kan anvÀnda WORK_MEM för att skapa en hash-tabell.
vÀlj
l_shipmode,
summa(fall
nÀr o_orderpriority = '1-URGENT'
eller o_orderpriority = '2-HIGH'
dÄ 1
annars 0
slut) som high_line_count,
summa(fall
nÀr o_orderpriority <> '1-URGENT'
och o_orderpriority <> '2-HIGH'
dÄ 1
annars 0
slut) som low_line_count
frÄn
orders,
lineitem
var
o_orderkey = l_orderkey
och l_shipmode i ('MAIL', 'AIR')
och l_commitdate < l_receiptdate
och l_shipdate < l_commitdate
och l_receiptdate >= datum '1996-01-01'
och l_receiptdate < datum '1996-01-01' + intervall '1' Är
grupp efter
l_shipmode
ordna efter
l_shipmode
BEGRĂNSA 1;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
BegrÀnsning (kostnad=1964755.66..1964961.44 rader=1 bred=27) (aktuell tid=7579.592..7922.997 rader=1 loop=1)
-> Avsluta GroupAggregate (kostnad=1964755.66..1966196.11 rader=7 bred=27) (aktuell tid=7579.590..7579.591 rader=1 loop=1)
Gruppnyckel: lineitem.l_shipmode
-> Samla Samman (kostnad=1964755.66..1966195.83 rader=28 bred=27) (aktuell tid=7559.593..7922.319 rader=6 loop=1)
Arbetare Planerade: 4
Arbetare Startade: 4
-> Delad GroupAggregate (kostnad=1963755.61..1965192.44 rader=7 bred=27) (aktuell tid=7548.103..7564.592 rader=2 loop=5)
Gruppnyckel: lineitem.l_shipmode
-> Sortera (kostnad=1963755.61..1963935.20 rader=71838 bred=27) (aktuell tid=7530.280..7539.688 rader=62519 loop=5)
Sorteringsnyckel: lineitem.l_shipmode
Sorteringsmetod: extern sammanfogning Disk: 2304kB
Arbetare 0: Sorteringsmetod: extern sammanfogning Disk: 2064kB
Arbetare 1: Sorteringsmetod: extern sammanfogning Disk: 2384kB
Arbetare 2: Sorteringsmetod: extern sammanfogning Disk: 2264kB
Arbetare 3: Sorteringsmetod: extern sammanfogning Disk: 2336kB
-> Parallell Hash Join (kostnad=382571.01..1957960.99 rader=71838 bred=27) (aktuell tid=7036.917..7499.692 rader=62519 loop=5)
Hash Villkor: (lineitem.l_orderkey = orders.o_orderkey)
-> Parallell Sekventiell Skanning pÄ lineitem (kostnad=0.00..1552386.40 rader=71838 bred=19) (aktuell tid=0.583..4901.063 rader=62519 loop=5)
Filter: ((l_shipmode = VALFRI ('{MAIL,AIR}'::bpchar[])) OCH (l_commitdate < l_receiptdate) OCH (l_shipdate < l_commitdate) OCH (l_receiptdate >= '1996-01-01'::datum) OCH (l_receiptdate < '1997-01-01 00:00:00'::tidsstÀmpel utan tidszon))
Rader Borttagna av Filter: 11934691
-> Parallell Hash (kostnad=313722.45..313722.45 rader=3750045 bred=20) (aktuell tid=2011.518..2011.518 rader=3000000 loop=5)
Bucket: 65536 Batch: 256 Minnesbruk: 3840kB
-> Parallell Sekventiell Skanning pÄ orders (kostnad=0.00..313722.45 rader=3750045 bred=20) (aktuell tid=0.029..995.948 rader=3000000 loop=5)
Planeringstid: 0.977 ms
Utförandeavdelningstid: 7923.770 msFrÄga 12 frÄn TPC-H visar tydligt en parallell hash-förbindelse. Varje arbetare deltar i att skapa en gemensam hash-tabell.
SammanlĂ€ggning â Merge Join
SammanlÀggning som inte Àr parallell till sin natur. Oroa dig inte om detta Àr det sista steget i förfrÄgan, den kan fortfarande utföras parallellt.
-- Förfrågan 2 från TPC-H
förklara (kostnader av) välj s_acctbal, s_name, n_name, p_partkey, p_mfgr, s_address, s_phone, s_comment
från part, supplier, partsupp, nation, region
var
p_partkey = ps_partkey
och s_suppkey = ps_suppkey
och p_size = 36
och p_type som '%BRASS'
och s_nationkey = n_nationkey
och n_regionkey = r_regionkey
och r_name = 'AMERIKA'
och ps_supplycost = (
välj
min(ps_supplycost)
från partsupp, supplier, nation, region
var
p_partkey = ps_partkey
och s_suppkey = ps_suppkey
och s_nationkey = n_nationkey
och n_regionkey = r_regionkey
och r_name = 'AMERIKA'
)
ordna efter s_acctbal desc, n_name, s_name, p_partkey
BEGRÄNSA 100;
FRÅGEPLAN
----------------------------------------------------------------------------------------------------------
Begränsa
-> Sortera
Sorteringsnyckel: supplier.s_acctbal DESC, nation.n_name, supplier.s_name, part.p_partkey
-> Sammanfoga
Sammanfoga villkor: (part.p_partkey = partsupp.ps_partkey)
Join Filter: (partsupp.ps_supplycost = (SubPlan 1))
-> Samla samman
Planerade arbetare: 4
-> Parallell indexskanning med hjälp av <strong>part_pkey</strong> på del
Filter: (((p_type)::text ~~ '%BRASS'::text) OCH (p_size = 36))
-> Materialisera
-> Sortera
Sorteringsnyckel: partsupp.ps_partkey
-> Nästlad slinga
-> Nästlad slinga
Join Filter: (nation.n_regionkey = region.r_regionkey)
-> Sekventiell skanning på region
Filter: (r_name = 'AMERIKA'::bpchar)
-> Hash-sammanfogning
Hash-villkor: (supplier.s_nationkey = nation.n_nationkey)
-> Sekventiell skanning på leverantör
-> Hash
-> Sekventiell skanning på nation
-> Indexskanning med hjälp av idx_partsupp_suppkey på partsupp
Indexvillkor: (ps_suppkey = supplier.s_suppkey)
SubPlan 1
-> Aggregat
-> Nästlad slinga
Join Filter: (nation_1.n_regionkey = region_1.r_regionkey)
-> Sekventiell skanning på region region_1
Filter: (r_name = 'AMERIKA'::bpchar)
-> Nästlad slinga
-> Nästlad slinga
-> Indexskanning med hjälp av idx_partsupp_partkey på partsupp partsupp_1
Indexvillkor: (part.p_partkey = ps_partkey)
-> Indexskanning med hjälp av supplier_pkey på supplier supplier_1
Indexvillkor: (s_suppkey = partsupp_1.ps_suppkey)
-> Indexskanning med hjälp av nation_pkey på nation nation_1
Indexvillkor: (n_nationkey = supplier_1.s_nationkey)Noden «Merge Join» ligger ovanför «Gather Merge». SÄ sammanlÀggningen anvÀnder inte parallell bearbetning. Men noden «Parallel Index Scan» hjÀlper ÀndÄ till med segmentet. part_pkey.
SammanlÀggning efter sektioner
I PostgreSQL 11 Àr stÀngd som standard: den har mycket kostsam planering. Tabeller med liknande sektionering kan sammanlÀggas sektion för sektion. SÄ kommer Postgres att anvÀnda mindre hash-tabeller. Varje sektion kan vara parallell.
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;
QUERY PLAN
---------------------------------------------------
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;
QUERY PLAN
-----------------------------------------------------------
Gather
Workers Planned: 4
-> Parallel Append
-> Parallel Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Parallel Seq Scan on prt2_p2 t2_1
Filter: ((b >= 0) AND (b <= 10000))
-> Parallel Hash
-> Parallel Seq Scan on prt1_p2 t1_1
Filter: (b = 0)
-> Parallel Hash Join
Hash Cond: (t2.b = t1.a)
-> Parallel Seq Scan on prt2_p1 t2
Filter: ((b >= 0) AND (b <= 10000))
-> Parallel Hash
-> Parallel Seq Scan on prt1_p1 t1
Filter: (b = 0)Huvudsaken Àr att sammanlÀggning efter sektioner endast kan vara parallell om dessa sektioner Àr tillrÀckligt stora.
Parallell bilaga â Parallel Append
kan anvÀndas istÀllet för olika block i olika arbetsprocesser. Detta intrÀffar vanligtvis med UNION ALL-förfrÄgningar. Nackdelen Àr mindre parallellism, eftersom varje arbetsprocess bara behandlar 1 förfrÄgan.
HÀr körs 2 arbetsprocesser, Àven om 4 Àr aktiverade.
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)De viktigaste variablerna
- WORK_MEM begrÀnsar mÀngden minne för varje process, inte bara för frÄgor: work_mem processer anslutningar = vÀldigt mycket minne.
- â hur mĂ„nga arbetsprocesser programmet kommer att anvĂ€nda för parallell bearbetning frĂ„n planen.
- â justerar det totala antalet arbetsprocesser baserat pĂ„ antalet CPU-kĂ€rnor pĂ„ servern.
- âsamma, men för parallella arbetsprocesser.
Sammanfattning
FrÄn och med version 9.6 kan parallell bearbetning avsevÀrt förbÀttra prestandan för komplexa frÄgor som skannar mÄnga rader eller index. I PostgreSQL 10 Àr parallell bearbetning aktiverad som standard. Glöm inte att stÀnga av den pÄ servrar med hög OLTP-belastning. Sekventiella skanningar eller indexskanningar förbrukar mycket resurser. Om du inte kör en rapport pÄ hela datamÀngden kan frÄgor göras mer effektiva genom att bara lÀgga till saknade index eller anvÀnda korrekt partitionering.
LĂ€nkar
KĂ€lla: habr.com
