PG-Strom v6.1リリース

PG-Strom Development Team (18th-Nov-2025)

概要

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

  • Apache Parquetファイルの対応
  • libarrow/libparquetの利用(独自実装からの移行)
  • SELECT INTO Directモード
  • GPUメモリフットプリントの削減
  • 累積的なバグの修正

動作環境

  • PostgreSQL v15以降
  • CUDA Toolkit 13 以降
  • CUDA ToolkitのサポートするLinuxディストリビューション
  • Intel x86 64bit アーキテクチャ(x86_64)
  • NVIDIA GPU CC 7.5 以降 (Turing以降)

Apache Parquetファイルの対応

PG-Strom v6.1ではApache Parquetファイルの読み出しに対応しました。

Arrow_Fdw外部テーブルのオプションでParquetファイルを指定する事で、Arrowファイルと同様に読み出す事ができるようになりました。 複数ファイルを指定する場合、同じスキーマ定義を持つファイルであれば混在する事も可能です。

Apache Parquet形式は、Apache Arrowとよく似た特徴を持つ列指向の構造化データ形式ですが、多様な圧縮オプションを持ち、比較的低速なストレージからデータを読み出す際の帯域がボトルネックとなるケースで有用です。 現在のところ、圧縮データの展開にはCPU上で動作するlibparquetを利用しているためにParquetファイルに対してはGPU-Direct SQLを使用する事ができません。(ストライピング構成のNVME-SSDなど、PCI-Eバスの帯域をフルに活用できる高速なストレージを利用する場合は、引き続きApache Arrowの利用を推奨します)

利用方法は以下の通りです。外部テーブルのオプションfilefilesdirでApache Parquetファイルを指定します。

postgres=# IMPORT FOREIGN SCHEMA weather FROM SERVER arrow_fdw
                            INTO public OPTIONS (file '/tmp/weather.parquet');
IMPORT FOREIGN SCHEMA

postgres=# EXPLAIN SELECT count(*), avg("MinTemp"), avg("MaxTemp") FROM weather WHERE "WindDir9am" like '%N%';
                                     QUERY PLAN
-------------------------------------------------------------------------------------
 Custom Scan (GpuPreAgg) on weather  (cost=100.23..100.25 rows=1 width=72)
   GPU Projection: pgstrom.nrows(), pgstrom.pavg("MinTemp"), pgstrom.pavg("MaxTemp")
   GPU Scan Quals: ("WindDir9am" ~~ '%N%'::text) [plan: 366 -> 366]
   GPU Group Key:
   referenced: "MinTemp", "MaxTemp", "WindDir9am"
   file0: /tmp/weather.parquet (read: 0B, size: 20.51KB)
   Scan-Engine: VFS with GPU0
(7 rows)

postgres=# SELECT count(*), avg("MinTemp"), avg("MaxTemp")
             FROM weather
            WHERE "WindDir9am" like '%N%';
 count |        avg         |        avg
-------+--------------------+--------------------
   164 | 6.6121951219512205 | 19.833536585365856
(1 row)

同一のスキーマ構造を持つファイルであれば、Apache ArrowとParquetの混在も可能です。

postgres=# IMPORT FOREIGN SCHEMA f_ssbm_part FROM SERVER arrow_fdw
                            INTO public OPTIONS (files '/tmp/ssmb_part.arrow,/tmp/ssmb_part.parquet');
IMPORT FOREIGN SCHEMA

postgres=# EXPLAIN ANALYZE
           SELECT count(*), p_color, max(p_size)
             FROM f_ssbm_part
            WHERE p_mfgr LIKE 'MFGR#%'
            GROUP BY p_color;
                                                                  QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------
 Gather  (cost=4116.93..4142.39 rows=200 width=72) (actual time=173.977..174.230 rows=92 loops=1)
   Workers Planned: 2
   Workers Launched: 2
   ->  Parallel Custom Scan (GpuPreAgg) on f_ssbm_part  (cost=3116.93..3118.39 rows=200 width=72) (actual time=58.952..59.007 rows=31 loops=3)
         GPU Projection: pgstrom.nrows(), pgstrom.pmax((p_size)::double precision), p_color
         GPU Scan Quals: (p_mfgr ~~ 'MFGR#%'::text) [plan: 3600000 -> 7500, exec: 3600000 -> 3600000]
         GPU Group Key: p_color
         referenced: p_mfgr, p_color, p_size
         file0: /tmp/ssmb_part.arrow (read: 54.65MB, size: 176.95MB)
         file1: /tmp/ssmb_part.parquet (read: 0B, size: 17.62MB)
         Scan-Engine: VFS with GPU0; vfs=6996, ntuples=3600000
 Planning Time: 0.276 ms
 Execution Time: 174.388 ms
