CPU modernoek nukleo asko dituzte. Urteak daramatzate aplikazioek datu-baseetara kontsultak bidaltzen paraleloan. Taula bateko errenkada anitzetan txosten-kontsulta bat bada, azkarrago exekutatzen da CPU bat baino gehiago erabiltzean, eta PostgreSQL 9.6 bertsiotik aurrera egin ahal izan du.
3 urte behar izan dira kontsulta paraleloen funtzioa ezartzeko; kodea berridatzi behar izan dugu kontsultaren exekuzioaren fase desberdinetan. PostgreSQL 9.6-k azpiegitura sartu zuen kodea gehiago hobetzeko. Hurrengo bertsioetan, beste kontsulta mota batzuk exekutatzen dira paraleloan.
Murrizketak
Ez gaitu exekuzio paraleloa nukleo guztiak dagoeneko okupatuta badaude, bestela beste eskaerak motelduko dira.
Garrantzitsuena, WORK_MEM balio altuak dituen prozesatze paraleloak memoria asko erabiltzen du - hash-juntura edo ordena bakoitzak work_mem memoria hartzen du.
Latentzia baxuko OLTP kontsultak ezin dira bizkortu exekuzio paraleloarekin. Eta kontsultak errenkada bat itzultzen badu, prozesamendu paraleloak moteldu besterik ez du egingo.
Garatzaileei gustatzen zaie TPC-H erreferentzia erabiltzea. Agian antzeko kontsultak dituzu exekuzio paralelo perfekturako.
Predikatuen blokeorik gabeko SELECT kontsultak bakarrik exekutatzen dira paraleloan.
Batzuetan, indexazio egokia modu paraleloan taula sekuentziala eskaneatzea baino hobea da.
Ez dira onartzen kontsultak eta kurtsoreak pausatzea.
Leiho-funtzioak eta multzo ordenatutako funtzio agregatuak ez dira paraleloak.
I/O lan-kargan ez duzu ezer irabazten.
Ez dago ordenatzeko algoritmo paralelorik. Baina ordenak dituzten kontsultak paraleloan exekutatu daitezke alderdi batzuetan.
Ordeztu CTE (WITH ...) habiaratutako SELECT batekin prozesaketa paraleloa gaitzeko.
Hirugarrenen datu-bilgarriek oraindik ez dute prozesaketa paraleloa onartzen (baina liteke!)
FULL OUTER JOIN ez da onartzen.
max_rows-ek prozesaketa paraleloa desgaitzen du.
Kontsulta batek PARALLEL SAFE markatuta ez duen funtzio bat badu, hari bakarrekoa izango da.
SERIALIZABLE transakzio isolamendu mailak prozesamendu paraleloa desgaitzen du.
Proba ingurunea
PostgreSQL garatzaileak TPC-H erreferentziazko kontsulten erantzun-denbora murrizten saiatu ziren. Deskargatu erreferentzia eta egokitu PostgreSQLra. TPC-H erreferentzia-erabilera ez ofiziala da hau, ez datu-basea edo hardwarea alderatzeko.
Deskargatu TPC-H_Tools_v2.17.3.zip (edo bertsio berriagoa) TPC-tik kanpo.
Aldatu makefile.suite Makefile-ra eta aldatu hemen azaltzen den moduan: https://github.com/tvondra/pg_tpch . Konpilatu kodea make komandoarekin.
Sortu datuak: ./dbgen -s 10 23 GB-ko datu-base bat sortzen du. Hau nahikoa da kontsulta paraleloen eta ez-paraleloen errendimenduan dagoen aldea ikusteko.
Bihurtu fitxategiak tbl в csv с for и sed.
Klonatu biltegia pg_tpch eta kopiatu fitxategiak csv в pg_tpch/dss/data.
Sortu kontsultak komando batekin qgen.
Kargatu datuak datu-basean komandoarekin ./tpch.sh.
Miaketa sekuentziala paraleloa
Azkarragoa izan daiteke irakurketa paraleloagatik ez, datuak CPU nukleo askotan zabaltzen direlako baizik. Sistema eragile modernoetan, PostgreSQL datu-fitxategiak ondo gordetzen dira cachean. Aurretik irakurrita, PG deabruak eskatzen duena baino bloke handiago bat lor daiteke biltegiratzetik. Hori dela eta, kontsulten errendimendua ez da diskoko I/O-k mugatzen. CPU zikloak kontsumitzen ditu:
irakurri taula-orrietako errenkadak banan-banan;
konparatu kateen balioak eta baldintzak WHERE.
Egin dezagun kontsulta sinple bat 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
Eskaneatu sekuentzialak errenkada gehiegi sortzen ditu agregaziorik gabe, beraz, kontsulta PUZaren nukleo bakar batek exekutatzen du.
Gehitzen baduzu SUM(), ikus dezakezu bi lan-fluxuek kontsulta bizkortzen lagunduko dutela:
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
Agregazio paraleloa
Parallel Seq Scan nodoak agregazio partzialerako errenkadak sortzen ditu. "Agregazio partziala" nodoak lerro hauek mozten ditu erabiliz SUM(). Bukaeran, langile prozesu bakoitzeko SUM kontagailua "Bildu" nodoak biltzen du.
Azken emaitza "Finalizatu agregatua" nodoak kalkulatzen du. Zure agregazio-funtzioak badituzu, ez ahaztu "seguru paralelo" gisa markatzea.
Langile-prozesuen kopurua
Langile-prozesuen kopurua handitu daiteke zerbitzaria berrabiarazi gabe:
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
Zer gertatzen da hemen? 2 aldiz lan-prozesu gehiago izan ziren, eta eskaera 1,6599 aldiz azkarragoa izan zen. Kalkuluak interesgarriak dira. 2 langile prozesu eta lider 1 genituen. Aldaketaren ostean 4+1 bihurtu zen.
Prozesamendu paralelotik gure abiadura maximoa: 5/3 = 1,66 (6) aldiz.
Nola funtzionatzen du?
Prozesuak
Eskaeraren exekuzioa prozesu nagusiarekin hasten da beti. Liderrak paralelo ez den guztia eta prozesamendu paralelo batzuk egiten ditu. Eskaera berdinak egiten dituzten beste prozesu batzuei langile prozesu deitzen zaie. Prozesamendu paraleloak azpiegiturak erabiltzen ditu atzeko langile prozesu dinamikoak (9.4 bertsiotik). PostgreSQL-ren beste zati batzuek hariak baino prozesuak erabiltzen dituztenez, 3 langile-prozesu dituen kontsulta bat prozesamendu tradizionala baino 4 aldiz azkarragoa izan daiteke.
Elkarrekintza
Langile-prozesuak liderrarekin komunikatzen dira mezu-ilara baten bidez (memoria partekatuan oinarrituta). Prozesu bakoitzak 2 ilara ditu: erroreetarako eta tupleetarako.
Mahaia baino 3 aldiz handiagoa den bakoitzean min_parallel_(index|table)_scan_size, Postgres-ek langile prozesu bat gehitzen du. Lan-fluxuen kopurua ez da kostuetan oinarritzen. Mendekotasun zirkularrak inplementazio konplexuak zailtzen ditu. Horren ordez, planifikatzaileak arau sinpleak erabiltzen ditu.
Praktikan, arau hauek ez dira beti egokiak ekoizpenerako, beraz, mahai jakin baterako langile-prozesuen kopurua alda dezakezu: ALTER TABLE ... SET (parallel_workers = N).
Zergatik ez da prozesaketa paraleloa erabiltzen?
Murrizketen zerrenda luzeaz gain, kostuen egiaztapenak ere badaude:
parallel_setup_cost - Eskaera laburren tratamendu paraleloa saihesteko. Parametro honek memoria prestatzeko, prozesua hasteko eta hasierako datu-trukea egiteko denbora kalkulatzen du.
parallel_tuple_cost: lider eta langileen arteko komunikazioa atzeratu daiteke lan-prozesuetako tupla kopuruaren proportzioan. Parametro honek datu-trukearen kostua kalkulatzen du.
Loop habiaratuak elkartzeak
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)
Bilketa azken fasean gertatzen da, beraz, Habiaratua Loop Left Join eragiketa paraleloa da. Parallel Index Only Scan 10. bertsioan bakarrik sartu zen. Serieko eskaneaketa paraleloaren antzera funtzionatzen du. Baldintza c_custkey = o_custkey bezero-kate bakoitzeko eskaera bat irakurtzen du. Beraz, ez da paraleloa.
Hash Batua
Langile prozesu bakoitzak bere hash taula sortzen du PostgreSQL 11 arte. Eta prozesu horietako lau baino gehiago badira, errendimendua ez da hobetuko. Bertsio berrian, hash taula partekatzen da. Langile prozesu bakoitzak WORK_MEM erabil dezake hash taula bat sortzeko.
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
TPC-H-ko 12. kontsultak argi eta garbi erakusten du hash konexio paraleloa. Langile prozesu bakoitzak hash taula komun bat sortzen laguntzen du.
Batu bat egin
Fusion juntadurak izaera ez du paraleloa. Ez kezkatu hau kontsultaren azken urratsa bada - paraleloan exekutatu daiteke oraindik.
-- 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)
"Batu batu" nodoa "Bildu bateratzea"ren gainean dago. Beraz, bat egiteak ez du prozesamendu paraleloa erabiltzen. Baina "Indize paraleloen eskaneatzea" nodoak oraindik ere laguntzen du segmentuarekin part_pkey.
Atalen araberako konexioa
PostgreSQL 11n atalen araberako konexioa lehenespenez desgaituta: programazio oso garestia du. Partizio antzekoa duten taulak partizioz partizio elkartu daitezke. Modu honetan Postgres-ek hash taula txikiagoak erabiliko ditu. Atalen konexio bakoitza paraleloa izan daiteke.
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)
Gauza nagusia da atalen konexioa paraleloa dela atal horiek nahikoa handiak badira.
Eranskin Paraleloa
Eranskin Paraleloa bloke ezberdinen ordez erabil daiteke lan-fluxu desberdinetan. Hau normalean UNION ALL kontsultekin gertatzen da. Desabantaila paralelismo txikiagoa da, langile prozesu bakoitzak eskaera bakarra prozesatzen duelako.
2 lan-prozesu daude martxan hemen, 4 gaituta dauden arren.
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)
Aldagai garrantzitsuenak
WORK_MEMek memoria mugatzen du prozesu bakoitzeko, ez soilik kontsultak: work_mem prozesuak konexioak = memoria asko.
max_parallel_workers_per_gather — Programa exekutatzaileak planaren prozesamendu paralelorako zenbat langile-prozesu erabiliko dituen.
max_worker_processes — Langile-prozesuen guztizko kopurua zerbitzariko CPU-nukleoen kopurura doitzen du.
9.6 bertsiotik aurrera, prozesatze paraleloak errenkada edo indize asko eskaneatzen dituzten kontsulta konplexuen errendimendua asko hobe dezake. PostgreSQL 10-n, prozesaketa paraleloa gaituta dago lehenespenez. Gogoratu OLTP lan karga handia duten zerbitzarietan desgaitzen duzula. Miaketa sekuentzialak edo indizeen azterketak baliabide asko kontsumitzen ditu. Datu-multzo osoari buruzko txostenik ez baduzu exekutatzen, kontsultaren errendimendua hobe dezakezu falta diren indizeak gehituz edo partizio egokia erabiliz.