本章ではPG-Stromの持つ先進機能について説明します。

SSD-to-GPUダイレクトSQL実行

概要

SQLワークロードを高速に処理するには、プロセッサが効率よく処理を行うのと同様に、ストレージやメモリからプロセッサへ高速にデータを供給する事が重要です。処理すべきデータがプロセッサに届いていなければ、プロセッサは手持ち無沙汰になってしまいます。

SSD-to-GPUダイレクトSQL実行機能は、PCIeバスに直結する事で高速なI/O処理を実現するNVMe-SSDと、同じPCIeバス上に接続されたGPUをダイレクトに接続し、ハードウェア限界に近い速度でデータをプロセッサに供給する事でSQLワークロードを高速に処理するための機能です。

通常、ストレージ上に格納されたPostgreSQLデータブロックは、PCIeバスを通していったんCPU/RAMへとロードされます。その後、クエリ実行計画にしたがってWHERE句によるフィルタリングやJOIN/GROUP BYといった処理を行うわけですが、集計系ワークロードの特性上、入力するデータ件数より出力するデータ件数の方がはるかに少ない件数となります。例えば数十億行を読み出した結果をGROUP BYで集約した結果が高々数百行という事も珍しくありません。

言い換えれば、我々はゴミデータを運ぶためにPCIeバス上の帯域を消費しているとも言えますが、CPUがレコードの中身を調べるまでは、その要不要を判断できないため、一般的な実装ではこれは不可避と言えます。

SSD2GPU Direct SQL Execution Overview

SSD-to-GPUダイレクトSQL実行はデータの流れを変え、ストレージ上のデータブロックをPCIeバス上のP2P DMAを用いてGPUに直接転送し、GPUでSQLワークロードを処理する事でCPUが処理すべきレコード数を減らすための機能です。いわば、ストレージとCPU/RAMの間に位置してSQLを処理するためのプリプロセッサとしてGPUを活用し、結果としてI/O処理を高速化するためのアプローチです。

本機能は内部的にNVIDIAのGPUDirect RDMAを使用しています。これはカスタムLinux kernel moduleを利用する事で、GPUデバイスメモリと他のPCIeデバイスの間でP2Pのデータ転送を可能にする基盤技術です。 そのため、本機能を利用するには、PostgreSQLの拡張モジュールであるPG-Stromだけではなく、Linux kernelの拡張モジュールであるNVMe-Stromドライバが必要です。

また、本機能が対応しているのはNVMe仕様のSSDのみです。SASやSATAといったインターフェースで接続されたSSDはサポートされていません。今までに動作実績のあるNVMe-SSDについては 002: HW Validation List が参考になるでしょう。

初期設定

ドライバのインストール

SSD-to-GPUダイレクトSQL実行機能を利用するにはnvme_stromパッケージが必要です。このパッケージはNVMe-SSDとGPU間のP2P DMAを仲介するLinux kernel moduleを含んでおり、HeteroDB Software Distribution Centerから入手可能です。

既にheterodb-swdcパッケージをインストールしている場合、yumコマンドによるインストールも可能です。

$ sudo yum install nvme_strom
            :
================================================================================
 Package             Arch            Version            Repository         Size
================================================================================
Installing:
 nvme_strom          x86_64          0.8-1.el7          heterodb          178 k

Transaction Summary
================================================================================
Install  1 Package
            :
DKMS: install completed.
  Verifying  : nvme_strom-0.8-1.el7.x86_64                                  1/1

Installed:
  nvme_strom.x86_64 0:0.8-1.el7

Complete!

nvme_stromパッケージのインストールが完了すると、以下のようにlsmodコマンドでnvme_stromモジュールが出力されます。

$ lsmod | grep nvme
nvme_strom             12625  0
nvme                   27722  4
nvme_core              52964  9 nvme

テーブルスペースの設計

SSD-to-GPUダイレクトSQL実行は以下の条件で発動します。

  • スキャン対象のテーブルがNVMe-SSDで構成された区画に配置されている。
    • /dev/nvmeXXXXブロックデバイス、または/dev/nvmeXXXXブロックデバイスのみから構成されたmd-raid0区画が対象です。
  • テーブルサイズがpg_strom.nvme_strom_thresholdよりも大きい事。
    • この設定値は任意に変更可能ですが、デフォルト値は本体搭載物理メモリにshared_buffersの設定値の1/3を加えた大きさです。

