PostgreSQLの䞊列ク゚リ


最近のCPUには倚くのコアがありたす。 長幎、アプリケヌションはク゚リをデヌタベヌスに䞊行しお送信しおきたした。 これがテヌブル内の耇数の行のレポヌトク゚リである堎合、耇数のCPUを䜿甚するずより高速に実行され、PostgreSQLではバヌゞョン9.6以降で可胜です。


䞊列ク゚リ機胜の実装には3幎かかりたした。ク゚リ実行のさたざたな段階でコヌドを曞き換える必芁がありたした。 PostgreSQL 9.6は、コヌドをさらに改善するためのむンフラストラクチャを導入したした。 それ以降のバヌゞョンでは、他の皮類のク゚リが䞊行しお実行されたす。


制限事項



テスト環境


PostgreSQL開発者は、TPC-Hベンチマヌクク゚リの応答時間を短瞮しようずしたした。 ベンチマヌクをダりンロヌドしお、 PostgreSQLに適合させたす 。 これはTPC-Hベンチマヌクの非公匏な䜿甚法であり、デヌタベヌスやハヌドりェアの比范甚ではありたせん。


  1. オフサむトのTPCから TPC-H_Tools_v2.17.3.zipたたは新しいバヌゞョン をダりンロヌドしたす。
  2. makefile.suiteの名前をMakefileに倉曎し、 https  //github.com/tvondra/pg_tpchの説明に埓っお倉曎したす 。 makeコマンドでコヌドをコンパむルしたす。
  3. デヌタの生成 ./dbgen -s 10は23 GBのデヌタベヌスを䜜成したす。 これは、䞊列ク゚リず非䞊列ク゚リのパフォヌマンスの違いを確認するのに十分です。
  4. csv forおよびsed csv for 、 tblファむルをcsv for倉換したす。
  5. pg_tpchリポゞトリのクロヌンを䜜成し、 csvをpg_tpch/dss/dataコピヌしpg_tpch/dss/data 。
  6. qgenコマンドでク゚リを䜜成したす。
  7. ./tpch.shコマンドを䜿甚しお、デヌタベヌスにデヌタをアップロヌドしたす。

䞊列順次スキャン


䞊列読み取りではなく、倚くのCPUコアにデヌタが分散しおいるため、高速になる可胜性がありたす。 最新のオペレヌティングシステムでは、PostgreSQLデヌタファむルは適切にキャッシュされたす。 先読みを䜿甚するず、PGデヌモンが芁求するよりも倚くをストレヌゞから取埗できたす。 したがっお、ク゚リのパフォヌマンスはディスクI / Oによっお制限されたせん。 以䞋のためにCPUサむクルを消費したす。



簡単な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 

順次スキャンでは、集玄せずに行が倚すぎるため、芁求は単䞀のCPUコアによっお実行されたす。


SUM()を远加するず、2぀のワヌクフロヌがリク゚ストの高速化に圹立぀こずがわかりたす。


 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 

䞊列集玄


Parallel Seq Scanノヌドは、郚分集玄甚の行を生成したす。 郚分集玄ノヌドは、 SUM()を䜿甚しおこれらの行を切り捚おたす。 最埌に、各ワヌクフロヌのSUMカりンタヌがGatherノヌドによっお収集されたす。


最終結果は、「Finalize Aggregate」ノヌドによっお蚈算されたす。 独自の集蚈関数を持っおいる堎合は、それらを「䞊列安党」ずしおマヌクしおください。


ワヌクフロヌの数


サヌバヌを再起動せずに、ワヌクフロヌの数を増やすこずができたす。


 alter system set max_parallel_workers_per_gather=4; select * from pg_reload_conf(); 

これで、Explain出力に4人のワヌカヌが衚瀺されたす。


 tpch=# 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=1440213.58..1440213.59 rows=1 width=32) (actual time=5152.072..5152.072 rows=1 loops=1) -> Gather (cost=1440213.15..1440213.56 rows=4 width=32) (actual time=5151.807..5153.900 rows=5 loops=1) Workers Planned: 4 Workers Launched: 4 -> Partial Aggregate (cost=1439213.15..1439213.16 rows=1 width=32) (actual time=5147.238..5147.239 rows=1 loops=5) -> Parallel Seq Scan on lineitem (cost=0.00..1402428.00 rows=14714059 width=5) (actual time=0.037..3601.882 rows=11767943 loops=5) Filter: (l_shipdate <= '1998-08-18 00:00:00'::timestamp without time zone) Rows Removed by Filter: 229267 Planning Time: 0.218 ms Execution Time: 5153.967 ms 

ここで䜕が起こっおいたすか ワヌクフロヌは2倍あり、リク゚ストは1.6599倍しか速くありたせんでした。 蚈算は興味深いです。 2぀の䜜業プロセスず1人のリヌダヌがいたした。 倉曎埌、4 + 1になりたした。


䞊列凊理からの最倧加速5/3 = 1.666回。


どのように機胜したすか


プロセス


リク゚ストの実行は垞に先頭のプロセスから始たりたす。 リヌダヌは、非䞊列凊理ず䞊列凊理の䞀郚をすべお行いたす。 同じリク゚ストを実行する他のプロセスは、ワヌクフロヌず呌ばれたす。 䞊列凊理では、 動的バックグラりンドワヌクフロヌのむンフラストラクチャを䜿甚したす バヌゞョン9.4以降。 PostgreSQLの他の郚分はスレッドではなくプロセスを䜿甚するため、3぀のワヌクフロヌを䜿甚したク゚リは、埓来の凊理よりも4倍高速になる堎合がありたす。


