PG-Strom v5.1リリース

PG-Strom Development Team (17-Apr-2024)

概要

PG-Strom v5.1における主要な変更は点は以下の通りです。

  • パーティションに対応したGpuJoin/PreAggのサポートを追加しました。
  • GPUコードのビルドを起動時に実行環境で行うようになりました。
  • pg2arrowが並列処理に対応しました。
  • CUDA Stackのサイズを適応的に設定するようになりました。
  • 累積的なバグの修正

動作環境

  • PostgreSQL v15.x, v16.x
  • CUDA Toolkit 12.2 以降
  • CUDA ToolkitのサポートするLinuxディストリビューション
  • Intel x86 64bit アーキテクチャ(x86_64)
  • NVIDIA GPU CC 6.0 以降 (Pascal以降; Volta以降を推奨)

パーティション対応

PostgreSQLパーティションへの対応自体はPG-Strom v3.0でも行われていましたが、うまく実行計画を作成できない事が多く、実験的ステータスを脱する事のできないものでした。そこで、PG-Strom v5.1では内部の設計を根本的に見直して再度実装し、再び正式な機能として取り入れました。

以下のlineorderテーブルがパーティション化されており、date1テーブルが非パーティション化テーブルである場合、これまでは、lineorder配下のパーティションテーブルから読み出したデータを全てAppendノードによって結合された後でなければJOINする事ができませんでした。 通常、PG-StromはCPUをバイパスしてNVME-SSDからGPUへデータをロードして各種のSQL処理を行う(GPU-Direct SQL)ため、JOINに先立ってCPU側へデータを戻さねばならないというのは大きなペナルティです。

ssbm=# explain (costs off)
select sum(lo_extendedprice*lo_discount) as revenue
from lineorder,date1
where lo_orderdate = d_datekey
and d_year = 1993
and lo_discount between 1 and 3
and lo_quantity < 25;
                                                                              QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate
   ->  Hash Join
         Hash Cond: (lineorder.lo_orderdate = date1.d_datekey)
         ->  Append
               ->  Custom Scan (GpuScan) on lineorder__p1992 lineorder_1
                     GPU Projection: lo_extendedprice, lo_discount, lo_orderdate
                     GPU Scan Quals: ((lo_discount >= '1'::numeric) AND (lo_discount <= '3'::numeric) AND (lo_quantity < '25'::numeric)) [rows: 91250920 -> 11911380]
                     GPU-Direct SQL: enabled (GPU-0)
               ->  Custom Scan (GpuScan) on lineorder__p1993 lineorder_2
                     GPU Projection: lo_extendedprice, lo_discount, lo_orderdate
                     GPU Scan Quals: ((lo_discount >= '1'::numeric) AND (lo_discount <= '3'::numeric) AND (lo_quantity < '25'::numeric)) [rows: 91008500 -> 11980460]
                     GPU-Direct SQL: enabled (GPU-0)
               ->  Custom Scan (GpuScan) on lineorder__p1994 lineorder_3
                     GPU Projection: lo_extendedprice, lo_discount, lo_orderdate
                     GPU Scan Quals: ((lo_discount >= '1'::numeric) AND (lo_discount <= '3'::numeric) AND (lo_quantity < '25'::numeric)) [rows: 91044060 -> 12150700]
                     GPU-Direct SQL: enabled (GPU-0)
               ->  Custom Scan (GpuScan) on lineorder__p1995 lineorder_4
                     GPU Projection: lo_extendedprice, lo_discount, lo_orderdate
                     GPU Scan Quals: ((lo_discount >= '1'::numeric) AND (lo_discount <= '3'::numeric) AND (lo_quantity < '25'::numeric)) [rows: 91011720 -> 11779920]
                     GPU-Direct SQL: enabled (GPU-0)
               ->  Custom Scan (GpuScan) on lineorder__p1996 lineorder_5
                     GPU Projection: lo_extendedprice, lo_discount, lo_orderdate
                     GPU Scan Quals: ((lo_discount >= '1'::numeric) AND (lo_discount <= '3'::numeric) AND (lo_quantity < '25'::numeric)) [rows: 91305650 -> 11942810]
                     GPU-Direct SQL: enabled (GPU-0)
               ->  Custom Scan (GpuScan) on lineorder__p1997 lineorder_6
                     GPU Projection: lo_extendedprice, lo_discount, lo_orderdate
                     GPU Scan Quals: ((lo_discount >= '1'::numeric) AND (lo_discount <= '3'::numeric) AND (lo_quantity < '25'::numeric)) [rows: 91049100 -> 12069740]
                     GPU-Direct SQL: enabled (GPU-0)
               ->  Custom Scan (GpuScan) on lineorder__p1998 lineorder_7
                     GPU Projection: lo_extendedprice, lo_discount, lo_orderdate
                     GPU Scan Quals: ((lo_discount >= '1'::numeric) AND (lo_discount <= '3'::numeric) AND (lo_quantity < '25'::numeric)) [rows: 53370560 -> 6898138]
                     GPU-Direct SQL: enabled (GPU-0)
               ->  Seq Scan on lineorder__p1999 lineorder_8
                     Filter: ((lo_discount >= '1'::numeric) AND (lo_discount <= '3'::numeric) AND (lo_quantity < '25'::numeric))
         ->  Hash
               ->  Seq Scan on date1
                     Filter: (d_year = 1993)