Note

md-raid0を用いて複数のNVMe-SSD区画からストライピング読出しを行うには、HeteroDB社の提供するエンタープライズサブスクリプションの適用が必要です。

テーブルをNVMe-SSDで構成された区画に配置するには、データベースクラスタ全体をNVMe-SSDボリュームに格納する以外にも、PostgreSQLのテーブルスペース機能を用いて特定のテーブルや特定のデータベースのみをNVMe-SSDボリュームに配置する事ができます。

例えば /opt/nvme にNVMe-SSDボリュームがマウントされている場合、以下のようにテーブルスペースを作成する事ができます。 PostgreSQLのサーバプロセスの権限で当該ディレクトリ配下のファイルを読み書きできるようパーミッションが設定されている必要がある事に留意してください。

CREATE TABLESPACE my_nvme LOCATION '/opt/nvme';

このテーブルスペース上にテーブルを作成するには、CREATE TABLE構文で以下のように指定します。

CREATE TABLE my_table (...) TABLESPACE my_nvme;

あるいは、データベースのデフォルトテーブルスペースを変更するには、ALTER DATABASE構文で以下のように指定します。 この場合、既存テーブルの配置されたテーブルスペースは変更されない事に留意してください。

ALTER DATABASE my_database SET TABLESPACE my_nvme;

運用

GUCパラメータによる制御

SSD-to-GPUダイレクトSQL実行に関連するGUCパラメータは2つあります。

一つはpg_strom.nvme_strom_enabledで、SSD-to-GPUダイレクト機能の有効/無効を単純にon/offします。 本パラメータがoffになっていると、テーブルのサイズや物理配置とは無関係にSSD-to-GPUダイレクトSQL実行は使用されません。デフォルト値はonです。

もう一つのパラメータはpg_strom.nvme_strom_thresholdで、SSD-to-GPUダイレクトSQL実行が使われるべき最小のテーブルサイズを指定します。

テーブルの物理配置がNVMe-SSD区画(または、NVMe-SSDのみで構成されたmd-raid0区画)上に存在し、かつ、テーブルのサイズが本パラメータの指定値よりも大きな場合、PG-StromはSSD-to-GPUダイレクトSQL実行を選択します。 本パラメータのデフォルト値は、システムの物理メモリサイズとshared_buffersパラメータの指定値の1/3です。つまり、初期設定では間違いなくオンメモリで処理しきれないサイズのテーブルに対してだけSSD-to-GPUダイレクトSQL実行を行うよう調整されています。

これは、一回の読み出しであればSSD-to-GPUダイレクトSQL実行に優位性があったとしても、オンメモリ処理ができる程度のテーブルに対しては、二回目以降のディスクキャッシュ利用を考慮すると、必ずしも優位とは言えないという仮定に立っているという事です。

ワークロードの特性によっては必ずしもこの設定が正しいとは限りません。

SSD-to-GPUダイレクトSQL実行の利用を確認する

EXPLAINコマンドを実行すると、当該クエリでSSD-to-GPUダイレクトSQL実行が利用されるのかどうかを確認する事ができます。

以下のクエリの例では、Custom Scan (GpuJoin)によるlineorderテーブルに対するスキャンにNVMe-Strom: enabledとの表示が出ています。この場合、lineorderテーブルからの読出しにはSSD-to-GPUダイレクトSQL実行が利用されます。

# explain (costs off)
select sum(lo_revenue), d_year, p_brand1
from lineorder, date1, part, supplier
where lo_orderdate = d_datekey
and lo_partkey = p_partkey
and lo_suppkey = s_suppkey
and p_category = 'MFGR#12'
and s_region = 'AMERICA'
  group by d_year, p_brand1
  order by d_year, p_brand1;
                                          QUERY PLAN
