はじめに
2018/10/18、PostgreSQL 11がリリースされました。
- PostgreSQL 11 Released
https://www.postgresql.org/about/news/1894/
パラレルクエリやパーティショニングの強化など、大規模データベース向けの様々な機能が強化されています。
今回はその中でも個人的に注目している&なにげに構築が難しい、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を入手します。
- PostgreSQL RPM Chart
https://yum.postgresql.org/rpmchart.php
例)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句や集計等のために使用されます。
(詳細はマニュアルの以下の部分をご参照ください)
- 32.1.1. JIT Accelerated Operations
https://www.postgresql.org/docs/11/static/jit-reason.html#JIT-ACCELERATED-OPERATIONS
そのため適当なWHERE句を付けて、SQL文を実行し、その実行計画を見てみます。
EXPLAIN ANALYZE SELECT * FROM jit_test WHERE a%2 = 1;
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の設定をしています。
=# 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 行)
=# 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コンパイルの詳細・ベンチマークテスト
-
PGCon 2017 (JIT-Compiling SQL Queries in PostgreSQL Using LLVM)
https://www.pgcon.org/2017/schedule/events/1092.en.html
PGConf 2017におけるJITコンパイルの講演です。
従来との違いや、JITコンパイルの目的など詳しい情報が載っています。
-
PostgreSQL 11 and Just In Time Compilation of Queries
https://www.citusdata.com/blog/2018/09/11/postgresql-11-just-in-time/
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導入
-
TPC
http://www.tpc.org/default.asp
TPCの公式ページです。TPC-Hをはじめとしたベンチマークテストの入手やその説明文書が手に入ります。
-
PostgreSQL 9.6 with Parallel Query vs. TPC-H
http://rhaas.blogspot.com/2016/04/postgresql-96-with-parallel-query-vs.html
PostgreSQL 9.6上でTPC-Hの実施検証が載っています。リンク先のGitにPostgreSQL向けのTPC-Hクエリや、PostgreSQLへの導入方法など載っています。
-
DBGENを使って、PostgreSQLにTPC-H測定用データを格納
http://someone620.hatenablog.com/entry/2014/10/27/025535
TPC-HをPostgreSQL上で実施する方法について、日本語で提供された情報です。
-
TPC-H Queries on PostgreSQL
http://myfpgablog.blogspot.com/2016/08/tpc-h-queries-on-postgresql.html
PostgreSQL上でTPC-Hを実施するための手順について記載されています。PostgreSQL向けに変換したSQLも載っています。