(37 rows)

PG-Strom v5.1では、非パーティションテーブルをプッシュダウンし、パーティション子テーブルから読み出したデータとJOINしてから結果を返す事ができるようになりました。 場合によってはGROUP-BY処理まで済ませた上でCPU側に戻す事もでき、例えば以下の例では、総数6億件のパーティション子テーブルから検索条件を満たす7千万行を返さねばならないところ、非パーティションテーブルであるdate1とのJOINと、その次に実行する集約関数SUM()をパーティション子テーブルにプッシュダウンする事で、CPUでは僅か8行を処理するだけで済んでいます。

INNER側の読出しが複数回発生するというデメリットはありますが(※将来のバージョンで改修される予定です)、このような書き換えによってCPUで処理すべきデータが大幅に減少し、処理速度の改善に寄与します。

ssbm=# explain (costs off)
select sum(lo_extendedprice*lo_discount) as revenue
from lineorder,date1
where lo_orderdate = d_datekey
and d_year = 1993
and lo_discount between 1 and 3
and lo_quantity < 25;
                                               QUERY PLAN
----------------------------------------------------------------------------------------------------
 Aggregate
   ->  Append
         ->  Custom Scan (GpuPreAgg) on lineorder__p1992 lineorder_1
               GPU Projection: pgstrom.psum(((lineorder_1.lo_extendedprice * lineorder_1.lo_discount))::double precision)
               GPU Scan Quals: ((lineorder_1.lo_discount >= '1'::numeric) AND (lineorder_1.lo_discount <= '3'::numeric) AND (lineorder_1.lo_quantity < '25'::numeric)) [rows: 91250920 -> 11911380]
               GPU Join Quals [1]: (lineorder_1.lo_orderdate = date1.d_datekey) ... [nrows: 11911380 -> 1700960]
               GPU Outer Hash [1]: lineorder_1.lo_orderdate
               GPU Inner Hash [1]: date1.d_datekey
               Inner Siblings-Id: 2
               GPU-Direct SQL: enabled (GPU-0)
               ->  Seq Scan on date1
                     Filter: (d_year = 1993)
         ->  Custom Scan (GpuPreAgg) on lineorder__p1993 lineorder_2
               GPU Projection: pgstrom.psum(((lineorder_2.lo_extendedprice * lineorder_2.lo_discount))::double precision)
               GPU Scan Quals: ((lineorder_2.lo_discount >= '1'::numeric) AND (lineorder_2.lo_discount <= '3'::numeric) AND (lineorder_2.lo_quantity < '25'::numeric)) [rows: 91008500 -> 11980460]
               GPU Join Quals [1]: (lineorder_2.lo_orderdate = date1.d_datekey) ... [nrows: 11980460 -> 1710824]
               GPU Outer Hash [1]: lineorder_2.lo_orderdate
               GPU Inner Hash [1]: date1.d_datekey
               Inner Siblings-Id: 2
               GPU-Direct SQL: enabled (GPU-0)
               ->  Seq Scan on date1
                     Filter: (d_year = 1993)
         ->  Custom Scan (GpuPreAgg) on lineorder__p1994 lineorder_3
               GPU Projection: pgstrom.psum(((lineorder_3.lo_extendedprice * lineorder_3.lo_discount))::double precision)
               GPU Scan Quals: ((lineorder_3.lo_discount >= '1'::numeric) AND (lineorder_3.lo_discount <= '3'::numeric) AND (lineorder_3.lo_quantity < '25'::numeric)) [rows: 91044060 -> 12150700]
               GPU Join Quals [1]: (lineorder_3.lo_orderdate = date1.d_datekey) ... [nrows: 12150700 -> 1735135]
               GPU Outer Hash [1]: lineorder_3.lo_orderdate
               GPU Inner Hash [1]: date1.d_datekey
               Inner Siblings-Id: 2
               GPU-Direct SQL: enabled (GPU-0)
               ->  Seq Scan on date1
                     Filter: (d_year = 1993)
         ->  Custom Scan (GpuPreAgg) on lineorder__p1995 lineorder_4
               GPU Projection: pgstrom.psum(((lineorder_4.lo_extendedprice * lineorder_4.lo_discount))::double precision)
               GPU Scan Quals: ((lineorder_4.lo_discount >= '1'::numeric) AND (lineorder_4.lo_discount <= '3'::numeric) AND (lineorder_4.lo_quantity < '25'::numeric)) [rows: 91011720 -> 11779920]
               GPU Join Quals [1]: (lineorder_4.lo_orderdate = date1.d_datekey) ... [nrows: 11779920 -> 1682188]
               GPU Outer Hash [1]: lineorder_4.lo_orderdate
               GPU Inner Hash [1]: date1.d_datekey
               Inner Siblings-Id: 2
               GPU-Direct SQL: enabled (GPU-0)
               ->  Seq Scan on date1
                     Filter: (d_year = 1993)
         ->  Custom Scan (GpuPreAgg) on lineorder__p1996 lineorder_5
               GPU Projection: pgstrom.psum(((lineorder_5.lo_extendedprice * lineorder_5.lo_discount))::double precision)
               GPU Scan Quals: ((lineorder_5.lo_discount >= '1'::numeric) AND (lineorder_5.lo_discount <= '3'::numeric) AND (lineorder_5.lo_quantity < '25'::numeric)) [rows: 91305650 -> 11942810]
               GPU Join Quals [1]: (lineorder_5.lo_orderdate = date1.d_datekey) ... [nrows: 11942810 -> 1705448]
               GPU Outer Hash [1]: lineorder_5.lo_orderdate
               GPU Inner Hash [1]: date1.d_datekey
               Inner Siblings-Id: 2
               GPU-Direct SQL: enabled (GPU-0)
               ->  Seq Scan on date1
                     Filter: (d_year = 1993)
         ->  Custom Scan (GpuPreAgg) on lineorder__p1997 lineorder_6
               GPU Projection: pgstrom.psum(((lineorder_6.lo_extendedprice * lineorder_6.lo_discount))::double precision)
               GPU Scan Quals: ((lineorder_6.lo_discount >= '1'::numeric) AND (lineorder_6.lo_discount <= '3'::numeric) AND (lineorder_6.lo_quantity < '25'::numeric)) [rows: 91049100 -> 12069740]
               GPU Join Quals [1]: (lineorder_6.lo_orderdate = date1.d_datekey) ... [nrows: 12069740 -> 1723574]
               GPU Outer Hash [1]: lineorder_6.lo_orderdate
               GPU Inner Hash [1]: date1.d_datekey
               Inner Siblings-Id: 2
               GPU-Direct SQL: enabled (GPU-0)
               ->  Seq Scan on date1
                     Filter: (d_year = 1993)
         ->  Custom Scan (GpuPreAgg) on lineorder__p1998 lineorder_7
               GPU Projection: pgstrom.psum(((lineorder_7.lo_extendedprice * lineorder_7.lo_discount))::double precision)
               GPU Scan Quals: ((lineorder_7.lo_discount >= '1'::numeric) AND (lineorder_7.lo_discount <= '3'::numeric) AND (lineorder_7.lo_quantity < '25'::numeric)) [rows: 53370560 -> 6898138]
               GPU Join Quals [1]: (lineorder_7.lo_orderdate = date1.d_datekey) ... [nrows: 6898138 -> 985063]
               GPU Outer Hash [1]: lineorder_7.lo_orderdate
               GPU Inner Hash [1]: date1.d_datekey
               Inner Siblings-Id: 2
               GPU-Direct SQL: enabled (GPU-0)
               ->  Seq Scan on date1
                     Filter: (d_year = 1993)
         ->  Custom Scan (GpuPreAgg) on lineorder__p1999 lineorder_8
               GPU Projection: pgstrom.psum(((lineorder_8.lo_extendedprice * lineorder_8.lo_discount))::double precision)
               GPU Scan Quals: ((lineorder_8.lo_discount >= '1'::numeric) AND (lineorder_8.lo_discount <= '3'::numeric) AND (lineorder_8.lo_quantity < '25'::numeric)) [rows: 150 -> 1]
               GPU Join Quals [1]: (lineorder_8.lo_orderdate = date1.d_datekey) ... [nrows: 1 -> 1]
               GPU Outer Hash [1]: lineorder_8.lo_orderdate
               GPU Inner Hash [1]: date1.d_datekey
               Inner Siblings-Id: 2
               GPU-Direct SQL: enabled (GPU-0)
               ->  Seq Scan on date1
                     Filter: (d_year = 1993)