----------------------------------------------------------------------------------------------
 GroupAggregate
   Group Key: date1.d_year, part.p_brand1
   ->  Sort
         Sort Key: date1.d_year, part.p_brand1
         ->  Custom Scan (GpuPreAgg)
               Reduction: Local
               GPU Projection: pgstrom.psum((lo_revenue)::double precision), d_year, p_brand1
               Combined GpuJoin: enabled
               ->  Custom Scan (GpuJoin) on lineorder
                     GPU Projection: date1.d_year, part.p_brand1, lineorder.lo_revenue
                     Outer Scan: lineorder
                     Depth 1: GpuHashJoin  (nrows 2406009600...97764190)
                              HashKeys: lineorder.lo_partkey
                              JoinQuals: (lineorder.lo_partkey = part.p_partkey)
                              KDS-Hash (size: 10.67MB)
                     Depth 2: GpuHashJoin  (nrows 97764190...18544060)
                              HashKeys: lineorder.lo_suppkey
                              JoinQuals: (lineorder.lo_suppkey = supplier.s_suppkey)
                              KDS-Hash (size: 131.59MB)
                     Depth 3: GpuHashJoin  (nrows 18544060...18544060)
                              HashKeys: lineorder.lo_orderdate
                              JoinQuals: (lineorder.lo_orderdate = date1.d_datekey)
                              KDS-Hash (size: 461.89KB)
                     NVMe-Strom: enabled
                     ->  Custom Scan (GpuScan) on part
                           GPU Projection: p_brand1, p_partkey
                           GPU Filter: (p_category = 'MFGR#12'::bpchar)
                     ->  Custom Scan (GpuScan) on supplier
                           GPU Projection: s_suppkey
                           GPU Filter: (s_region = 'AMERICA'::bpchar)
                     ->  Seq Scan on date1
(31 rows)

Visibility Mapに関する注意事項

現在のところ、PG-StromのGPU側処理では行単位のMVCC可視性チェックを行う事ができません。これは、可視性チェックを行うために必要なデータ構造がホスト側だけに存在するためですが、ストレージ上のブロックを直接GPUに転送する場合、少々厄介な問題が生じます。

NVMe-SSDにP2P DMAを要求する時点では、ストレージブロックの内容はまだCPU/RAMへと読み出されていないため、具体的にどの行が可視であるのか、どの行が不可視であるのかを判別する事ができません。これは、PostgreSQLがレコードをストレージへ書き出す際にMVCC関連の属性と共に書き込んでいるためで、似たような問題がIndexOnlyScanを実装する際に表面化しました。

これに対処するため、PostgreSQLはVisibility Mapと呼ばれるインフラを持っています。これは、あるデータブロック中に存在するレコードが全てのトランザクションから可視である事が明らかであれば、該当するビットを立てる事で、データブロックを読むことなく当該ブロックにMVCC不可視なレコードが存在するか否かを判定する事を可能とするものです。

SSD-to-GPUダイレクトSQL実行はこのインフラを利用しています。つまり、Visibility Mapがセットされており、"all-visible"であるブロックだけがSSD-to-GPU P2P DMAで読み出すようリクエストが送出されます。

Visibility MapはVACUUMのタイミングで作成されるため、以下のように明示的にVACUUMを実行する事で強制的にVisibility Mapを構築する事ができます。

VACUUM ANALYZE linerorder;

インメモリ列キャッシュ

概要

PG-Stromはプロセッサへ高速にデータを供給するためのストレージ関連機能をもう一つ持っています。

インメモリ列キャッシュは、対象テーブルのデータブロックを読み出し、PostgreSQL標準のデータ形式である行データから集計・解析ワークロードに適した列データ形式へと変換し、メモリ上にキャッシュする機能です。

SSD-to-GPUダイレクトSQL実行とは異なり、この機能を利用するには特別なハードウェアは必要ありません。しかし一方で、現在もなおRAMの容量はSSDよりも小さく、目安としてはシステムRAMサイズの60%~75%程度の「大規模でないデータセット」を取り扱うのに向いた機能です。

