LoginSignup
8
7

More than 5 years have passed since last update.

PostgreSQL11の新機能JITコンパイルを試してみた

Last updated at Posted at 2018-10-29

はじめに

2018/10/18、PostgreSQL 11がリリースされました。

パラレルクエリやパーティショニングの強化など、大規模データベース向けの様々な機能が強化されています。
今回はその中でも個人的に注目している&なにげに構築が難しい、JITコンパイル機能について検証した内容を共有しようと思います。

本記事を執筆するに辺り、様々な情報を参考にし、そのリンク先を載せています。
しかし一部のリンク先は、PostgreSQL 11ベータ版における検証情報です。
正式にリリースされたPostgreSQL 11とは結果が異なる可能性があるためご注意ください。

JITコンパイルの導入

PostgreSQL 11を普通に導入しても、JITコンパイルが使えるとは限りません。
デフォルトでは未導入のライブラリ等をインストールする必要があります。

ソースコードからPostgreSQLを構築する場合

ソースコードからPostgreSQLを構築する場合、事前にLLVMを導入する必要があります。
(CentOS7等はLLVMがデフォルトで導入されていますが、バージョンが古いため対応が必要です)

またコンパイル時に、LLVMを使用することをオプションで明示的にする必要があります。

ソースコードからの構築+LLVMの導入は参考にしたサイトの構築・検証系に詳しく載っています。
ただし、下記の情報はPostgreSQL 11のベータ版における情報です。
現在は、必要なLLVMのバージョンが上がっている可能性があるため注意ください。

RPMからPostgreSQLを構築する場合

RPMからのPostgreSQL導入については以前、記事を書いています(ダイマ)。
詳細な導入手順についてはこの記事をご参照ください。
初心者からこだわりのある人向けRPMからPostgreSQL導入

オンライン環境の場合

上記記事で紹介しているRPMからのPostgreSQL導入により、JIT対応のビルドを利用できます。
ただしOS側のLLVMのバージョンが、適切できない可能性があるため、注意が必要です。

LLVMのバージョンアップ方法は参考にしたサイトの構築・検証系で紹介したリンク先に案内されています。

オフライン環境の場合

LLVMはEPEL(Linux用拡張パッケージ)から入手できます。
今回は5.0以上のバージョンを入手します。

オフライン環境の場合、直接必要なRPMを上記のサイトから入手し、導入先の環境に配置します。

例)CentOS 7の場合
https://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/l/
↑から「llvm5.0-libs-5.0.1-7.el7.x86_64.rpm」と「llvm5.0-5.0.1-7.el7.x86_64.rpm」を入手する。

yum install llvm5.0-libs-5.0.1-7.el7.x86_64.rpm
yum install llvm5.0-5.0.1-7.el7.x86_64.rpm

これによりLLVMの導入が完了しました。
次はPostgreSQLのLLVM用のRPMを導入します。
以下のサイトから、適切なOS行におけるバージョンの数字をクリックすることで、PostgreSQL関連の全てのRPMが確認できます。
そこからLLVM関連のRPMを入手します。

例)CentOS 7環境におけるPostgreSQL 11の場合
https://download.postgresql.org/pub/repos/yum/11/redhat/rhel-7.4-x86_64/
↑から「postgresql11-llvmjit-11.0-1PGDG.rhel7.x86_64.rpm」を入手する。

yum install postgresql11-llvmjit-11.0-1PGDG.rhel7.x86_64.rpm

これによりJITコンパイルが利用可能な環境になりました。

実際に使ってみよう

JITコンパイルが利用可能な状態になっても、すぐ使える訳ではありません。
関連するパラメータを調整する必要があります。

パラメータについてはマニュアルとPostgreSQL11がやってくる!(1) - パラメータの差異の記事を参考にさせていただきました。
JIT関連以外にも、PostgreSQL 11で追加/変更されたパラメータの説明があるため、PostgreSQL関係者は必見です。
また(1)とあるように、執筆者である、ぬこ@横浜様は11に関する様々な記事を投稿しています。
私自身、PostgreSQL 11検証で参考にしているのでオススメです(ダイマその2)。

JIT関連のパラメータ

JITコンパイルを使用するために必要なパラメータ群です。
特にjit_above_cost、jit_inline_above_cost、jit_optimize_above_costの3つは関連性が強いです。
これらのパラメータを変更し、JITコンパイルを適切に使用します。

