2015年12月1日

PostgreSQL 9.6のパラレルシーケンシャルスキャンを検証する

今年もこの季節になりました。PostgreSQL Advent Calendar 2015のDay1の記事です。

今回は、現在開発中のPostgreSQL 9.6に実装されたパラレルシーケンシャルスキャンについて、動作確認とフラッシュストレージでの簡単な性能検証をしてみようと思います。

なお、現在開発中の機能ですので、正式リリースされる時には仕様や動作などが変わっている可能性があることをご了承ください。

■「パラレルシーケンシャルスキャン」とは


「パラレルシーケンシャルスキャン」は、その名の通り、シーケンシャルスキャンを並列処理する機能です。

今まで、PostgreSQLでは1つのCPUを使ってシングルスレッド(というかプロセス)でしか処理をすることができませんでした。

しかし、直近のPostgreSQLのメジャーバージョンの拡張を見ていた方はご存じの通り、PostgreSQLではパラレル処理に向けての基本的な機能(開発者たちは infrastructure と呼んでいますが)が少しずつ実装されてきました。

そして、先月、PostgreSQLの待望のパラレル処理の一つ目の機能として、パラレルシーケンシャルスキャンがGitの最新版のコードにコミットされました。
このコードツリーは「9.6devel」と呼ばれており、約1年後にリリースされる予定のメジャーバージョン9.6から利用できるようになる予定です。

なお、コミットは以下のものになります。 コミットログにも記述がありますが、きちんとした機能として ship するためにはまだまだやらなければならないことが残っていますが、開発版のブランチに正式にコミットされることによって、より多くの開発者やユーザにテストしてもらえるだろう、とのことです。

というわけで、まずはこの機能を試してみることにします。

■パラレルシーケンシャルスキャンの使い方


パラレルシーケンシャルスキャンを試してみるためには、
  • 最大の並列度を指定する max_parallel_degree パラメータの設定
  • それなりの大きさのテーブル
が必要です。

2つ目の「それなりの大きさのテーブル」というのは、現在の実装では、パラレルシーケンシャルスキャンの実行計画を生成する際にテーブルサイズに応じて並列度を自動的に決定しているためです。

ソースコードを見ると分かるのですが、
+ int   parallel_threshold = 1000;