本機能は「列ストア」ではありません。すなわち、列データに変換しキャッシュされた内容は例えばPostgreSQLサーバプロセスを再起動すれば消えてしまいます。また、キャッシュされた領域を更新するようなUPDATE文を実行すると、PG-Stromは当該キャッシュを消去します。 これは、列データ形式は本質的に更新ワークロードに弱い事を踏まえた上での設計です。つまり、行ストアの更新に対して整合性を保ったまま列ストアを更新しようとすると、書き込み性能の大幅な劣化は不可避です。一方で、単純に更新されたブロックを含む列キャッシュを消去(invalidation)するだけであれば、ほとんど処理コストはかかりません。 PG-Stromは行データであっても列データであっても、起動するGPUプログラムを変更するだけで対応可能です。すなわち、列キャッシュが消去され、通常通りPostgreSQLのshared bufferからデータを読み出さざるを得ない状況であっても柔軟に対応する事ができるのです。

overview of in-memory columnar cache

初期設定

列キャッシュの格納先

pg_strom.ccache_base_dirパラメータによって列キャッシュの格納先を指定する事ができます。デフォルト値は/dev/shmで、これは一般的なLinxディストリビューションにおいてtmpfsが配置されているパスであり、この配下に作成されたファイルは二次記憶装置のバッキングストアを持たない揮発性のデータとなります。

このパラメータを変更する事で、例えばNVMe-SSD等、より大容量かつリーズナブルに高速なストレージ領域をバッキングストアとする列キャッシュを構築する事ができます。ただし、列キャッシュの更新はたとえ一行であってもその前後の領域を含むチャンク全体(128MB単位)の無効化を引き起こす事は留意してください。I/Oを伴う読み書きが頻発するような状況になると、意図しない性能劣化を招く可能性があります。

列キャッシュビルダの設定

PG-Stromは一つまたは複数のバックグラウンドワーカーを使用して、インメモリ列キャッシュを非同期かつ自動的に構築する事ができます。この処理を行うバックグラウンドワーカーを列キャッシュビルダーと呼びます。

列キャッシュビルダーは、ユーザのSQLを処理するセッションの動作とは非同期に、指定されたデータベース内のテーブルのうち列キャッシュを構築すべき対象をラウンドロビンでスキャンし、これを列データへと変換した上でキャッシュします。

一度列キャッシュが構築されると、他の全てのバックエンドからこれを参照する事ができます。一般的なディスクキャッシュのメカニズムとは異なり、列キャッシュが構築されていない領域へのアクセスであっても、列キャッシュをオンデマンドで作成する事はありません。この場合は、通常のPostgreSQLのストレージシステムを通して行データを参照する事となります。

列キャッシュビルダの数は起動時に決まっていますので、これを増やすには後述のpg_strom.ccache_num_buildersパラメータを設定し、PostgreSQLの再起動が必要です。 また、列キャッシュビルダは特定のデータベースに紐付けられますので、複数のデータベースで列キャッシュを使用する場合には、少なくともデータベース数以上の列キャッシュビルダが存在している事が必要です。

列キャッシュビルダを紐づけるデータベースを指定するには、pg_strom.ccache_databasesパラメータを指定します。 このパラメータの指定には特権ユーザ権限が必要ですが、PostgreSQLの実行中にも変更する事が可能です。(もちろん、postgresql.confに記載して起動時に設定する事も可能です。)

データベース名をカンマ区切りで指定すると、列キャッシュビルダが順番に指定したデータベースに関連付けられていきます。例えば、列キャッシュビルダが5プロセス存在し、postgres,my_test,benchmarkという3つのデータベースをpg_strom.ccache_databasesに指定した場合、postgresおよびmy_testデータベースには2プロセスの、benchmarkデータベースには1プロセスの列キャッシュビルダが割り当てられる事になります。

対象テーブルの設定

DB管理者は列キャッシュに格納すべきテーブルを予め指定する必要があります。

SQL関数pgstrom_ccache_enabled(regclass)は、引数で指定したテーブルを列キャッシュの構築対象に加えます。 逆に、SQL関数pgstrom_ccache_disabled(regclass)は、引数で指定したテーブルの列キャッシュの構築対象から外します。

内部的には、これらの操作は対象テーブルに対して更新時のキャッシュ無効化を行うトリガ関数の設定として実装されています。 つまり、キャッシュを無効化する手段を持たないテーブルに対しては列キャッシュを作成しないという事です。

postgres=# select pgstrom_ccache_enabled('t0');
 pgstrom_ccache_enabled
------------------------
 enabled
(1 row)

運用

列キャッシュの状態を確認する