(13 rows)

独自実装からlibarrow/libparquetへの移行

PG-StromがApache Arrow形式に対応したのは2019年リリースのv2.2ですが、この頃はC言語で利用可能なライブラリの品質がまだ十分ではなく、Apache Arrow形式ファイルの読み出しを独自に実装していました。 その後、数年を経てApache Arrow形式が多くの分野で利用されるようになり、また圧縮データの展開を含むApache Parquet形式にも対応するため、本バージョン以降のPG-Stromではlibarrow/libparquetを利用して実装されています。

周辺コマンドのlibarrow/libparquet対応も順次進めており、以下のモジュールに関しては対応が完了しています。 - Arrow_Fdw (PG-Strom本体) - pg2arrow - arrow2csv - tsv2arrow

以下のモジュールに関しても、順次対応を進めていく予定です。 - pcap2arrow - vcf2arrow - fluentd(arrow-file)プラグイン

SELECT INTO Directモード

ETLなどでよく見られるワークロードですが、元となるテーブルをスキャンしその結果を別のテーブルに挿入するケースでは、GPUでのSQL処理の後でPostgreSQLでの処理をバイパスできるケースがあります。本バージョンの新機能であるSELECT INTO Directモードは、そのような場合にGPUでの処理結果をPostgreSQLでの処理をバイパスして直接ストレージに書き込む事で、大量データの書き込みを高速化します。

以下の例は、lineorderテーブル(316GB)をスキャンした結果をCREATE TABLE ASを用いて新しいテーブルへ書き込む例です。 GatherCustom Scan (GpuScan)のactual rows=0となっていますが、SELECT-INTO Directが有効で1106653ブロック(8.44GB)文を書き込んだと表示されています。 これはバグではなく、Custom Scan (GpuScan)の処理結果をPostgreSQLバックエンドへ返す事なくストレージに書き込んでいるため、Gatherノードを通過した結果行は0件であったという事を意味しています。

postgres=# explain analyze create unlogged table lineorder_1995_ship as select * from lineorder where lo_orderdate between 19950101 and 19951231 and lo_shipmode='RAIL';
                                                                                                                               QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Gather  (cost=1100.00..9514869.83 rows=48077662 width=103) (actual time=27233.307..27236.331 rows=0 loops=1)
   Workers Planned: 2
   Workers Launched: 2
   ->  Parallel Custom Scan (GpuScan) on lineorder  (cost=100.00..4706103.63 rows=20032359 width=103) (actual time=27182.877..27182.880 rows=0 loops=3)
         GPU Projection: lo_orderkey, lo_linenumber, lo_custkey, lo_partkey, lo_suppkey, lo_orderdate, lo_orderpriority, lo_shippriority, lo_quantity, lo_extendedprice, lo_ordertotalprice, lo_discount, lo_revenue, lo_supplycost, lo_tax, lo_commit_date, lo_shipmode
         GPU Scan Quals: ((lo_orderdate >= 19950101) AND (lo_orderdate <= 19951231) AND (lo_shipmode = 'RAIL'::bpchar)) [plan: 2400012000 -> 20032360, exec: 2400012063 -> 52012672]
         Scan-Engine: GPU-Direct with 2 GPUs <0,1>; direct=41379519, ntuples=2400012063
         SELECT-INTO Direct: enabled, nblocks=1106653 (8.44GB)
 Planning Time: 0.286 ms
 Execution Time: 27247.873 ms
(10 rows)

SELECT INTO Directモードは以下のような場合に発動します。

  • SELECT INTOまたはCREATE TABLE ASでテーブルを新しく作成する場合
    • したがって、トランザクションはACCESS EXCLUSIVEロックを持っている事が保証されます
  • テーブルがUNLOGGEDテーブルである場合
    • したがって、トランザクションログを書き込む必要はありません。
  • テーブルのアクセスメソッドがheap(PostgreSQLの標準)である場合
  • pg_strom.cpu_fallback = off であること。
    • TOAST化の必要な可変長データが存在する場合、PG-StromはCPU fallbackで処理するため、書き込むべきデータが全てPostgreSQL Blockにインライン格納できるものである事が保証されます。
  • pg_strom.enable_select_into_direct = onであること。

