PCI-E 4.0がやってきた!

突然ですが、サーバを新調しました。

昨年、先行して NVIDIA A100 を調達していたのですが、手持ちのサーバ自体はSkylake-SpでPCI-E 3.0世代なので、まだGPU自体の持つポテンシャルを評価できず・・・といったところでした。


調達したサーバは、Supermicro社のAS-2014CS-TRというモデルで、構成自体は以下の通りとなります。

  • 筐体: AS-2014CS-TR
  • CPU: AMD EPYC 7402P (24C; 2.85GHz) x1
  • RAM: 128GB [16GB DDR4-3200 (ECC) x8]
  • GPU: NVIDIA A100 (PCI-E; 40GB) x1
  • SSD: Intel D7-P5510 (U.2; 3.84TB) x4
  • HDD: Toshiba 3.5 1.0TB (7.2krpm; 6.0Gb/s) x2
  • N/W: AIOM 2-port 10Gbase-T x1

今回、はじめてCPUとSSDPCI-E 4.0に対応した世代のものを調達して、これでCPU/GPU/SSDPCI-E 4.0に揃えてのベンチマークが可能となります。
CPUにMillan世代ではなくRoma世代を選んだのは、この世代ではI/O周りの変化がない(らしい)とされている事と、昨今の半導体不足の影響でタダでさえ長い納期がさらに延びる懸念が・・・というワケです。(そもそもこのサーバを発注したのは6月だw)

さて、早速、いつものように SSBM のデータベースをSF=999で構築し、13本のクエリの応答速度を計測してみます。
最もサイズの大きなlineorderテーブルのサイズは 875GB となるので、ストレージ中心のベンチマークには十分なサイズです。

f:id:kaigai:20211020092809p:plain

まず結果はこの通り。
分かりやすさのためスループット表記でグラフにしていますが、これは単純に(875GB ÷ クエリ応答時間)ですので、縦軸の『Query Execution Throughput [MB/s]』の値が大きいほど処理性能が高い事を示しています。

Filesystem I/Oの場合、元々PCI-E 3.0世代でもSSD性能を遥かに下回る処理性能しか出せていませんでしたが、H/Wが新しくなっても同じような性能値であるという事は、ストレージ読み出しではなく、バッファコピーの繰り返しなど別のところにボトルネックがある事を示唆しています。

一方、GPU-Direct SQLの場合、クエリによっては19GB/sに迫る値を出しています。
PCI-E3.0のSkylake-Sp世代では8.5GB/s~9.0GB/s程度で頭打ちになっていた事を考えると、中々のスコアといえるでしょうか。

続いて、iostatで測定したnvme0~nvme3の各デバイスのクエリ実行中の読み出しスループットを計測すると、これはこれで中々ひどい。
同じサイズのデータを読み出すにも、短時間でガツん!と読み出すか、時間をかけてチンタラ読み出すかというのが可視化されていると思います。
f:id:kaigai:20211020094245p:plain

ここまでは同じデータ形式の場合。

では、この875GB/60億行のlineorderテーブルを Pg2Arrow でApache Arrow形式に変換し、列データとして読み出す場合であればどうか?
行データと列データの処理性能をスループットで測るのは適切とは言えませんので、今度は(60億 ÷ クエリ応答時間)を『1秒あたり処理した行数』としてプロットしてみます。すると、I/Oの効率が良い分、さらに差が広がる結果となりました。

f:id:kaigai:20211020100430p:plain

細かいところで言うと、『秒速で10億行』を達成しているQ1_1と、Q1_2およびQ1_3のデータ読み出しサイズは等しいので、処理時間もそれと同じ程度になっていてほしいのですが、オプティマイザがいま一つ、おバカな実行計画を作ってしまったようです。この辺は要改善といったところでしょうか。

Q1_1のEXPLAIN ANALYZE結果