列キャッシュの状態を確認するにはpgstrom.ccache_infoシステムビューを使用します。

チャンク単位で、テーブル、ブロック番号やキャッシュの作成時刻、最終アクセス時刻などを参照する事ができます。

contrib_regression_pg_strom=# SELECT * FROM pgstrom.ccache_info ;
 database_id | table_id | block_nr | nitems  |  length   |             ctime             |             atime
-------------+----------+----------+---------+-----------+-------------------------------+-------------------------------
       13323 | 25887    |   622592 | 1966080 | 121897472 | 2018-02-18 14:31:30.898389+09 | 2018-02-18 14:38:43.711287+09
       13323 | 25887    |   425984 | 1966080 | 121897472 | 2018-02-18 14:28:39.356952+09 | 2018-02-18 14:38:43.514788+09
       13323 | 25887    |    98304 | 1966080 | 121897472 | 2018-02-18 14:28:01.542261+09 | 2018-02-18 14:38:42.930281+09
         :       :             :         :          :                :                               :
       13323 | 25887    |    16384 | 1963079 | 121711472 | 2018-02-18 14:28:00.647021+09 | 2018-02-18 14:38:42.909112+09
       13323 | 25887    |   737280 | 1966080 | 121897472 | 2018-02-18 14:34:32.249899+09 | 2018-02-18 14:38:43.882029+09
       13323 | 25887    |   770048 | 1966080 | 121897472 | 2018-02-18 14:28:57.321121+09 | 2018-02-18 14:38:43.90157+09
(50 rows)

列キャッシュの利用を確認する

あるクエリが列キャッシュを使用する可能性があるかどうか、EXPLAINコマンドを使用して確認する事ができます。

以下のクエリは、テーブルt0t1をジョインしますが、t0に対するスキャンを含むCustom Scan (GpuJoin)CCache: enabledと表示されています。 これは、t0に対するスキャンの際に列キャッシュを使用する可能性がある事を示しています。ただし、実際に使われるかどうかはクエリが実行されるまで分かりません。並行する更新処理の影響で、列キャッシュが破棄される可能性もあるからです。

postgres=# EXPLAIN SELECT id,ax FROM t0 NATURAL JOIN t1 WHERE aid < 1000;

                                  QUERY PLAN
-------------------------------------------------------------------------------
 Custom Scan (GpuJoin) on t0  (cost=12398.65..858048.45 rows=1029348 width=12)
   GPU Projection: t0.id, t1.ax
   Outer Scan: t0  (cost=10277.55..864623.44 rows=1029348 width=8)
   Outer Scan Filter: (aid < 1000)
   Depth 1: GpuHashJoin  (nrows 1029348...1029348)
            HashKeys: t0.aid
            JoinQuals: (t0.aid = t1.aid)
            KDS-Hash (size: 10.78MB)
   CCache: enabled
   ->  Seq Scan on t1  (cost=0.00..1935.00 rows=100000 width=12)
(10 rows)

EXPLAIN ANALYZEコマンドを使用すると、クエリが実際に列キャッシュを何回参照したのかを知る事ができます。

先ほどのクエリを実行すると、t0に対するスキャンを含むCustom Scan (GpuJoin)CCache Hits: 50と表示されています。 これは、列キャッシュへの参照が50回行われた事を示しています。列キャッシュのチャンクサイズは128MBですので、合計で6.4GB分のストレージアクセスが列キャッシュにより代替された事となります。

postgres=# EXPLAIN ANALYZE SELECT id,ax FROM t0 NATURAL JOIN t1 WHERE aid < 1000;

                                    QUERY PLAN

-------------------------------------------------------------------------------------------
 Custom Scan (GpuJoin) on t0  (cost=12398.65..858048.45 rows=1029348 width=12)
                              (actual time=91.766..723.549 rows=1000224 loops=1)
   GPU Projection: t0.id, t1.ax
   Outer Scan: t0  (cost=10277.55..864623.44 rows=1029348 width=8)
                   (actual time=7.129..398.270 rows=100000000 loops=1)
   Outer Scan Filter: (aid < 1000)
   Rows Removed by Outer Scan Filter: 98999776
   Depth 1: GpuHashJoin  (plan nrows: 1029348...1029348, actual nrows: 1000224...1000224)
            HashKeys: t0.aid
            JoinQuals: (t0.aid = t1.aid)
            KDS-Hash (size plan: 10.78MB, exec: 64.00MB)
   CCache Hits: 50
   ->  Seq Scan on t1  (cost=0.00..1935.00 rows=100000 width=12)
                       (actual time=0.011..13.542 rows=100000 loops=1)
 Planning time: 23.390 ms
 Execution time: 1409.073 ms