パラメータ名 デフォルト値 説明
jit off JITコンパイルの使用可能にするかを決めます。
ベータ版ではデフォルトでonでしたが、正式版ではoffがデフォルトです。
jit_above_cost 100000 JITコンパイルが使用されるかを決めるコストの閾値です。
クエリの推定コストが本値を超えると使用されます。
値が-1の場合、無効化されます。
jit_inline_above_cost 500000 JITコンパイル使用後に、インライン展開するかを決める閾値です。
JITコンパイルのオーバヘッドは増加しますが、クエリ実行時間は減ります。
クエリの推定コストが本値を超えると使用されます。
値が-1の場合、無効化されます。
jit_optimize_above_cost 500000 インライン展開後、高価な最適化をするか決める閾値です。
JITコンパイルのオーバヘッドは増加しますが、クエリ実行時間は減ります。
クエリの推定コストが本値を超えると使用されます。
値が-1の場合、無効化されます。
jit_provider llvmjit 使用するJITプロパイダを決定します。
現在はLLVMのみ対応しているため変更の必要はありません。

この他にも開発者向けのパラメータがありますが、通常運用する際には特に気にする必要はないと思います。

パラメータを変更しJITコンパイルを使ってみる

JIT関連のパラメータを見れば分かる通り、JIT使用可否の閾値を下げればJITコンパイルをとりあえず使うことはできます。

まずJITコンパイルを使用するテスト用のテーブルを作成します。

テスト用テーブル作成
CREATE TABLE jit_test (a INTEGER, b TEXT);

INSERT INTO jit_test (a, b)
SELECT g, md5(g::text) FROM generate_series(1, 50000) AS g;

次にJITコンパイルを無理やり実行するために、JITコンパイルを有効化し、関連パラメータの閾値を下げます。

パラメータを調整
SET jit=on;
SET jit_above_cost=10;
SET jit_inline_above_cost=10;
SET jit_optimize_above_cost=10;

これによりJITコンパイルを使用する準備が出来ました。
PostgreSQL 11現在、JITコンパイルはWHERE句や集計等のために使用されます。
(詳細はマニュアルの以下の部分をご参照ください)

そのため適当なWHERE句を付けて、SQL文を実行し、その実行計画を見てみます。

JITコンパイル対象のSQL文
EXPLAIN ANALYZE SELECT * FROM jit_test WHERE a%2 = 1;
SQL文の実行結果
                                                  QUERY PLAN
---------------------------------------------------------------------------------------------------------------
 Seq Scan on jit_test  (cost=0.00..1167.00 rows=250 width=37) (actual time=71.657..77.408 rows=25000 loops=1)
   Filter: ((a % 2) = 1)
   Rows Removed by Filter: 25000
 Planning Time: 0.044 ms
 JIT:
   Functions: 2
   Options: Inlining true, Optimization true, Expressions true, Deforming true
   Timing: Generation 0.443 ms, Inlining 50.772 ms, Optimization 12.781 ms, Emission 7.972 ms, Total 71.969 ms
 Execution Time: 86.891 ms
(9 )

「JIT:」以降の行を見れば分かるように、JITコンパイルが使用されています。
ただし今回は閾値を下げて、無理やり使用したため、JITコンパイル未使用時と比べてパフォーマンスは低下しています。
これはJITコンパイルによる高速化よりも、オーバーヘッドによるコストが上回ってしまったためです。
では実際にどのような状況でJITコンパイルが有効であるかを次項で調査・検証してみます。

性能を検証してみた

JITコンパイルによるクエリ実行は、通常のクエリ実行と比べ必ずしも高速になるとは限りません。
JITコンパイルによるオーバーヘッドによるコストが高まってしまえば、通常のクエリより遅くなります。
JITコンパイルにより高速化する主なクエリは、I/Oは問題ないが、CPUがボトルネックとなるSQLです。
言い換えると、複雑な集計や計算処理が含まれるSQL処理に適しています。

なので今回はTPC-Hベンチマークテストを使用しました。

TPC-Hベンチマークテストの導入

TPC-HベンチマークテストはTPCのサイトから入手可能です。
ただしPostgreSQL向けには設定されていないため、調整が必要です。
本記事では導入手順については割愛させていただきます。
(いつか別記事として書くかも)
詳細を知りたい方は、参考にしたサイトのTPC-HのPostgreSQL導入のリンク先をご参照ください。