postgres=# explain analyze
select sum(lo_extendedprice*lo_discount) as revenue
from flineorder,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=9717634.19..9717634.20 rows=1 width=8) (actual time=4670.374..4781.510 rows=1 loops=1)
   ->  Gather  (cost=9717633.96..9717634.17 rows=2 width=8) (actual time=4387.503..4781.496 rows=3 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Parallel Custom Scan (GpuPreAgg)  (cost=9716633.96..9716633.97 rows=1 width=8) (actual time=4359.970..4359.976 rows=1 loops=3)
               Reduction: NoGroup
               Combined GpuJoin: enabled
               GPU Preference: GPU0 (NVIDIA A100-PCIE-40GB)
               ->  Parallel Custom Scan (GpuJoin) on flineorder  (cost=17101.66..9716355.33 rows=594409 width=8) (never executed)
                     Outer Scan: flineorder  (cost=17060.26..9711145.81 rows=4162493 width=12) (actual time=156.771..564.225 rows=5993990673 loops=1)
                     Outer Scan Filter: ((lo_discount >= 1) AND (lo_discount <= 3) AND (lo_quantity < 25))
                     Rows Removed by Outer Scan Filter: 5209361385
                     Depth 1: GpuHashJoin(plan nrows: 4162493...1426582, actual nrows: 784629288...119025391)
                              HashSize: 20.50KB (estimated: 63.28KB)
                              HashKeys: flineorder.lo_orderdate
                              JoinQuals: (flineorder.lo_orderdate = date1.d_datekey)
                     GPU Preference: GPU0 (NVIDIA A100-PCIE-40GB) with GPUDirect SQL
                     referenced: lo_orderdate, lo_quantity, lo_extendedprice, lo_discount
                     files0: /opt/pgdata13/flineorder.arrow (read: 89.37GB, size: 681.05GB)
                     ->  Seq Scan on date1  (cost=0.00..78.95 rows=365 width=4) (actual time=0.060..0.317 rows=365 loops=1)
                           Filter: (d_year = 1993)
                           Rows Removed by Filter: 2191
 Planning Time: 2.334 ms
 Execution Time: 4910.442 ms
(24 rows)

上記のようにGpuPreAggの直下にGpuJoinが入っており、加えて「Combined GpuJoin: enabled」と出力されています。
このとき、GpuJoinの処理結果は一度CPUに戻される事なくGPU上で集約されるため、最も効率のよいパターンとなります。
また、Apache Arrowファイルは全体で681GBあり、そのうち89GBを読み出した事が分かります。つまり、20GB/s程度の読み出し速度があれば、5秒程度で処理を終えるのは不思議な事ではないという事になります。


Q1_2のEXPLAIN ANALYZE結果

postgres=# explain analyze
select sum(lo_extendedprice*lo_discount) as revenue
from flineorder, date1
where lo_orderdate = d_datekey
  and d_yearmonthnum = 199401
  and lo_discount between 4 and 6
  and lo_quantity between 26 and 35;
                                                                                  QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=10085531.30..10085531.31 rows=1 width=8) (actual time=13043.719..13155.506 rows=1 loops=1)
   ->  Gather  (cost=10085531.08..10085531.29 rows=2 width=8) (actual time=12720.924..13155.496 rows=3 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Partial Aggregate  (cost=10084531.08..10084531.09 rows=1 width=8) (actual time=12709.551..12709.556 rows=1 loops=3)
               ->  Hash Join  (cost=17686.41..10084527.30 rows=757 width=8) (actual time=4078.065..12645.401 rows=1404451 loops=3)
                     Hash Cond: (flineorder.lo_orderdate = date1.d_datekey)
                     ->  Parallel Custom Scan (GpuScan) on flineorder  (cost=17607.07..10084283.79 rows=62438 width=12) (actual time=313.555..6825.444 rows=108984171 loops=3)
                           GPU Filter: ((lo_discount >= 4) AND (lo_discount <= 6) AND (lo_quantity >= 26) AND (lo_quantity <= 35))
                           Rows Removed by GPU Filter: 5667038161
                           GPU Preference: GPU0 (NVIDIA A100-PCIE-40GB) with GPUDirect SQL
                           referenced: lo_orderdate, lo_quantity, lo_extendedprice, lo_discount
                           files0: /opt/pgdata13/flineorder.arrow (read: 89.37GB, size: 681.05GB)
                     ->  Hash  (cost=78.95..78.95 rows=31 width=4) (actual time=0.521..0.522 rows=31 loops=3)
                           Buckets: 1024  Batches: 1  Memory Usage: 10kB
                           ->  Seq Scan on date1  (cost=0.00..78.95 rows=31 width=4) (actual time=0.171..0.513 rows=31 loops=3)
                                 Filter: (d_yearmonthnum = '199401'::numeric)
                                 Rows Removed by Filter: 2525
 Planning Time: 2.308 ms
 Execution Time: 13268.534 ms
(20 rows)

一方、Q1_2の結果を見てみると、GpuPreAggが選択されておらず、GpuScanによるフィルタを実行した後はCPUでHashJoinとAggregateを実行しています。GpuScanの返す行数の推定値が62438行足らずなので『そんな程度の処理にGPUを使うまでもない』というのは分かるのですが、実際には3億行ちょい(1億行×3ワーカー)を読み出している事になるので、読みが大ハズレだったというわけです。

ハードウェアの増強だけでは高速化を達成する事はできないというよい例ですが、この辺は、追って調べてみたいと思います。

なお、今回のこの測定結果を含む、大量データを処理するための PG-Strom の諸機能については、11月12日(金)のPostgreSQL Conference Japan 2021にて発表を行います。

このご時世、なかなか顔を突き合わせたディスカッションの機会が少ないのですが、こちらはオフラインでの開催予定となっております。
ぜひのご参加をお待ちしております。