pg_strom.enable_select_into_directパラメータを使って、意図的にSELECT INTO Directモードを無効化する事もできます。 その場合、以下のようにCustom Scan (GpuScan)の処理結果を1行ごとにGatherが受け取り、最終的に、PostgreSQLのテーブルアクセスメソッドを通じてディスクに書き込みが行われますが、GPU-ServiceからUNIXドメインソケットを介してPostgreSQLバックエンドに処理結果を転送したり、それを1行ごとに取り出してメモリ上でコピー・整形するための処理コストにより、処理時間が伸びています。

postgres=# set pg_strom.enable_select_into_direct = off;
SET
postgres=# explain analyze create unlogged table lineorder_1995_ship_normal as select * from lineorder where lo_orderdate between 19950101 and 19951231 and lo_shipmode='RAIL';
                                                                                                                               QUERY PLAN

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Gather  (cost=1100.00..9514869.83 rows=48077662 width=103) (actual time=28.196..9550.290 rows=52012672 loops=1)
   Workers Planned: 2
   Workers Launched: 2
   ->  Parallel Custom Scan (GpuScan) on lineorder  (cost=100.00..4706103.63 rows=20032359 width=103) (actual time=30.414..10766.055 rows=17337557 loops=3)
         GPU Projection: lo_orderkey, lo_linenumber, lo_custkey, lo_partkey, lo_suppkey, lo_orderdate, lo_orderpriority, lo_shippriority, lo_quantity, lo_extendedprice, lo_ordertotalprice, lo_discount, lo_revenue, lo_supplycost, lo_tax, lo_commit_date, lo_shipmode
         GPU Scan Quals: ((lo_orderdate >= 19950101) AND (lo_orderdate <= 19951231) AND (lo_shipmode = 'RAIL'::bpchar)) [plan: 2400012000 -> 20032360, exec: 2400012063 -> 52012672]
         Scan-Engine: GPU-Direct with 2 GPUs <0,1>; direct=41379519, ntuples=2400012063
 Planning Time: 0.577 ms
 Execution Time: 63814.360 ms
(9 rows)

その他の変更点

GPUメモリフットプリントの削減

GPU-Joinで巨大なテーブル同士のJOINを実行する際に、最もシビアな制約条件となるのは、INNER側テーブルをハッシュ表に読み込んだ際にGPUメモリに収まるかどうかという点です。 従来は、PostgreSQLのHeapTupleDataデータ構造を元にハッシュ値やポインタなどを付加したデータ構造を用いており、INNER側テーブル1行あたりペイロード以外に48バイトの領域が必要でした。通常、INNER側テーブルは必要な行だけが読み出されるため、ペイロードよりもヘッダの方がGPUメモリを消費するといった状況も珍しくありませんでした。

本バージョンでは、PostgreSQLのMinimalTupleDataデータ構造を元にして不要なデータを捨象し、また単にパディングとして未使用になっていたエリアにハッシュ値などのデータを詰め込む事で、INNER側テーブル1行あたりペイロード以外の所用データ量を24バイトに圧縮しました。 これはとりわけ大容量のテーブル同士のJOINにおいて有用で、例えば、あるテスト用クエリにおいては従来INNER側テーブルが85GBを要していたGPUメモリ消費を61GBにまで削減する事ができました。(28%削減)

ハードウェア的なGPUメモリサイズを越えない範囲に収めるというのは非常に重要で、例えば、80GBのDRAMを持つNVIDIA H100では85GBのハッシュ表をロードする事はできず、INNERテーブルを2分割してGPU-Joinを2周する必要がありましたが、これをGPUメモリの範囲に収める事で、GPU-Joinを1周するだけで巨大テーブルのJOINを完了できることを意味します。

pg2arrowの--flattenオプション

PostgreSQLが一度に出力できる列は1600個に制限されていますが、非常にスケールの大きなデータセットをApache ArrowまたはParquet形式で出力する場合、この制限が問題となる事があります。 これまでは、主クエリの結果を元にパラメータとして副クエリを実行し、その結果をクライアント側(pg2arrow)で結合する事で1600列以上のApache Arrow形式を出力する--inner-joinおよび--outer-joinオプションを提供していました。しかし、この方法は極めて低速で、大規模なデータセットの出力にはかなりの困難を伴うものでした。

