CitusDB + PG-StromでScale-up+outする。
PostgreSQL Advent Calendar 2019の14日目です。
PG-Stromの開発をやってると、しばしば聞かれるのが
『マルチノードの並列処理って対応してるんですか?』
という質問。
まぁ、『対応しておりませんし、対応する予定もございません』という回答になるんですが、別にこれはウチのやる気の問題ではなく、PG-StromはPostgreSQLの拡張モジュールとして設計されているため、並列分散処理に関しては他のメカニズムに任せてしまえばよい、というだけの話である。
そこで、今回は同じくPostgreSQLの拡張モジュールとして実装されているスケールアウト機能の Citus と、PG-Stromを組み合わせてちゃんと動作するんですよという事を検証してみる事にする。
Citusとは?
PostgreSQLにデータ分散と並列処理機構を付加する拡張モジュールで、PostgreSQLの拡張モジュールとして実装されている。*1
この人はコーディネータ+多数のワーカーノードという構成になっており、指定したテーブルを複数のワーカーノードに分割して保存するシャーディング機能を持っている。したがって、テーブルサイズが肥大化しても、一台のワーカーノードで処理すべきデータ量を抑える事ができる。
また、巨大テーブルをスキャンする集計クエリを受け取ると、コーディネータはそれを各ワーカーノードで分割実行できるよう切り分け、ワーカーノードは自らの担当範囲だけを集計した上で、コーディネータが最終結果を生成してユーザに返すというMap-Reduceチックな並列処理を行う事ができる。
一部機能を限定はしているものの、Citusの拡張モジュールはオープンソースとして公開されており、お手軽に試すことができる。
開発元の CitusData社 は2011年創業のスタートアップで、2019年には Microsoft に買収される(パチパチ)。現在は Azure上で HyperScale というブランドで展開されている。
検証構成
CitusをPG-Stromを組み合わせて使うケースで最も素直な構成は、各ワーカーノードが GPU + NVME を持ち、そこでPG-Stromが動作するという構成である。
Citusのコーディネータがクエリを受け取った後、それを各ワーカーノードが実行可能な形に書き換えて処理をキックするが、これはワーカーノードにとっては通常のSQL処理と変わらないので、そこでGPUを用いる方が合理的であれば(つまり、オプティマイザがそう判断すれば)GPUを使用するし、NVME-SSDからのデータ読出しであればSSD-to-GPU Direct SQLを使用する事もできる。
少し機材の都合で、自宅のパソコンのPCIEカードを以下のように組み替えて、2台の独立したサーバに相当する構成を作ってみた。
この場合、NVME0~3⇒GPU0と、NVME4~7⇒GPU1へのデータ転送は全く他方に影響を与えないため、I/O負荷は完全に独立して処理されると考えてよい。
まず、これらディスク領域の準備からはじめる。
[kaigai@kujira ~]$ ls -l /dev/nvme*n1p1 brw-rw----. 1 root disk 259, 1 Dec 6 14:47 /dev/nvme0n1p1 brw-rw----. 1 root disk 259, 3 Dec 6 14:47 /dev/nvme1n1p1 brw-rw----. 1 root disk 259, 14 Dec 6 14:47 /dev/nvme2n1p1 brw-rw----. 1 root disk 259, 5 Dec 6 14:47 /dev/nvme3n1p1 brw-rw----. 1 root disk 259, 13 Dec 6 14:47 /dev/nvme4n1p1 brw-rw----. 1 root disk 259, 15 Dec 6 14:47 /dev/nvme5n1p1 brw-rw----. 1 root disk 259, 12 Dec 6 14:47 /dev/nvme6n1p1 brw-rw----. 1 root disk 259, 10 Dec 6 14:47 /dev/nvme7n1p1
ご覧のように、8台のNVME-SSDが見えており、これらを4台ずつ束ねてストライピング構成を作る。
ここで使用しているNVME-SSDはIntel製DC P4510 (U.2; 1.0TB)で、SeqReadのカタログスペックは 2850MB/s。つまり、4台束ねれば理論上 11.4GB/s 程度までは出る事になる。*2
以下のようにmdadm
コマンドを用いて md-raid0 区画を作成する。
最後のは、/etc/mdadm.conf
ファイルを作成して再起動時に区画が復元されるようにするための設定。
[root@kujira kaigai]# mdadm -C /dev/md0 -c 128 -l 0 -n 4 /dev/nvme0n1p1 /dev/nvme1n1p1 /dev/nvme2n1p1 /dev/nvme3n1p1 mdadm: Defaulting to version 1.2 metadata mdadm: array /dev/md0 started. [root@kujira kaigai]# mdadm -C /dev/md1 -c 128 -l 0 -n 4 /dev/nvme4n1p1 /dev/nvme5n1p1 /dev/nvme6n1p1 /dev/nvme7n1p1 mdadm: Defaulting to version 1.2 metadata mdadm: array /dev/md1 started. [root@kujira kaigai]# mdadm --detail --scan > /etc/mdadm.conf
この辺は流れ作業だが、md-raid0区画上にパーティションを切り、Ext4で初期化する。
# fdisk /dev/md0 # fdisk /dev/md1 # mkfs.ext4 -LNVME0 /dev/md0p1 # mkfs.ext4 -LNVME1 /dev/md1p1 # cat /etc/fstab : (snip) : LABEL=NVME0 /nvme/0 ext4 nofail 0 0 LABEL=NVME1 /nvme/1 ext4 nofail 0 0
ここで設定した /nvme/0
ボリューム上にCitus+PG-Stromのワーカー1号を、/nvme/1
ボリューム上にワーカー2号を配置する。
PostgreSQL (Citus + PG-Strom) の構築
まず、各ワーカーノードの構築を行う。物理的にI/Oは切り離されているとはいえ、同じマシンなので、ポート番号を微妙にずらすことにする。
ワーカー1号を 5433 ポートで、ワーカー2号を 5434 ポートで動かすことにする。
initdb
でデータベースクラスタを作成する。
# mkdir /nvme/0/pgdata # chown postgres:postgres -R /nvme/0/pgdata # su - postgres -c 'initdb -D /nvme/0/pgdata'
次に、ワーカー側のpostgreql.conf
を修正する。とはいってもshared_preload_libraries
など基本的なものだけ。
port = 5433 shared_buffers = 12GB work_mem = 10GB max_worker_processes = 100 shared_preload_libraries = 'citus,pg_strom'
これで、ワーカー側のPostgreSQLを起動する事ができる。
# su - postgres -c 'pg_ctl start -D /nvme/0/pgdata'
ログを見ると、2台のTesla P40にそれぞれ近傍のNVME-SSDが4台ずつ認識されている事がわかる。
(見やすさのため、タイムスタンプをカット)
LOG: number of prepared transactions has not been configured, overriding LOG: PG-Strom version 2.3 built for PostgreSQL 11 LOG: PG-Strom: GPU1 Tesla P40 (3840 CUDA cores; 1531MHz, L2 3072kB), RAM 22.38GB (384bits, 3.45GHz), CC 6.1 LOG: PG-Strom: GPU2 Tesla P40 (3840 CUDA cores; 1531MHz, L2 3072kB), RAM 22.38GB (384bits, 3.45GHz), CC 6.1 LOG: - PCIe[0000:17] LOG: - PCIe(0000:17:00.0) LOG: - PCIe(0000:18:00.0) LOG: - PCIe(0000:19:08.0) LOG: - PCIe(0000:1a:00.0) LOG: - PCIe(0000:1b:00.0) LOG: - PCIe(0000:1c:00.0) nvme0 (INTEL SSDPE2KX010T8) LOG: - PCIe(0000:1b:01.0) LOG: - PCIe(0000:1d:00.0) nvme1 (INTEL SSDPE2KX010T8) LOG: - PCIe(0000:1b:02.0) LOG: - PCIe(0000:1e:00.0) nvme2 (INTEL SSDPE2KX010T8) LOG: - PCIe(0000:1b:03.0) LOG: - PCIe(0000:1f:00.0) nvme3 (INTEL SSDPE2KX010T8) LOG: - PCIe(0000:19:10.0) LOG: - PCIe(0000:20:00.0) GPU1 (Tesla P40) LOG: - PCIe[0000:ae] LOG: - PCIe(0000:ae:00.0) LOG: - PCIe(0000:af:00.0) LOG: - PCIe(0000:b0:08.0) LOG: - PCIe(0000:b1:00.0) GPU2 (Tesla P40) LOG: - PCIe(0000:b0:10.0) LOG: - PCIe(0000:b2:00.0) LOG: - PCIe(0000:b3:00.0) LOG: - PCIe(0000:b4:00.0) nvme4 (INTEL SSDPE2KX010T8) LOG: - PCIe(0000:b3:01.0) LOG: - PCIe(0000:b5:00.0) nvme5 (INTEL SSDPE2KX010T8) LOG: - PCIe(0000:b3:02.0) LOG: - PCIe(0000:b6:00.0) nvme6 (INTEL SSDPE2KX010T8) LOG: - PCIe(0000:b3:03.0) LOG: - PCIe(0000:b7:00.0) nvme7 (INTEL SSDPE2KX010T8) LOG: GPU<->SSD Distance Matrix LOG: GPU0 GPU1 LOG: nvme1 ( 5) -1 LOG: nvme0 ( 5) -1 LOG: nvme7 -1 ( 5) LOG: nvme4 -1 ( 5) LOG: nvme5 -1 ( 5) LOG: nvme3 ( 5) -1 LOG: nvme6 -1 ( 5) LOG: nvme2 ( 5) -1 LOG: HeteroDB License: { "version" : 2, "serial_nr" : "HDB-TRIAL", "issued_at" : "6-Dec-2019", "expired_at" : "1-Jan-2030", "gpus" : [ { "uuid" : "GPU-b44c118b-1058-16cb-1cbb-5dbe0fe6181a", "pci_id" : "0000:20:00.0" } , { "uuid" : "GPU-a137b1df-53c9-197f-2801-f2dccaf9d42f", "pci_id" : "0000:b1:00.0" } ] } LOG: listening on IPv6 address "::1", port 5434 LOG: listening on IPv4 address "127.0.0.1", port 5434 LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5434" LOG: listening on Unix socket "/tmp/.s.PGSQL.5434" LOG: redirecting log output to logging collector process HINT: Future log output will appear in directory "log".
この様に、それぞれ/nvme/0
と/nvme/1
上にワーカーのPostgreSQLインスタンスを起動した。
最後に、これらワーカー側に直接接続してCREATE EXTENSION
を実行する。
$ psql -U postgres -p 5433 psql (11.5) Type "help" for help. postgres=# create extension pg_strom; CREATE EXTENSION postgres=# create extension citus; CREATE EXTENSION
次にコーディネータの構築であるが、この人は自分でデータを持つわけでもなく、大量のスキャンを行うわけでもないので、どこか適当な磁気ディスク上の区画でも割り当てておけばよい。
デフォルトの/var/lib/pgdata
にDBを構築し、Citusモジュールだけをロードするように構成した。
以上でコーディネータ、ワーカーの初期設定は完了である。
テーブルの作成とデータの投入
テスト用に、いつもの SSBM (Star Schema Benchmark) のテーブルを構築し、このうち最もサイズの大きなlineorder
テーブルを各ワーカーに分散配置する事にする。
scale factorは401で、DBサイズにすると 353GB 程度。数字に深い意味はないが、これまでこのサイズで性能計測をしていた事が多かったので。
まず、通常通りCREATE TABLE
文を投入する。例えばlineorder
テーブルであれば以下のような、いたって何の変哲もないCREATE TABLE
である。
CREATE TABLE lineorder ( lo_orderkey numeric, lo_linenumber integer, lo_custkey numeric, lo_partkey integer, lo_suppkey numeric, lo_orderdate integer, lo_orderpriority character(15), lo_shippriority character(1), lo_quantity numeric, lo_extendedprice numeric, lo_ordertotalprice numeric, lo_discount numeric, lo_revenue numeric, lo_supplycost numeric, lo_tax numeric, lo_commit_date character(8), lo_shipmode character(10) );
少し Citus のオリジナル要素はこの次。lineorder
を分散配置する場合は、create_distributed_table
関数を用いて、システムに分散配置すべきテーブルと分散キーに用いるカラムを教えてやる。
postgres=# SELECT create_distributed_table('lineorder', 'lo_orderkey'); create_distributed_table -------------------------- (1 row)
テーブルを分散配置せず、各ワーカーに複製をコピーするというやり方もある。この場合はcreate_reference_table
関数を使用する。
postgres=# SELECT create_reference_table('date1'); create_reference_table ------------------------ (1 row)
このように設定する事でdistributed tableとreference tableのJOINをワーカー側で実行する事が可能となり、コーディネータの負荷を最大限に下げる事ができる。
そして、最後にコーディネータ側からデータを投入する。
postgres=# \copy lineorder from program './dbgen-ssbm -X -Tl -s 401' delimiter '|' SSBM (Star Schema Benchmark) Population Generator (Version 1.0.0) Copyright Transaction Processing Performance Council 1994 - 2000 COPY 2406009932
コーディネータ側から見ても実際のデータサイズは分からないが、、、
postgres=# \d+ List of relations Schema | Name | Type | Owner | Size | Description --------+-----------+-------+----------+------------+------------- public | customer | table | postgres | 8192 bytes | public | date1 | table | postgres | 8192 bytes | public | lineorder | table | postgres | 8192 bytes | public | part | table | postgres | 8192 bytes | public | supplier | table | postgres | 8192 bytes | (5 rows)
ワーカー側に接続してみると、確かに分散テーブルが細切れになって保存されている様子が分かる。
(そして reference table は単純にコピーされている)
[kaigai@kujira ~]$ psql -U postgres postgres -p 5433 psql (11.5) Type "help" for help. postgres=# \d+ List of relations Schema | Name | Type | Owner | Size | Description --------+------------------+-------+----------+---------+------------- public | customer_102072 | table | postgres | 1627 MB | public | date1_102075 | table | postgres | 416 kB | public | lineorder_102040 | table | postgres | 11 GB | public | lineorder_102042 | table | postgres | 11 GB | public | lineorder_102044 | table | postgres | 11 GB | public | lineorder_102046 | table | postgres | 11 GB | public | lineorder_102048 | table | postgres | 11 GB | public | lineorder_102050 | table | postgres | 11 GB | public | lineorder_102052 | table | postgres | 11 GB | public | lineorder_102054 | table | postgres | 11 GB | public | lineorder_102056 | table | postgres | 11 GB | public | lineorder_102058 | table | postgres | 11 GB | public | lineorder_102060 | table | postgres | 11 GB | public | lineorder_102062 | table | postgres | 11 GB | public | lineorder_102064 | table | postgres | 11 GB | public | lineorder_102066 | table | postgres | 11 GB | public | lineorder_102068 | table | postgres | 11 GB | public | lineorder_102070 | table | postgres | 11 GB | public | part_102073 | table | postgres | 206 MB | public | supplier_102074 | table | postgres | 528 MB | (20 rows)
vacuum analyzeを走らせる
最後に、SSD-to-GPU Direct SQLに必要な visibility map を強制的に作成するために、vacuum analyze
を実行する。まさかコーディネータ側の空っぽテーブルをvacuumするだけじゃないよね?と思ったが、さすがに杞憂だったというか、ワーカー側できちんと分散処理してくれていた。
postgres=# vacuum analyze lineorder ;
を、実行中のtop
コマンドの出力
top - 16:14:08 up 4:03, 5 users, load average: 5.79, 1.35, 1.04 Tasks: 418 total, 24 running, 394 sleeping, 0 stopped, 0 zombie %Cpu(s): 15.2 us, 33.0 sy, 0.0 ni, 7.9 id, 43.8 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 19651427+total, 525488 free, 5696528 used, 19029225+buff/cache KiB Swap: 8388604 total, 8380412 free, 8192 used. 18758867+avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 150 root 20 0 0 0 0 R 59.3 0.0 0:52.53 kswapd0 17275 postgres 20 0 12.9g 48344 44076 D 56.3 0.0 0:05.91 postgres 151 root 20 0 0 0 0 R 53.7 0.0 0:27.33 kswapd1 321 root 20 0 0 0 0 S 51.0 0.0 0:33.39 kworker/u274:5 17311 root 20 0 0 0 0 S 48.7 0.0 0:01.91 kworker/u274:0 17286 postgres 20 0 12.9g 48016 43760 D 44.7 0.0 0:04.55 postgres 17295 postgres 20 0 12.9g 47956 43704 R 42.3 0.0 0:04.33 postgres 17298 postgres 20 0 12.9g 47932 43680 R 42.0 0.0 0:04.65 postgres 6657 postgres 20 0 12.9g 339180 324212 R 41.3 0.2 6:54.01 postgres 17281 postgres 20 0 12.9g 48340 44076 R 41.3 0.0 0:05.74 postgres 17301 postgres 20 0 12.9g 48180 43928 R 41.3 0.0 0:05.26 postgres 17278 postgres 20 0 12.9g 47804 43540 R 40.7 0.0 0:04.32 postgres 17277 postgres 20 0 12.9g 48180 43916 R 40.3 0.0 0:05.22 postgres 17287 postgres 20 0 12.9g 47928 43660 R 38.7 0.0 0:04.15 postgres 17279 postgres 20 0 12.9g 48000 43732 R 38.3 0.0 0:04.10 postgres 17284 postgres 20 0 12.9g 48056 43796 R 36.3 0.0 0:04.96 postgres 17280 postgres 20 0 12.9g 48196 43928 R 34.0 0.0 0:04.84 postgres 6658 postgres 20 0 12.9g 330544 322924 D 31.3 0.2 6:29.77 postgres 17292 postgres 20 0 12.9g 47804 43552 R 31.3 0.0 0:04.45 postgres 17294 postgres 20 0 12.9g 48068 43812 D 27.7 0.0 0:04.30 postgres 17283 postgres 20 0 12.9g 47832 43560 R 26.0 0.0 0:04.14 postgres 17289 postgres 20 0 12.9g 48060 43800 R 25.7 0.0 0:05.18 postgres 17291 postgres 20 0 12.9g 47804 43552 R 25.7 0.0 0:04.01 postgres 17302 postgres 20 0 12.9g 47940 43688 R 24.0 0.0 0:05.04 postgres 17299 postgres 20 0 12.9g 48000 43744 R 21.7 0.0 0:04.01 postgres 17304 postgres 20 0 12.9g 47792 43540 R 21.3 0.0 0:04.53 postgres 17282 postgres 20 0 12.9g 47828 43564 R 19.3 0.0 0:03.52 postgres 17290 postgres 20 0 12.9g 48080 43816 R 17.3 0.0 0:04.83 postgres 17285 postgres 20 0 12.9g 47956 43688 D 15.0 0.0 0:04.18 postgres 17288 postgres 20 0 12.9g 47980 43724 D 15.0 0.0 0:04.75 postgres 17303 postgres 20 0 12.9g 48084 43828 D 14.0 0.0 0:04.89 postgres 17276 postgres 20 0 12.9g 47968 43692 D 13.0 0.0 0:04.28 postgres 17300 postgres 20 0 12.9g 47800 43548 R 12.3 0.0 0:03.76 postgres
集計クエリを実行してみる(Take.1)
早速、Star Schema Benchmarkの集計クエリを実行してみる事にする。
先ずは実行計画。
postgres=# explain 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 (cost=0.00..0.00 rows=0 width=0) -> Custom Scan (Citus Adaptive) (cost=0.00..0.00 rows=0 width=0) Task Count: 32 Tasks Shown: One of 32 -> Task Node: host=localhost port=5433 dbname=postgres -> Aggregate (cost=958010.91..958010.92 rows=1 width=32) -> Gather (cost=957962.46..958006.83 rows=408 width=8) Workers Planned: 2 -> Parallel Custom Scan (GpuPreAgg) (cost=956962.46..956966.03 rows=204 width=8) Reduction: NoGroup Combined GpuJoin: enabled GPU Preference: GPU1 (Tesla P40) -> Parallel Custom Scan (GpuJoin) on lineorder_102040 lineorder (cost=30257.08..962321.28 rows=590200 width=10) Outer Scan: lineorder_102040 lineorder (cost=30259.93..963572.01 rows=4133020 width=14) Outer Scan Filter: ((lo_discount >= '1'::numeric) AND (lo_discount <= '3'::numeric) AND (lo_quantity < '25'::numeric)) Depth 1: GpuHashJoin (hash-size: 66.06KB, nrows 4133020...1416481) HashKeys: lineorder.lo_orderdate JoinQuals: (lineorder.lo_orderdate = date1.d_datekey) GPU Preference: GPU1 (Tesla P40) -> Seq Scan on date1_102075 date1 (cost=0.00..78.95 rows=365 width=4) Filter: (d_year = 1993) (22 rows)
これを見ると、確かに Citus の独自プランであるCustom Scan (Citus Adaptive)
と、PG-Stromの独自プランであるCustom Scan (GpuPreAgg)
、Custom Scan (GpuJoin)
が混在している。
ではさっそく実行してみると、、、、Out of managed memory
エラーで止まってしまった。要はGPUリソースの食いすぎである。
GPUを使用するプログラムは、GPUデバイス上に「CUDAコンテキスト」と呼ばれる実行時情報を保持する必要がある。これが最低でも150MB程度の大きさがあり、さらに、PG-Stromで処理すべきデータを保持すると、プロセスあたり平均して600MB~800MB程度のメモリを消費する事になる。
今回は、Citusがlineorder
テーブルを32個のセグメントに分割し、ワーカーノード1個あたり16個のセグメントを持っている事になる。加えて、ワーカーノードで実行される集計クエリはPostgreSQLのパラレルクエリ機能により、3並列で実行((Workers Planned: 2
と表示されているため、自分自身+バックグラウンドワーカー×2))されるため、GPUあたり48プロセスが全力で回るという事になる。
さすがにこれは辛みがあるので、そもそも16セグメントに分割されている分、ワーカー側の並列実行を止める事にする。
また、この実行計画には NVMe-Strom: enabled
というメッセージが表示されておらず、せっかくのNVME-SSDなのに、SSD-to-GPU Direct SQLモードを利用できない。
これは、lineorder
が細かく分割された結果、テーブル1個あたりのサイズが、SSD-to-GPU Direct SQLを使用する閾値よりも小さな値になっているからである。
そこで、上記2つの問題を解決するため、ワーカー側で以下の設定を追加した。
max_parallel_workers_per_gather = 0 pg_strom.nvme_strom_threshold = 1GB
集計クエリを実行してみる(Take.2)
さて、再度集計クエリを実行してみる事にする。
まずは実行計画を確認。
postgres=# explain 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 (cost=0.00..0.00 rows=0 width=0) -> Custom Scan (Citus Adaptive) (cost=0.00..0.00 rows=0 width=0) Task Count: 32 Tasks Shown: One of 32 -> Task Node: host=localhost port=5433 dbname=postgres -> Aggregate (cost=1007098.32..1007098.33 rows=1 width=32) -> Custom Scan (GpuPreAgg) (cost=1007092.71..1007096.28 rows=204 width=8) Reduction: NoGroup Combined GpuJoin: enabled GPU Preference: GPU1 (Tesla P40) -> Custom Scan (GpuJoin) on lineorder_102040 lineorder (cost=9114.62..1019939.70 rows=1416481 width=10) Outer Scan: lineorder_102040 lineorder (cost=9426.71..1098945.56 rows=9919249 width=14) Outer Scan Filter: ((lo_discount >= '1'::numeric) AND (lo_discount <= '3'::numeric) AND (lo_quantity < '25'::numeric)) Depth 1: GpuHashJoin (hash-size: 66.06KB, nrows 9919249...1416481) HashKeys: lineorder.lo_orderdate JoinQuals: (lineorder.lo_orderdate = date1.d_datekey) GPU Preference: GPU1 (Tesla P40) NVMe-Strom: enabled -> Seq Scan on date1_102075 date1 (cost=0.00..78.95 rows=365 width=4) Filter: (d_year = 1993) (21 rows)
ワーカー側でのパラレルクエリが消えており、また、今度はNVMe-Strom: enabled
の表示が出た。
今度はEXPLAIN ANALYZE
で実際にクエリを実行してみる。
実行時間は24.67sで、合計 353GB のテーブルをスキャンした結果としては中々のものである。
postgres=# explain analyze 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 (cost=0.00..0.00 rows=0 width=0) (actual time=24666.217..24666.218 rows=1 loops=1) -> Custom Scan (Citus Adaptive) (cost=0.00..0.00 rows=0 width=0) (actual time=24666.174..24666.181 rows=32 loops=1) Task Count: 32 Tasks Shown: One of 32 -> Task Node: host=localhost port=5433 dbname=postgres -> Aggregate (cost=1007098.32..1007098.33 rows=1 width=32) (actual time=3569.988..3569.989 rows=1 loops=1) -> Custom Scan (GpuPreAgg) (cost=1007092.71..1007096.28 rows=204 width=8) (actual time=3569.953..3569.960 rows=1 loops=1) Reduction: NoGroup Combined GpuJoin: enabled GPU Preference: GPU1 (Tesla P40) -> Custom Scan (GpuJoin) on lineorder_102040 lineorder (cost=9114.62..1019939.70 rows=1416481 width=10) (never executed) Outer Scan: lineorder_102040 lineorder (cost=9426.71..1098945.56 rows=9919249 width=14) (actual time=261.049..628.315 rows=75214815 loops=1) Outer Scan Filter: ((lo_discount >= '1'::numeric) AND (lo_discount <= '3'::numeric) AND (lo_quantity < '25'::numeric)) Rows Removed by Outer Scan Filter: 65374058 Depth 1: GpuHashJoin (hash-size: 66.06KB actual-size: 23.29KB, plan nrows: 9919249...1416481, actual nrows: 9840757...1490844) HashKeys: lineorder.lo_orderdate JoinQuals: (lineorder.lo_orderdate = date1.d_datekey) GPU Preference: GPU1 (Tesla P40) NVMe-Strom: load=1437612 -> Seq Scan on date1_102075 date1 (cost=0.00..78.95 rows=365 width=4) (actual time=0.075..0.348 rows=365 loops=1) Filter: (d_year = 1993) Rows Removed by Filter: 2191 Planning Time: 0.485 ms Execution Time: 3759.495 ms Planning Time: 2.875 ms Execution Time: 24666.270 ms (27 rows)
以下は、集計クエリ実行中の iostat の出力。これはいつも通り、I/Oの帯域をほぼ限界まで使い切っている事が分かる。
Device: tps MB_read/s MB_wrtn/s MB_read MB_wrtn nvme0n1 21134.00 2641.50 0.01 5283 0 nvme1n1 21214.00 2641.22 0.00 5282 0 nvme2n1 21128.50 2640.96 0.00 5281 0 nvme6n1 20678.00 2584.75 0.00 5169 0 nvme5n1 20761.50 2584.89 0.01 5169 0 nvme4n1 20683.00 2585.31 0.00 5170 0 nvme7n1 20753.00 2585.35 0.00 5170 0 nvme3n1 21194.50 2639.98 0.00 5279 0 md0 84611.50 10556.17 0.01 21112 0 md1 82911.00 10344.69 0.01 20689 0
結論
PostgreSQL向けスケールアウト拡張であるCitusと、スケールアップ拡張であるPG-Stromを組み合わせて実行できることを確認した。
こういった形で、PostgreSQL向けに設計された周辺ソフトウェアと必要に応じて組み合わせる事ができるのが、他のGPU-DBにはないPG-Stromの強みであろう。
ただし、分散テーブルの分割度合いと、PG-Stromの得意とするデータサイズを見極めて、セグメント数や並列ワーカー数を設定する必要がある。
現実問題として、PG-Stromがシングルノードで処理し切れないレベルの大規模DBという事になると、それなりの体制を組んで事前検証を行うはずなので問題にはならないハズだが。CitusはCitusで、PG-StromはPG-Stromで、それぞれ単独で使ったケースを想定してのデフォルト値設定なので、これは致し方ないところ。