今回は10GBのテストデータを用いて検証を実施しています。

テスト用データ生成
./dbgen -s 10

また検証用の環境として以下を作成しております。
(TPC-Hの用意されたSQLはTPCDスキーマ上にオブジェクトがあることが前提のため)

ロールの作成
=# CREATE ROLE tpcd;
=# ALTER ROLE tpcd LOGIN SUPERUSER CREATEROLE CREATEDB CONNECTION LIMIT -1 PASSWORD 'password';
データベースの作成
=# CREATE DATABASE tpcd OWNER tpcd;
スキーマの作成
=# \c tpcd tpcd
=# CREATE SCHEMA tpcd;

なお、検証には以下の仮想環境を用いています。

OS メモリ CPU
CentOS 7.4 4GB 2コア

JITコンパイルの有無による性能比較

今回はTPC-Hのクエリ1を用いて、JITコンパイル使用有無における性能を比較します。
まずはJITコンパイルを使用しなかった場合のEXPLAN ANALYZEの結果です。
デフォルトでJITの使用はOFFですが、分かりやすいように敢えてJIT=OFFの設定をしています。

JITコンパイル未使用時のTPC-Hクエリ1の実行結果
=# SET jit=off;
=# explain analyze
select
  l_returnflag,
  l_linestatus,
  sum(l_quantity) as sum_qty,
  sum(l_extendedprice) as sum_base_price,
  sum(l_extendedprice * (1 - l_discount)) as sum_disc_price,
  sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) as sum_charge,
  avg(l_quantity) as avg_qty,
  avg(l_extendedprice) as avg_price,
  avg(l_discount) as avg_disc,
  count(*) as count_order
from
  lineitem
where
  l_shipdate <= date '1998-12-01' - interval ':1' day
group by
  l_returnflag,
  l_linestatus
order by
  l_returnflag,
  l_linestatus
LIMIT 1;

                                                                            QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=2437688.60..2437688.94 rows=1 width=236) (actual time=68884.525..68885.261 rows=1 loops=1)
   ->  Finalize GroupAggregate  (cost=2437688.60..2437690.67 rows=6 width=236) (actual time=68884.524..68884.525 rows=1 loops=1)
         Group Key: l_returnflag, l_linestatus
         ->  Gather Merge  (cost=2437688.60..2437690.00 rows=12 width=236) (actual time=68884.493..68885.228 rows=4 loops=1)
               Workers Planned: 2
               Workers Launched: 2
               ->  Sort  (cost=2436688.57..2436688.59 rows=6 width=236) (actual time=68879.669..68879.670 rows=3 loops=3)
                     Sort Key: l_returnflag, l_linestatus
                     Sort Method: quicksort  Memory: 27kB
                     Worker 0:  Sort Method: quicksort  Memory: 27kB
                     Worker 1:  Sort Method: quicksort  Memory: 27kB
                     ->  Partial HashAggregate  (cost=2436688.33..2436688.50 rows=6 width=236) (actual time=68879.633..68879.641 rows=4 loops=3)
                           Group Key: l_returnflag, l_linestatus
                           ->  Parallel Seq Scan on lineitem  (cost=0.00..1437019.25 rows=24991727 width=25) (actual time=2.905..13461.643 rows=19995278 loops=3)
                                 Filter: (l_shipdate <= '1998-11-30 00:00:00'::timestamp without time zone)
                                 Rows Removed by Filter: 73
 Planning Time: 0.201 ms
 Execution Time: 68885.341 ms
(18 )
JITコンパイル未使用時のTPC-Hクエリ1の実行結果
=# SET jit=on;
=# explain analyze
select
  l_returnflag,
  l_linestatus,
  sum(l_quantity) as sum_qty,
  sum(l_extendedprice) as sum_base_price,
  sum(l_extendedprice * (1 - l_discount)) as sum_disc_price,
  sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) as sum_charge,
  avg(l_quantity) as avg_qty,
  avg(l_extendedprice) as avg_price,
  avg(l_discount) as avg_disc,
  count(*) as count_order
from
  lineitem
where
  l_shipdate <= date '1998-12-01' - interval ':1' day
group by
  l_returnflag,
  l_linestatus