(13 rows)

DROP DATABASEコマンドに関する注意事項

列キャッシュビルダを使用して非同期に列キャッシュを構築する場合、内部的にはバックグラウンドワーカープロセスが指定されたデータベースに接続し続ける事になります。 DROP DATABASEコマンドを使用してデータベースを削除する時、PostgreSQLは当該データベースに接続しているセッションが存在するかどうかをチェックします。この時、ユーザセッションが一つも存在していないにも関わらず、列キャッシュビルダがデータベースへの接続を保持し続ける事でDROP DATABASEコマンドが失敗してしまいます。

これを避けるには、DROP DATABASEコマンドの実行前に、pg_strom.ccache_databasesパラメータから当該データベースを除外してください。列キャッシュビルダは直ちに再起動し、新しい設定に基づいてデータベースへの接続を試みます。

GPUメモリストア(gstore_fdw)

概要

通常、PG-StromはGPUデバイスメモリを一時的にだけ利用します。クエリの実行中に必要なだけのデバイスメモリを割り当て、その領域にデータを転送してSQLワークロードを実行するためにGPUカーネルを実行します。GPUカーネルの実行が完了すると、当該領域は速やかに開放され、他のワークロードでまた利用する事が可能となります。

これは複数セッションの並行実行やGPUデバイスメモリよりも巨大なテーブルのスキャンを可能にするための設計ですが、状況によっては必ずしも適切ではない場合もあります。

典型的な例は、それほど巨大ではなくGPUデバイスメモリに載る程度の大きさのデータに対して、繰り返し様々な条件で計算を行うといった利用シーンです。これは機械学習やパターンマッチ、類似度サーチといったワークロードが該当します。 S

現在のGPUにとって、数GB程度のデータをオンメモリで処理する事はそれほど難しい処理ではありませんが、PL/CUDA関数の呼び出しの度にGPUへロードすべきデータをCPUで加工し、これをGPUへ転送するのはコストのかかる処理です。

加えて、PostgreSQLの可変長データには1GBのサイズ上限があるため、これをPL/CUDA関数の引数として与える場合、データサイズ自体は十分にGPUデバイスメモリに載るものであってもデータ形式には一定の制約が存在する事になります。

GPUメモリストア(gstore_fdw)は、あらかじめGPUデバイスメモリを確保しデータをロードしておくための機能です。 これにより、PL/CUDA関数の呼び出しの度に引数をセットアップしたりデータを転送する必要がなくなるほか、GPUデバイスメモリの容量が許す限りデータを確保する事ができますので、可変長データの1GBサイズ制限も無くなります。

gstore_fdwはその名の通り、PostgreSQLの外部データラッパ(Foreign Data Wrapper)を使用して実装されています。 gstore_fdwの制御する外部テーブル(Foreign Table)に対してINSERTUPDATEDELETEの各コマンドを実行する事で、GPUデバイスメモリ上のデータ構造を更新する事ができます。また、同様にSELECT文を用いてデータを読み出す事ができます。

外部テーブルを通してGPUデバイスメモリに格納されたデータは、PL/CUDA関数から参照する事ができます。 現在のところ、SQLから透過的に生成されたGPUプログラムは当該GPUデバイスメモリ領域を参照する事はできませんが、将来のバージョンにおいて改良が予定されています。

GPU memory store

初期設定

通常、外部テーブルを作成するには以下の3ステップが必要です。

  • CREATE FOREIGN DATA WRAPPERコマンドにより外部データラッパを定義する
  • CREATE SERVERコマンドにより外部サーバを定義する
  • CREATE FOREIGN TABLEコマンドにより外部テーブルを定義する