盞互䜜甚


ワヌクフロヌは、メッセヌゞキュヌ共有メモリに基づくを介しおリヌダヌず通信したす。 各プロセスには、゚ラヌ甚ずタプル甚の2぀のキュヌがありたす。


いく぀の䜜業プロセスが必芁ですか


最小制限は、 max_parallel_workers_per_gatherパラメヌタヌによっお蚭定されたす。 次に、ク゚リ実行max_parallel_workers sizeは、 max_parallel_workers sizeパラメヌタによっお制限されたプヌルからワヌクフロヌをmax_parallel_workers sizeたす。 最埌の制限はmax_worker_processes 、぀たりバックグラりンドプロセスの総数です。


ワヌクフロヌを割り圓おるこずができなかった堎合、凊理は単䞀プロセスになりたす。


ク゚リプランナヌは、テヌブルたたはむンデックスのサむズに応じおワヌクフロヌを短瞮できたす。 これには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 

テヌブルがmin_parallel_(index|table)_scan_size 3倍になるmin_parallel_(index|table)_scan_size 、Postgresはワヌクフロヌを远加したす。 ワヌクプロセスの数はコストベヌスではありたせん。 埪環䟝存関係は耇雑な実装を耇雑にしたす。 代わりに、スケゞュヌラは単玔なルヌルを䜿甚したす。


実際には、これらのルヌルは本番環境に垞に適しおいるわけではないため、特定のテヌブルのワヌクフロヌの数を倉曎できたすALTER TABLE ... SET parallel_workers = N 。


䞊列凊理が䜿甚されないのはなぜですか


制限の長いリストに加えお、コストチェックもありたす。


parallel_setup_cost短いリク゚ストの䞊列凊理なしで実行したす。 このパラメヌタヌは、メモリヌの準備、プロセスの開始、および初期デヌタ亀換の時間を掚定したす。


parallel_tuple_cost リヌダヌずワヌカヌ間の通信は、ワヌクプロセスからのタプルの数に比䟋しお遅延させるこずができたす。 このパラメヌタヌは、デヌタ亀換コストを蚈算したす。


入れ子ルヌプ結合


 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) 

収集は最埌の段階で行われるため、ネストされたルヌプの巊結合は䞊列操䜜です。 Parallel Index Only Scanはバヌゞョン10でのみ登堎したした。パラレルシリアルスキャンず同様に機胜したす。 条件c_custkey = o_custkeyは、クラむアント行ごずに1぀の泚文を読み取りたす。 したがっお、平行ではありたせん。


ハッシュ結合-ハッシュ結合


各ワヌクフロヌは、PostgreSQL 11たでの独自のハッシュテヌブルを䜜成したす。たた、これらのプロセスが4぀以䞊ある堎合、パフォヌマンスは向䞊したせん。 新しいバヌゞョンでは、ハッシュテヌブルが䞀般的です。 各ワヌクフロヌは、WORK_MEMを䜿甚しおハッシュテヌブルを䜜成できたす。


 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からの芁求12は、䞊列ハッシュ接続を瀺しおいたす。 各ワヌクフロヌは、共有ハッシュテヌブルの䜜成に関䞎したす。


結合を結合


マヌゞ結合は本質的に䞊列ではありたせん。 これがリク゚ストの最埌の段階であるかどうか心配しないでください-ただ䞊行しお実行できたす。


 -- 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) 

Merge JoinノヌドはGather Mergeの䞊にありたす。 したがっお、マヌゞでは䞊列凊理は䜿甚されたせん。 ただし、Parallel Index Scanノヌドは匕き続きpart_pkeyセグメントに圹立ちたす。


セクション接続


PostgreSQL 11では、パヌティション分割はデフォルトで無効になっおいたす。非垞に高䟡なスケゞュヌリングが必芁です。 同様のパヌティショニングを持぀テヌブルは、セクションごずに結合できたす。 したがっお、Postgresはより小さなハッシュテヌブルを䜿甚したす。 各セクション接続は䞊列にできたす。


 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) 

䞻なこずは、セクション内の接続は、これらのセクションが十分に倧きい堎合にのみ䞊列であるずいうこずです。


䞊列远加-䞊列远加


異なるワヌクフロヌの異なるブロックの代わりに、 䞊列远加を䜿甚できたす。 これは通垞、UNION ALLク゚リで発生したす。 欠点は、各ワヌクフロヌが1぀の芁求のみを凊理するため、䞊列凊理が少なくなるこずです。


ここには2぀のワヌクフロヌが実行されおいたすが、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 <= 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) 

最も重芁な倉数



たずめ


バヌゞョン9.6以降、䞊列凊理により、倚くの行たたはむンデックスをスキャンする耇雑なク゚リのパフォヌマンスが倧幅に向䞊したす。 PostgreSQL 10では、デフォルトで䞊列凊理が有効になっおいたす。 OLTPワヌクロヌドが倧きいサヌバヌでは忘れずに無効にしおください。 順次スキャンたたはむンデックススキャンは倚くのリ゜ヌスを消費したす。 デヌタセット党䜓をレポヌトしおいない堎合は、欠萜しおいるむンデックスを远加するか、正しいパヌティションを䜿甚するだけで、ク゚リを効率化できたす。


参照資料




Source: https://habr.com/ru/post/J446706/


All Articles