そこで、クエリが複数列の内容をパックした複合型を含む場合、それらを複合型のフィールドとして扱うのではなく、複合型を展開してそれらのサブフィールドを「列」としてApache ArrowまたはParquet形式で書き込むためのオプションが--flattenです。

例えば、3000個の列を持つようなデータセットをダンプしたい場合、100列ずつを複合型としてパックしてそれをクライアント側(pg2arrow)で展開するという方法をとれば、そのクエリは30個の複合型を出力しているに過ぎませんので、PostgreSQLの制限には抵触しません。

この機能により--inner-joinおよび--outer-joinオプションは不要となったため、本バージョンより削除されました。

pg_strom.cpu_fallbackのデフォルト値

pg_strom.cpu_fallbackのデフォルト値がnoticeからoffへ変更されました。

これは、本パラメータの無効化が、GPU-PreAggの最終マージをGPU上で実行する事、GPU-JoinにPinned-Inner-Buffer機能を有効化する事、さらにSELECT INTO Directモードを有効化するための前提条件となっているためです。

累積的なバグの修正

  • [#974] ExplainForeignScan() didn't deliver ancestors, then get_parameter() trapped by assertion
  • [#968] pg2arrow didn't assign timezone for Timestamp
  • [#966] GPU-based RIGHT/FULL OUTER JOIN may fetch datum from non-exists source kvec buffer.
  • [#964] wrong AggPath pathtarget attached on GPU-PreAgg
  • [#963] wrong json variable references
  • [#956] pg2arrow: wrong composite type binary reader logic
  • [#955] __lookupCompositePGType refered different system catalog if no pg_type hint
  • [#954] pg2arrow --set didn't work correctly
  • [#953] PG18 changed TupleDesc layout, thus, it is not legal to change tupdesc->natts
  • [#967] GPU-PreAgg with OUTER-JOIN didn't back the final result
  • [#948] gpupreagg: allows to embed grouping-key and aggregate-functions in expressions
  • [#947] OpenSession had unintentional 1sec wait if other worker already did the final command.
  • [#938] arrow_meta.cpp has wrong type cast (TimeType as TimestampType)
  • [#937] parquet: prevent parquet::arrow::FileReader::ReadRowGroup() calls in concurrent multi-threading
  • [#934] fix build issue related to libarrow/libparquet on RHEL8
  • [#931] allows to build in CUDA 13.0
  • [#929] Hetedodb-extra is marked as recommends, for OSS only installation
  • [#921] an empty inner relation with RIGHT OUTER JOIN makes GPU kernel launch failure
  • [#919] allows to build PG-Strom towards PostgreSQL v18
  • [#918] incorrect GPU-Join using numeric join key
  • [#916] improvement of pinned-inner-buffer memory management
  • [#xxx] force CPU-based Agg-node if no grouping-key aggregation
  • [#xxx] fix label of cuMemRangeGetAttribute() argument
  • [#xxx] has_aggfuncs was not set correctly
  • [#xxx] gpupreagg: corrupted catalog definition for MAX(timestamp/timestamptz)
  • [#xxx] arrow_fdw: ANALYZE didn't fetch any fields of Parquet files
  • [#xxx] arrow_fdw: ForeignPath didn't add parallel_workers, even if parallel_aware is true.
  • [#xxx] arrow_fdw: stat datum for Date/Timestamp epoch drifting
  • [#xxx] fix: LockHeldByMe() was changed at PG17
  • [#xxx] adjust kds_nslots on partitioned inner pinned buffer
  • [#xxx] pushdown of Result node just over the GPU-PreAgg node
  • [#xxx] simple Result has no outer-plan
  • [#xxx] nogroup aggregation might crash on mergeGpuPreAggGroupByBuffer
  • [#xxx] const ColumnChunkMetaData::statistics() potentially returns NULL
  • [#xxx] __dlist_foreach() may goes into infinite loop in c++ code
  • [#xxx] arrow_fdw wrong 'files' option parse, infinite loop by strtok
  • [#xxx] arrow_fdw: chunk_buffer as StringInfoData cannot expand more than 1GB
  • [#xxx] parquetReadArrowTable put Decimal values on misaligned address