このうち、最初の2ステップはCREATE EXTENSION pg_stromコマンドの実行に含まれており、個別に実行が必要なのは最後のCREATE FOREIGN TABLEのみです。

CREATE FOREIGN TABLE ft (
    id int,
    signature smallint[] OPTIONS (compression 'pglz')
)
SERVER gstore_fdw OPTIONS(pinning '0', format 'pgstrom');

CREATE FOREIGN TABLEコマンドを使用して外部テーブルを作成する際、いくつかのオプションを指定することができます。

SERVER gstore_fdwは必須です。外部テーブルがgstore_fdwによって制御されることを指定しています。

OPTIONS句では以下のオプションがサポートされています。

名前 対象 説明
pinning テーブル デバイスメモリを確保するGPUのデバイス番号を指定します。
format テーブル GPUデバイスメモリ上の内部データ形式を指定します。デフォルトはpgstromです。
compression カラム 可変長データを圧縮して保持するかどうかを指定します。デフォストは非圧縮です。

formatオプションで選択可能なパラメータは、現在のところpgstromのみです。これは、PG-Stromがインメモリ列キャッシュの内部フォーマットとして使用しているものと同一です。 純粋にSQLを用いてデータの入出力を行うだけであればユーザが内部データ形式を意識する必要はありませんが、PL/CUDA関数をプログラミングしたり、IPCハンドルを用いて外部プログラムとGPUデバイスメモリを共有する場合には考慮が必要です。

compressionオプションで選択可能なパラメータは、現在のところplgzのみです。これは、PostgreSQLが可変長データを圧縮する際に用いているものと同一の形式で、PL/CUDA関数からはGPU内関数pglz_decompress()を呼び出す事で展開が可能です。圧縮アルゴリズムの特性上、例えばデータの大半が0であるような疎行列を表現する際に有用です。

運用

データのロード

通常のテーブルと同様にINSERT、UPDATE、DELETEによって外部テーブルの背後に存在するGPUデバイスメモリを更新する事ができます。

ただし、gstore_fdwはこれらコマンドの実行開始時にSHARE UPDATE EXCLUSIVEロックを獲得する事に注意してください。これはある時点において1トランザクションのみがgstore_fdw外部テーブルを更新できることを意味します。 この制約は、PL/CUDA関数からgstore_fdw外部テーブルを参照するときに個々のレコード単位で可視性チェックを行う必要がないという特性を得るためのトレードオフです。

また、gstore_fdw外部テーブルに書き込まれた内容は、通常のテーブルと同様にトランザクションがコミットされるまでは他のセッションからは不可視です。 この特性は、トランザクションの原子性を担保するには重要な性質ですが、古いバージョンを参照する可能性のある全てのトランザクションがコミットまたはアボートするまでの間は、古いバージョンのgstore_fdw外部テーブルの内容をGPUデバイスメモリに保持しておかねばならない事を意味します。

そのため、通常のテーブルと同様にINSERT、UPDATE、DELETEが可能であるとはいえ、数行を更新してトランザクションをコミットするという事を繰り返すのは避けるべきです。基本的には大量行のINSERTによるバルクロードを行うべきです。

通常のテーブルとは異なり、gstore_fdwに記録された内容は揮発性です。つまり、システムの電源断やPostgreSQLの再起動によってgstore_fdw外部テーブルの内容は容易に失われてしまいます。したがって、gstore_fdw外部テーブルにロードするデータは、他のデータソースから容易に復元可能な形にしておくべきです。

デバイスメモリ消費量の確認

gstore_fdwによって消費されるデバイスメモリのサイズを確認するにはpgstrom.gstore_fdw_chunk_infoシステムビューを参照します。

ああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ

postgres=# select * from pgstrom.gstore_fdw_chunk_info ;
 database_oid | table_oid | revision | xmin | xmax | pinning | format  |  rawsize  |  nitems
--------------+-----------+----------+------+------+---------+---------+-----------+----------
        13806 |     26800 |        3 |    2 |    0 |       0 | pgstrom | 660000496 | 15000000
        13806 |     26797 |        2 |    2 |    0 |       0 | pgstrom | 440000496 | 10000000
(2 rows)

nvidia-smiコマンドを

