Moderne SVE's het baie kerne. Vir jare stuur toepassings parallel na databasisse navrae. As dit 'n verslag-navraag teen veelvuldige rye in 'n tabel is, is dit vinniger wanneer dit veelvuldige SVE's gebruik, en PostgreSQL kon dit sedert weergawe 9.6 doen.
Dit het 3 jaar geneem om die parallelle navraagfunksie te implementeer - ek moes die kode in verskillende stadiums van navraaguitvoering herskryf. PostgreSQL 9.6 het die infrastruktuur bekendgestel om die kode verder te verbeter. In daaropvolgende weergawes word ander tipes navrae parallel uitgevoer.
Beperkings
Moenie parallelle uitvoering aktiveer as alle kerns reeds besig is nie, anders sal ander versoeke vertraag.
Belangriker nog, parallelle verwerking met hoë WORK_MEM-waardes gebruik baie geheue - elke hash-verbinding of sorteer verbruik geheue in die hoeveelheid work_mem.
Lae latensie OLTP-navrae kan nie deur parallelle uitvoering versnel word nie. En as die navraag 'n enkele ry terugstuur, sal parallelle verwerking dit net vertraag.
Ontwikkelaars hou daarvan om die TPC-H-maatstaf te gebruik. Miskien het jy soortgelyke navrae vir perfekte parallelle uitvoering.
Slegs SELECT-navrae sonder predikaatsluiting word parallel uitgevoer.
Soms is behoorlike indeksering beter as opeenvolgende tabelskanderings in parallel.
Onderbreking van navraag en wysers word nie ondersteun nie.
Vensterfunksies en geordende versamelingsfunksies is nie parallel nie.
Jy kry niks in die I/O-werklading nie.
Daar is geen parallelle sorteeralgoritmes nie. Maar navrae met soorte kan in sommige aspekte parallel loop.
Vervang CTE (MET ...) met 'n geneste SELECT om parallelle verwerking moontlik te maak.
Buitelandse data-omhulsels ondersteun nog nie parallelle verwerking nie (maar hulle kan!)
VOLLEDIGE UITSTEKENDE aansluiting word nie ondersteun nie.
max_rows deaktiveer parallelle verwerking.
Indien die versoek 'n funksie het wat nie as PARALLEL VEILIG gemerk is nie, sal dit enkeldraad wees.
Die transaksie-isolasievlak SERIALIZABLE deaktiveer parallelle verwerking.
Toets omgewing
PostgreSQL-ontwikkelaars het probeer om die reaksietyd van TPC-H-maatstafnavrae te verminder. Laai die maatstaf af en pas dit aan by PostgreSQL. Dit is 'n nie-amptelike gebruik van die TPC-H-maatstaf - nie om databasisse of hardeware te vergelyk nie.
Genereer data: ./dbgen -s 10 skep 'n 23 GB databasis. Dit is genoeg om die prestasieverskil tussen parallelle en nie-parallelle navrae te sien.
Skakel lêers om tbl в csv с for и sed.
Kloon die bewaarplek pg_tpch en kopieer die lêers csv в pg_tpch/dss/data.
Skep versoeke met 'n opdrag qgen.
Laai die data in die databasis met die opdrag ./tpch.sh.
Parallelle opeenvolgende skandering
Dit kan vinniger wees, nie as gevolg van parallelle lees nie, maar omdat die data oor baie SVE-kerne versprei is. In moderne bedryfstelsels is PostgreSQL-datalêers goed gekas. Met leesvooruit is dit moontlik om 'n blok groter van berging te kry as wat die PG-demoon versoek. Daarom word navraagprestasie nie beperk deur skyf I/O nie. Dit verbruik SVE-siklusse om:
lees reëls een vir een van die bladsye van die tabel af;
vergelyk stringwaardes en voorwaardes WHERE.
Kom ons voer 'n eenvoudige navraag uit 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 ms
Opeenvolgende skandering gee te veel rye sonder samevoeging, dus word die navraag deur een SVE-kern uitgevoer.
As jy byvoeg SUM(), kan jy sien dat twee werkstrome sal help om die navraag te bespoedig:
explain analyze select sum(l_quantity) as sum_qty from lineitem where l_shipdate <= date '1998-12-01' - interval '105' day;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=1589702.14..1589702.15 rows=1 width=32) (actual time=8553.365..8553.365 rows=1 loops=1)
-> 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 ms
Parallelle Aggregasie
Die "Parallel Seq Scan" nodus produseer rye vir gedeeltelike samevoeging. Die "Gedeeltelike Aggregaat" knoop kap hierdie rye af met SUM(). Aan die einde word die SOM-teller van elke werkvloei deur die Gather-nodus ingesamel.
Die finale resultaat word bereken deur die "Finalize Aggregate"-knooppunt. As jy jou eie samevoegingsfunksies het, moenie vergeet om dit as "parallel veilig" te merk nie.
Aantal werkerprosesse
Die aantal werkerprosesse kan verhoog word sonder om die bediener te herbegin:
explain analyze select sum(l_quantity) as sum_qty from lineitem where l_shipdate <= date '1998-12-01' - interval '105' day;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=1589702.14..1589702.15 rows=1 width=32) (actual time=8553.365..8553.365 rows=1 loops=1)
-> 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 ms
Wat gaan hier aan? Daar is 2 keer meer werkerprosesse, en die navraag is net 1,6599 keer vinniger. Die berekeninge is interessant. Ons het 2 werkerprosesse en 1 leier gehad. Na die verandering het dit 4+1 geword.
Ons maksimum versnelling vanaf parallelle verwerking: 5/3 = 1,66(6) keer.
Hoe werk dit?
prosesse
Die uitvoering van 'n navraag begin altyd met die leidende proses. Die leier doen alles nie-parallel en sommige van die parallelle verwerking. Ander prosesse wat dieselfde versoeke uitvoer, word werkersprosesse genoem. Parallelle verwerking gebruik infrastruktuur dinamiese agtergrond werkstrome (sedert weergawe 9.4). Aangesien ander dele van PostgreSQL prosesse eerder as drade gebruik, kan 'n navraag met 3 werkerprosesse 4 keer vinniger wees as tradisionele verwerking.
Interaksie
Werkerprosesse kommunikeer met die leier via 'n boodskapwaglys (gebaseer op gedeelde geheue). Elke proses het 2 toue: vir foute en vir tuples.
Elke keer is die tafel 3 keer groter as min_parallel_(index|table)_scan_size, Postgres voeg 'n werkersproses by. Die aantal werkstrome is nie koste gebaseer nie. Sirkulêre afhanklikheid maak komplekse implementerings moeilik. In plaas daarvan gebruik die skeduleerder eenvoudige reëls.
In die praktyk is hierdie reëls nie altyd geskik vir produksie nie, dus is dit moontlik om die aantal werkerprosesse vir 'n spesifieke tabel te verander: VERANDER TABEL ... STEL (parallel_workers = N).
Hoekom word parallelle verwerking nie gebruik nie?
Benewens die lang lys beperkings, is daar ook kostekontroles:
parallel_setup_cost - om parallelle verwerking van kort versoeke te vermy. Hierdie parameter skat die tyd vir geheuevoorbereiding, prosesbegin en aanvanklike data-uitruiling.
parallel_tuple_cost: kommunikasie tussen die leier en die werkers kan vertraag word in verhouding tot die aantal tupels van die werkerprosesse. Hierdie parameter neem die koste van data-uitruiling in ag.
PostgreSQL 9.6+ может выполнять вложенные циклы параллельно — это простая операция.
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 '%special%deposits%'
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 !~~ '%special%deposits%'::text)
Insameling vind plaas in die laaste stap, dus geneste lus links aansluiting is 'n parallelle bewerking. Parallel Index Only Scan is eers in weergawe 10 bekendgestel. Dit werk soortgelyk aan 'n parallelle opeenvolgende skandering. Toestand c_custkey = o_custkey lees een bestelling vir elke kliëntry. Dit is dus nie parallel nie.
Hash Join - Hash Join
Elke werkerproses skep sy eie hash-tabel voor PostgreSQL 11. En as daar meer as vier van hierdie prosesse is, sal werkverrigting nie verbeter nie. In die nuwe weergawe word die hash-tabel gedeel. Elke werkerproses kan WORK_MEM gebruik om 'n hash-tabel te skep.
select
l_shipmode,
sum(case
when o_orderpriority = '1-URGENT'
or o_orderpriority = '2-HIGH'
then 1
else 0
end) as high_line_count,
sum(case
when o_orderpriority <> '1-URGENT'
and o_orderpriority <> '2-HIGH'
then 1
else 0
end) as low_line_count
from
orders,
lineitem
where
o_orderkey = l_orderkey
and l_shipmode in ('MAIL', 'AIR')
and l_commitdate < l_receiptdate
and l_shipdate < l_commitdate
and l_receiptdate >= date '1996-01-01'
and l_receiptdate < date '1996-01-01' + interval '1' year
group by
l_shipmode
order by
l_shipmode
LIMIT 1;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=1964755.66..1964961.44 rows=1 width=27) (actual time=7579.592..7922.997 rows=1 loops=1)
-> Finalize GroupAggregate (cost=1964755.66..1966196.11 rows=7 width=27) (actual time=7579.590..7579.591 rows=1 loops=1)
Group Key: lineitem.l_shipmode
-> Gather Merge (cost=1964755.66..1966195.83 rows=28 width=27) (actual time=7559.593..7922.319 rows=6 loops=1)
Workers Planned: 4
Workers Launched: 4
-> Partial GroupAggregate (cost=1963755.61..1965192.44 rows=7 width=27) (actual time=7548.103..7564.592 rows=2 loops=5)
Group Key: lineitem.l_shipmode
-> Sort (cost=1963755.61..1963935.20 rows=71838 width=27) (actual time=7530.280..7539.688 rows=62519 loops=5)
Sort Key: lineitem.l_shipmode
Sort Method: external merge Disk: 2304kB
Worker 0: Sort Method: external merge Disk: 2064kB
Worker 1: Sort Method: external merge Disk: 2384kB
Worker 2: Sort Method: external merge Disk: 2264kB
Worker 3: Sort Method: external merge Disk: 2336kB
-> Parallel Hash Join (cost=382571.01..1957960.99 rows=71838 width=27) (actual time=7036.917..7499.692 rows=62519 loops=5)
Hash Cond: (lineitem.l_orderkey = orders.o_orderkey)
-> Parallel Seq Scan on lineitem (cost=0.00..1552386.40 rows=71838 width=19) (actual time=0.583..4901.063 rows=62519 loops=5)
Filter: ((l_shipmode = ANY ('{MAIL,AIR}'::bpchar[])) AND (l_commitdate < l_receiptdate) AND (l_shipdate < l_commitdate) AND (l_receiptdate >= '1996-01-01'::date) AND (l_receiptdate < '1997-01-01 00:00:00'::timestamp without time zone))
Rows Removed by Filter: 11934691
-> Parallel Hash (cost=313722.45..313722.45 rows=3750045 width=20) (actual time=2011.518..2011.518 rows=3000000 loops=5)
Buckets: 65536 Batches: 256 Memory Usage: 3840kB
-> Parallel Seq Scan on orders (cost=0.00..313722.45 rows=3750045 width=20) (actual time=0.029..995.948 rows=3000000 loops=5)
Planning Time: 0.977 ms
Execution Time: 7923.770 ms
Navraag 12 van TPC-H illustreer 'n parallelle hash-koppeling. Elke werkerproses neem deel aan die skepping van 'n gedeelde hash-tabel.
Voeg saam
'n Samevoeging is nie-parallel van aard. Moenie bekommerd wees as dit die laaste stap van die navraag is nie – dit kan steeds parallel loop.
-- Query 2 from TPC-H
explain (costs off) select s_acctbal, s_name, n_name, p_partkey, p_mfgr, s_address, s_phone, s_comment
from part, supplier, partsupp, nation, region
where
p_partkey = ps_partkey
and s_suppkey = ps_suppkey
and p_size = 36
and p_type like '%BRASS'
and s_nationkey = n_nationkey
and n_regionkey = r_regionkey
and r_name = 'AMERICA'
and ps_supplycost = (
select
min(ps_supplycost)
from partsupp, supplier, nation, region
where
p_partkey = ps_partkey
and s_suppkey = ps_suppkey
and s_nationkey = n_nationkey
and n_regionkey = r_regionkey
and r_name = 'AMERICA'
)
order by s_acctbal desc, n_name, s_name, p_partkey
LIMIT 100;
QUERY PLAN
----------------------------------------------------------------------------------------------------------
Limit
-> Sort
Sort Key: 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
Workers Planned: 4
-> Parallel Index Scan using <strong>part_pkey</strong> on part
Filter: (((p_type)::text ~~ '%BRASS'::text) AND (p_size = 36))
-> Materialize
-> Sort
Sort Key: partsupp.ps_partkey
-> Nested Loop
-> Nested Loop
Join Filter: (nation.n_regionkey = region.r_regionkey)
-> Seq Scan on region
Filter: (r_name = 'AMERICA'::bpchar)
-> Hash Join
Hash Cond: (supplier.s_nationkey = nation.n_nationkey)
-> Seq Scan on supplier
-> Hash
-> Seq Scan on nation
-> Index Scan using idx_partsupp_suppkey on partsupp
Index Cond: (ps_suppkey = supplier.s_suppkey)
SubPlan 1
-> Aggregate
-> Nested Loop
Join Filter: (nation_1.n_regionkey = region_1.r_regionkey)
-> Seq Scan on region region_1
Filter: (r_name = 'AMERICA'::bpchar)
-> Nested Loop
-> Nested Loop
-> Index Scan using idx_partsupp_partkey on partsupp partsupp_1
Index Cond: (part.p_partkey = ps_partkey)
-> Index Scan using supplier_pkey on supplier supplier_1
Index Cond: (s_suppkey = partsupp_1.ps_suppkey)
-> Index Scan using nation_pkey on nation nation_1
Index Cond: (n_nationkey = supplier_1.s_nationkey)
Die "Merge Join"-nodus is bokant die "Gather Merge". So samesmelting gebruik nie parallelle verwerking nie. Maar die "Parallel Index Scan"-knooppunt help steeds met die segment part_pkey.
Seksie verbinding
In PostgreSQL 11 verbinding deur afdelings by verstek gedeaktiveer: dit het baie duur skedulering. Tabelle met soortgelyke verdeling kan afdeling vir afdeling saamgevoeg word. Dit sal maak dat Postgres kleiner hash-tabelle gebruik. Elke verbinding van seksies kan parallel wees.
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)
Die belangrikste ding is dat die verbinding deur seksies net parallel is as hierdie seksies groot genoeg is.
Parallelle Bylae
Parallelle Bylae kan gebruik word in plaas van verskillende blokke in verskillende werkstrome. Dit gebeur gewoonlik met UNION ALL-navrae. Die nadeel is minder gelyktydig, aangesien elke werkerproses slegs 1 versoek hanteer.
Daar is 2 werkerprosesse wat hier loop, alhoewel 4 geaktiveer is.
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 <= date '2000-12-01' - interval '105' day;
QUERY PLAN
------------------------------------------------------------------------------------------------
Gather
Workers Planned: 2
-> Parallel Append
-> Aggregate
-> Seq Scan on lineitem
Filter: (l_shipdate <= '2000-08-18 00:00:00'::timestamp without time zone)
-> Aggregate
-> Seq Scan on lineitem lineitem_1
Filter: (l_shipdate <= '1998-08-18 00:00:00'::timestamp without time zone)
Belangrikste veranderlikes
WORK_MEM beperk die hoeveelheid geheue per proses, nie net versoeke nie: work_mem prosesse verbindings = baie geheue.
max_parallel_workers_per_gather - hoeveel werkerprosesse die uitvoerende program sal gebruik vir parallelle verwerking vanaf die plan.
max_worker_processes - pas die totale aantal werkerprosesse aan by die aantal SVE-kerns op die bediener.
Vanaf weergawe 9.6 kan parallelle verwerking die werkverrigting van komplekse navrae wat baie rye of indekse skandeer, aansienlik verbeter. In PostgreSQL 10 is parallelle verwerking by verstek geaktiveer. Moenie vergeet om dit te deaktiveer op bedieners met 'n swaar OLTP-werklading nie. Opeenvolgende of indeksskanderings is baie hulpbronintensief. As jy nie oor die hele datastel verslag doen nie, kan navrae meer doeltreffend gemaak word deur eenvoudig ontbrekende indekse by te voeg of deur behoorlike partisionering te gebruik.