(82 rows)

GPUコードの起動時ビルド

以前のバージョンのPG-Stromでは、予めビルドされたGPU向けのバイナリモジュールを配布する方式をとっていました。 これはシンプルではあるのですが、PG-Strom(PostgreSQL)実行環境のCUDA ToolkitやNVIDIAドライバのバージョンの組合せによっては、GPUバイナリモジュールを認識できず実行時エラーを起こしてしまう事がありました。典型的には、RPMパッケージをビルドした環境よりも古いバージョンのCUDA ToolkitやNVIDIAドライバが実行環境にインストールされている場合です。

PG-Strom v5.1では、起動時にGPU用のソースコードやCUDA Toolkitのバージョンを確認し、差分があればGPU向けバイナリモジュールをビルドするように変更されました。この修正により、PG-Stromは実行環境にインストールされたGPUデバイス、およびCUDA Toolkit向けのGPUバイナリモジュールを利用することができるようになりました。

一部のPG-Strom用GPUモジュールにはビルドに時間がかかるものがあります。そのため、PG-StromやCUDA Toolkitのバージョンアップ後、初回の起動時にはPG-Stromの機能が利用可能となるまで数分の時間がかかる場合があります。

pg2arrowの並列実行

pg2arrowは新たに-n|--num-workersオプションと-k|--parallel-keysオプションをサポートするようになりました。