$ nvidia-smi
Wed Apr  4 15:11:50 2018
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 390.30                 Driver Version: 390.30                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla P40           Off  | 00000000:02:00.0 Off |                    0 |
| N/A   39C    P0    52W / 250W |   1221MiB / 22919MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0      6885      C   ...bgworker: PG-Strom GPU memory keeper     1211MiB |
+-----------------------------------------------------------------------------+

内部データ形式

gstore_fdwがGPUデバイスメモリ上にデータを保持する際の内部データ形式の詳細はノートを参照してください。

  • pgstromフォーマットの詳細
    • ここにノートのリンク

関連機能

CUDAにはcuIpcGetMemHandle()およびcuIpcOpenMemHandle()というAPIが用意されています。前者を用いてアプリケーションプログラムが確保したGPUデバイスメモリのユニークな識別子を取得し、後者を用いて別のアプリケーションプログラムから同一のGPUデバイスメモリを参照する事が可能となります。言い換えれば、ホストシステムにおける共有メモリのような仕組みを備えています。

このユニークな識別子はCUipcMemHandle型のオブジェクトで、内部的には単純な64バイトのバイナリデータです。 本節ではCUipcMemHandle識別子を利用して、PostgreSQLと外部プログラムの間でGPUを介したデータ交換を行うための関数について説明します。

gstore_export_ipchandle(reggstore)

本関数は、gstore_fdw制御下の外部テーブルがGPU上に確保しているデバイスメモリのCUipcMemHandle識別子を取得し、bytea型のバイナリデータとして出力します。 外部テーブルが空でGPU上にデバイスメモリを確保していなければNULLを返します。

  • 第1引数(ftable_oid): 外部テーブルのOID。reggstore型なので、外部テーブル名を文字列で指定する事もできる。
  • 戻り値: CUipcMemHandle識別子のbytea型表現。
# select gstore_export_ipchandle('ft');
                                                      gstore_export_ipchandle

------------------------------------------------------------------------------------------------------------------------------------
 \xe057880100000000de3a000000000000904e7909000000000000800900000000000000000000000000020000000000005c000000000000001200d0c10101005c
(1 row)

lo_import_gpu(int, bytea, bigint, bigint, oid=0)

本関数は、外部アプリケーションがGPU上に確保したデバイスメモリ領域をPostgreSQL側で一時的にオープンし、当該領域の内容を読み出してPostgreSQLラージオブジェクトとして書き出します。 第5引数で指定したラージオブジェクトが既に存在する場合、ラージオブジェクトはGPUデバイスメモリから読み出した内容で置き換えられます。ただし所有者・パーミッション設定は保持されます。これ以外の場合は、新たにラージオブジェクトを作成し、GPUデバイスメモリから読み出した内容を書き込みます。

  • 第1引数(device_nr): デバイスメモリを確保したGPUデバイス番号
  • 第2引数(ipc_mhandle): CUipcMemHandle識別子のbytea型表現。
  • 第3引数(offset): 読出し開始位置のデバイスメモリ領域先頭からのオフセット
  • 第4引数(length): バイト単位での読出しサイズ
  • 第5引数(loid): 書き込むラージオブジェクトのOID。省略した場合 0 が指定されたものと見なす。
  • 戻り値: 書き込んだラージオブジェクトのOID

lo_export_gpu(oid, int, bytea, bigint, bigint)

本関数は、外部アプリケーションがGPU上に確保したデバイスメモリ領域をPostgreSQL側で一時的にオープンし、当該領域へPostgreSQLラージオブジェクトの内容を書き出します。 ラージオブジェクトのサイズが指定された書き込みサイズよりも小さい場合、残りの領域は 0 でクリアされます。

  • 第1引数(loid): 読み出すラージオブジェクトのOID
  • 第2引数(device_nr): デバイスメモリを確保したGPUデバイス番号
  • 第3引数(ipc_mhandle): CUipcMemHandle識別子のbytea型表現。
  • 第4引数(offset): 書き込み開始位置のデバイスメモリ領域先頭からのオフセット
  • 第5引数(length): バイト単位での書き込みサイズ
  • 戻り値: 実際に書き込んだバイト数。指定されたラージオブジェクトの大きさがlengthよりも小さな場合、lengthよりも小さな値を返す事がある。