Parallelle navrae in PostgreSQL

Parallelle navrae in PostgreSQL
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.

  1. Laai TPC-H_Tools_v2.17.3.zip af (of later) van offsite TPC.
  2. Hernoem makefile.suite na Makefile en wysig soos hier beskryf: https://github.com/tvondra/pg_tpch . Stel die kode saam met make.
  3. Genereer data: ./dbgen -s 10 skep 'n 23 GB databasis. Dit is genoeg om die prestasieverskil tussen parallelle en nie-parallelle navrae te sien.
  4. Skakel lêers om tbl в csv с for и sed.
  5. Kloon die bewaarplek pg_tpch en kopieer die lêers csv в pg_tpch/dss/data.
  6. Skep versoeke met 'n opdrag qgen.
  7. 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.

Hoeveel werkerprosesse het jy nodig?

Die minimum limiet stel die parameter max_parallel_workers_per_gather. Dan neem die versoek eksekuteur werker prosesse uit die poel, beperk deur die parameter max_parallel_workers size. Die laaste beperking is max_worker_processes, wat die totale aantal agtergrondprosesse is.

Indien dit nie moontlik was om 'n werkerproses toe te ken nie, sal verwerking enkelproses wees.

Die navraagbeplanner kan werkvloei verminder afhangende van die grootte van die tabel of indeks. Daar is opsies hiervoor. min_parallel_table_scan_size и min_parallel_index_scan_size.

set min_parallel_table_scan_size='8MB'
8MB table => 1 worker
24MB table => 2 workers
72MB table => 3 workers
x => log(x / min_parallel_table_scan_size) / log(3) + 1 worker

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.

Geneste lus aansluitings - geneste lus aansluiting

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.
  • max_parallel_workers - dieselfde, maar vir parallelle werkstrome.

Resultate van

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.

verwysings

Bron: will.com

Voeg 'n opmerking