+ if (rel->consider_parallel && rel->pages > parallel_threshold &&
+  required_outer == NULL)
+ {

+  int parallel_degree = 1;
+
+  /*
+   * Limit the degree of parallelism logarithmically based on the size
+   * of the relation.  This probably needs to be a good deal more
+   * sophisticated, but we need something here for now.
+   */
+  while (rel->pages > parallel_threshold * 3 &&
+      parallel_degree < max_parallel_degree)
+  {
+   parallel_degree++;
+   parallel_threshold *= 3;
+   if (parallel_threshold >= PG_INT32_MAX / 3)
+    break;
+  }
となっていますので、最初に1000ブロックを閾値として設定して、それを超えていたらパラレルシーケンシャルスキャンを発動し、閾値を3倍にしながらブロック数に到達するまで並列度を増やしていく、という挙動になっているようです。

つまり、
  • 1,000ブロック超 → 並列度1
  • 3,000ブロック超 → 並列度2
  • 9,000ブロック超 → 並列度3
  • 27,000ブロック超 → 並列度4
という順番で自動的に並列度が上がっていくようです。

「3倍」という数字は現時点ではハードコードされていますが、これはそのうち(自動)チューニング可能な項目になるのではないかと思います。

■パラレルシーケンシャルスキャンを動かしてみる


それでは、実際にパラレルシーケンシャルスキャンを動かしてみます。今回はpgbenchツールのpgbanch_accountsテーブルを使用します。

まず、pgbench用のデータベースをスケールファクタ10で初期化します。
[snaga@localhost pgsql]$ /usr/pgsql-9.6/bin/pgbench -i -s 10 postgres
NOTICE:  table "pgbench_history" does not exist, skipping
NOTICE:  table "pgbench_tellers" does not exist, skipping
(...snip...)
900000 of 1000000 tuples (90%) done (elapsed 0.94 s, remaining 0.10 s)
1000000 of 1000000 tuples (100%) done (elapsed 1.05 s, remaining 0.00 s)
vacuum...
set primary keys...
done.
[snaga@localhost pgsql]$ /usr/pgsql-9.6/bin/psql postgres
psql (9.6devel)
Type "help" for help.

postgres=# select relpages from pg_class where relname = 'pgbench_accounts';
 relpages
----------
    16394
(1 row)

postgres=#
スケールファクタ10で初期化すると、pgbench_accountsテーブルは16,000ブロック強となり、パラレルシーケンシャルスキャンが動作する条件に合致します。

次に最大並列度を max_parallel_degree パラメータで指定します。今回は「10」とします。
postgres=# set max_parallel_degree to 10;
SET
postgres=#
最大並列度を指定したら、おもむろにテーブルのフルスキャンを実行してみます。
postgres=# select * from pgbench_accounts where filler = 'foo';
 aid | bid | abalance | filler
-----+-----+----------+--------
(0 rows)

postgres=#
これだけではよく分かりませんので、EXPLAIN ANALYZEコマンドで実行プランを見ながら実行してみます。
postgres=# explain analyze select * from pgbench_accounts where filler = 'foo';
                                                           QUERY PLAN

-----------------------------------------------------------------------------------------------
---------------------------------
 Gather  (cost=1000.00..9255.53 rows=1 width=97) (actual time=99.031..99.031 rows=0 loops=1)
   Number of Workers: 3
   ->  Parallel Seq Scan on pgbench_accounts  (cost=0.00..8255.43 rows=1 width=97) (actual time
=86.649..362.931 rows=0 loops=1)
         Filter: (filler = 'foo'::bpchar)
         Rows Removed by Filter: 1000000
 Planning time: 0.184 ms
 Execution time: 99.893 ms
(7 rows)

postgres=#
実行されたプランを見ると、「Parallel Seq Scan」というノードが出現しており、「Number of Workers: 3」という表示から並列度「3」で動作したということが分かります。

先ほどソースコードで見た通り、「ブロック数9,000超、27,000以下は、並列度3」で動くはずでしたから、期待通り動作していることが分かります。

■ハードディスクとフラッシュストレージでパフォーマンスを比較してみる


というわけで、パラレルシーケンシャルスキャンは動作しましたが、最後に簡単な性能評価として、それなりの大きさのシーケンシャルスキャンをハードディスクとフラッシュストレージで並列化したらどうなるかを試してみようと思います。

環境は以下の通りです。
  • サーバ本体: NEC Express5800/GT110b
  • CPU: Intel Xeon X3440 2.53GHz (1P/4C/8T), Hyper-Threading Enabled
  • メモリ: DDR3-1333 ECC 16GB
  • ハードディスク: MegaRAID SAS 9260-4 (6Gb/s SAS), 300GB SAS 6G 15,000rpm 16MB
  • フラッシュストレージ: Intel 910 SSD Series 400GB
  • OS: Red Hat Enterprise Linux 6.7 (x86_64)
データベースクラスタを丸ごと、SASディスク、またはフラッシュストレージに配置します。PostgreSQLの設定はデフォルトのままとし、先と同様にpgbench_accountsテーブルを使います。pgbenchのスケールファクタは5000とします。この場合、pgbench_accountsテーブルサイズは約63GBとなり、メモリには乗り切らないサイズとなります。実行するクエリは、先に実行したものと同じものを使います。

以下は、実行にかかった時間を比較したものです。数字は5回連続して実行した結果の平均値です。


縦軸は実行時間(ミリ秒)、横軸はパラレルシーケンシャルスキャンの並列度、青はSASディスク、赤はフラッシュストレージです。

見て分かる通り、今回の環境と条件では並列度を上げても、SASディスクの方は実行時間の短縮にはつながりませんでした。

一方で、フラッシュストレージの方は、1並列から4並列になった時に、2倍程度の性能向上(実行時間短縮)が実現できています。

以下は、SASディスクとフラッシュストレージで、それぞれ並列度8で実行した時のCPU使用率の推移です。(横軸は経過した秒数で、時間軸は揃えてあります。vmstatを使っています)


SASディスクの方は、おそらくディスクがボトルネックとなってしまっており、並列化してもパフォーマンスが向上しなかったものと考えられます。(vmstatの結果以外にもmpstatやiostatの結果も取ってあるのですが、長くなるため割愛します)

マルチコアでパラレルクエリのパフォーマンスを引き出すには、今まで以上にストレージの性能が重要になってきそうです。

■まとめ


今回は、最新の開発版のブランチにコミットされたパラレルシーケンシャルスキャンの機能を試してみました。

ストレージの性能にも依存しますが、確かにパラレル処理で実行時間が短縮できるようになってきたようです。

パラレル処理はデータ分析系のワークロードには非常に有用であり、個人的には非常に楽しみにしている機能の一つですので、今回確認し切れなかった部分も含めて、今後もいろいろと調べてみたいと思います。

ご紹介したコミットのコミットログにも書かれている通り、まだまだテストや改善が必要な機能です。9.6リリースに向けて、興味のある方にはぜひともトライしていただき、問題や改善点は開発者たちにフィードバックしていただければと思います。

では、また。

PostgreSQL Advent Calendar 2015、明日Day2の担当はPostgreSQLコミッターの@fujii_masao氏です。

お楽しみに。

0 件のコメント:

コメントを投稿