order by
  l_returnflag,
  l_linestatus
LIMIT 1;

                                                                            QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=2437688.60..2437688.94 rows=1 width=236) (actual time=55654.800..55658.101 rows=1 loops=1)
   ->  Finalize GroupAggregate  (cost=2437688.60..2437690.67 rows=6 width=236) (actual time=55278.626..55278.627 rows=1 loops=1)
         Group Key: l_returnflag, l_linestatus
         ->  Gather Merge  (cost=2437688.60..2437690.00 rows=12 width=236) (actual time=55278.556..55281.857 rows=4 loops=1)
               Workers Planned: 2
               Workers Launched: 2
               ->  Sort  (cost=2436688.57..2436688.59 rows=6 width=236) (actual time=55262.481..55262.482 rows=3 loops=3)
                     Sort Key: l_returnflag, l_linestatus
                     Sort Method: quicksort  Memory: 27kB
                     Worker 0:  Sort Method: quicksort  Memory: 27kB
                     Worker 1:  Sort Method: quicksort  Memory: 27kB
                     ->  Partial HashAggregate  (cost=2436688.33..2436688.50 rows=6 width=236) (actual time=55262.405..55262.413 rows=4 loops=3)
                           Group Key: l_returnflag, l_linestatus
                           ->  Parallel Seq Scan on lineitem  (cost=0.00..1437019.25 rows=24991727 width=25) (actual time=343.737..9535.259 rows=19995278 loops=3)
                                 Filter: (l_shipdate <= '1998-11-30 00:00:00'::timestamp without time zone)
                                 Rows Removed by Filter: 73
 Planning Time: 0.415 ms
 JIT:
   Functions: 35
   Options: Inlining true, Optimization true, Expressions true, Deforming true
   Timing: Generation 8.490 ms, Inlining 323.187 ms, Optimization 678.813 ms, Emission 392.651 ms, Total 1403.141 ms
 Execution Time: 55708.158 ms
(22 )

上記の通り、JITコンパイルにより性能が改善していることが分かります。
(環境が環境なので、目に見えて早くなってはいませんが…)
それぞれ5回ずつ実施した場合の比較は次の通りです。

実行時間(ms) 1回目 2回目 3回目 4回目 5回目 平均
JITコンパイルなし 62161.412 63427.404 65189.339 74441.315 62692.632 65582.42
JITコンパイルあり 56242.278 57135.679 58272.949 68390.395 57561.058 59520.47

このようにJITコンパイルを用いることで、複雑な計算を実施するSQLが高速化することが分かります。
よりメモリやCPUが大きい環境ならば、JITコンパイルによる影響はさらに増すと思います。

参考にしたサイト

JITコンパイルの詳細・ベンチマークテスト

構築・検証系

  • PostgreSQL 11 検証報告
    https://www.sraoss.co.jp/tech-blog/pgsql/pg11report/
    (→3.3.1. JIT コンパイルを使うためのビルド使うためのビルドうためのビルド)
    PostgreSQL 11の様々な検証結果が載っています。
    とりあえず11で何かできるのか、を知りたい方にオススメです。

  • KKIDA-GALAXY [PostgreSQL11のJITコンパイリングを試す]
    http://kkida-galaxy.blogspot.com/2018/04/postgresql11-with-jit-01.html
    JITコンパイル環境を構築手順として、実行コマンドとその結果など詳細に説明されています。
    環境構築が上手くいかない方は、こちらをじっくりと参照すると良いと思います。

  • How to compile PostgreSQL 11 with support for JIT compilation on RHEL/CentOS 7
    https://blog.dbi-services.com/how-to-compile-postgresql-11-with-support-for-jit-compilation-on-rhelcentos-7/
    海外のサイトですが、今回の記事を執筆するに辺り、大変参考になりました。
    導入手順からテスト方法まで丁寧に記載されています。
    重要なコマンドとその結果は載っているため、英語が苦手な方もGoogle翻訳を利用すれば簡単に理解可能です。

  • PostgreSQL 11に搭載されるJITコンパイラ機能を動かしてみる
    https://debug-life.net/entry/2928
    構築から検証まで丁寧に記載されており、なにより参考情報が豊富です。
    (参考にしたPostgreSQLのソース先も紹介されています)

TPC-HのPostgreSQL導入

8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7