-n N_WORKERSは指定した数のスレッドがそれぞれPostgreSQLに接続し、並列にクエリを実行した結果をApache Arrowファイルに書き込みます。クエリには特殊な文字列$(N_WORKERS)$(WORKER_ID)を含む事ができ、これらはPostgreSQLにクエリを投げる際に、それぞれワーカー数とワーカー固有のID値に置き換えられます。ユーザはこれを利用して、各ワーカースレッドが読み出すタプルが互いに重複したり欠損したりしないように調整する必要があります。

もう一つの-k|--parallel-keyオプションは、引数で与えたカンマ区切りのキー値のそれぞれに対してワーカースレッドを起動し、クエリ中の$(PARALLEL_KEY)をキーと置き換えた上で、これをPostgreSQLで実行した結果をApache Arrowファイルとして書き込みます。 例えば、lineorderテーブルがパーティション化されており、子テーブルとして、lineorder__sun, lineorder__mon, ... lineorder__sat が存在した場合、個々のワーカースレッドがパーティションの子テーブルをそれぞれスキャンするといった形で処理を並列化できます。 この場合、-kオプションは-k sun,mon,tue,wed,thu,fri,satと指定し、-cオプションにはSELECT * FROM lineorder__$(PARALLEL_KEY)と指定すれば、7個のワーカースレッドがそれぞれパーティションの子テーブルをスキャンする事になります。

$ pg2arrow -d ssbm -c 'SELECT * FROM lineorder__$(PARALLEL_KEY)' -o /path/to/f_lineorder.arrow -k=sun,mon,tue,wed,thu,fri,sat --progress
worker:1 SQL=[SELECT * FROM lineorder__sun]
worker:3 SQL=[SELECT * FROM lineorder__tue]
worker:2 SQL=[SELECT * FROM lineorder__mon]
worker:4 SQL=[SELECT * FROM lineorder__wed]
worker:5 SQL=[SELECT * FROM lineorder__thu]
   :
   :