2018年2月12日月曜日
2017年12月13日水曜日
pgAdmin4を日本語化する
この記事はPostgreSQL Advent Calendar 2017の13日目です。昨日はkmoriさんの「.psqlrcの話」でした。
本記事では、私もメンバーの一員として取り組んでいるpgAdmin4の日本語化について書きます。
・PostgreSQLを使い始めたので、まずはアクセスみたい
・監視やチューニングに関しても、手元でちょろっとできて欲しいんよ
データベースを利用する上で、例えばマルチDB対応しているA5や、補完機能などが抜群で圧倒的支持とウワサのオブジェクトブラウザ-など、便利なクライアントツールは複数知られていますが、PostgreSQL向けにとにかく気軽に使いたいならpgAdminでしょう。
特にGUIクライアントの需要が高いであろうWindows版であれば、PostgreSQLのインストーラーに同梱されていますので、使い始めが簡単ですし、簡単な監視、SQLチューニングまでいろいろできます。
ついに日本語対応しました。日本PostgreSQLユーザ会の有志で数ヶ月にわたる翻訳を行い、この度日本語対応することができました。
・翻訳ファイルの配置
まずは以下のファイルを手元にダウンロードしておいてください。
poファイルのダウンロード
こちらのリンク先で、「messages.po」「messages.mo」をそれぞれ入手しておいてください。(注:右クリックで「リンク先を保存」では正しく保存できませんでした。私の場合、pgadmin4のトップでソースコード全体をDOWNLOADしました)
Windowsの場合、デフォルトで「C:\Program Files\PostgreSQL\10\pgAdmin 4\web\pgadmin\translations」に各国語の翻訳ファイルが配置されていますので、「ja\LC_MESSAGES」フォルダを作成し、両ファイルを配置します。
・設定変更
「C:\Program Files\PostgreSQL\10\pgAdmin 4\web」にpgAdmin起動時に読み込まれる「config.py」がありますので、これをテキストエディタで開き、一か所編集します。
'ja','Japanese'の行を追加します。'Polish'の後のカンマも忘れずに。
---
# Languages we support in the UI
LANGUAGES = {
'en': 'English',
'zh': 'Chinese (Simplified)',
'de': 'German',
'pl': 'Polish',
'ja': 'Japanese'
}
---
・pgAdminの起動と設定変更
追加したフォルダのパスや設定が間違っていたりするとpgAdminが上手く起動しません。私は↑の設定ファイルでカンマを付け忘れて数時間悩みましたw
pgAdmin4を起動し、上部メニューの「File」-「Preferences」より設定変更を行います。
「Miscellaneous」-「User Language」で使用する言語を選択します。 ここでは config.pyファイルに記述した言語が候補になります。
変更はすぐには反映されませんので、「File」-「Reset Layout」します。
なにやら警告が。pgAdminから実行中の処理がある場合は気をつけましょう。
日本語になりました!
ところで、最初から日本語に対応した版が出るのはもう少し先になりそうです。そして今は日本語化しても英語交じりで「ん?!」って感じです。パッと見は確かに日本語なのですが、実用的なメニューではどこが変わったのよ?!という方に日本語版おススメポイントを1つ挙げておきます。
ちょっとマニアックな部分ですが、SQL実行する「Query Tool」では(そもそも「Query Tool」を日本語にしてよ、という意見が・・・(∩゚д゚)アーアーきこえなーい)
雷アイコンでユーザーが書いたSQLを実行します。が、ん?実行どこ?!って思ってました。昔のpgAdmin3は雷アイコンが黄色かったのでなんとなく目についたんですよね。
「どれだよ実行~」と思って探していくと、雷アイコンのマウスオーバーでちゃんと日本語ヘルプが出ますし、複数の実行オプションがあるのですが、こういうマニアックなメニューが日本語になっているのは嬉しい気がします。
(「EXPLAIN ANALYZE」はSQLコマンドなのでこのまま英語表記かな・・・)
以上です。
明日はやっさん(yassan168)さんがデータ分析でPostgreSQLな話をしてくれるそうです!
本記事では、私もメンバーの一員として取り組んでいるpgAdmin4の日本語化について書きます。
pgAdmin4とは?
クライアントPCからリモートのPostgreSQLサーバーに接続し、メンテナンス、状態監視、簡単な開発に利用できるGUIクライアントです。pgAdmin4、こんな人におススメ
・データベースをGUIで手軽に操作したい・PostgreSQLを使い始めたので、まずはアクセスみたい
・監視やチューニングに関しても、手元でちょろっとできて欲しいんよ
データベースを利用する上で、例えばマルチDB対応しているA5や、補完機能などが抜群で圧倒的支持とウワサのオブジェクトブラウザ-など、便利なクライアントツールは複数知られていますが、PostgreSQL向けにとにかく気軽に使いたいならpgAdminでしょう。
特にGUIクライアントの需要が高いであろうWindows版であれば、PostgreSQLのインストーラーに同梱されていますので、使い始めが簡単ですし、簡単な監視、SQLチューニングまでいろいろできます。
日本語対応しました!
知る人ぞ知る、pgAdminの開発メーリングリスト。ついに日本語対応しました。日本PostgreSQLユーザ会の有志で数ヶ月にわたる翻訳を行い、この度日本語対応することができました。
日本語化する方法
PostgreSQL10に同梱されているpgAmind4 v2以降、簡単に日本語化することができます。既に稼働しているpgAdmin4を日本語化する手順を説明します。・翻訳ファイルの配置
まずは以下のファイルを手元にダウンロードしておいてください。
poファイルのダウンロード
こちらのリンク先で、「messages.po」「messages.mo」をそれぞれ入手しておいてください。(注:右クリックで「リンク先を保存」では正しく保存できませんでした。私の場合、pgadmin4のトップでソースコード全体をDOWNLOADしました)
Windowsの場合、デフォルトで「C:\Program Files\PostgreSQL\10\pgAdmin 4\web\pgadmin\translations」に各国語の翻訳ファイルが配置されていますので、「ja\LC_MESSAGES」フォルダを作成し、両ファイルを配置します。
・設定変更
「C:\Program Files\PostgreSQL\10\pgAdmin 4\web」にpgAdmin起動時に読み込まれる「config.py」がありますので、これをテキストエディタで開き、一か所編集します。
'ja','Japanese'の行を追加します。'Polish'の後のカンマも忘れずに。
---
# Languages we support in the UI
LANGUAGES = {
'en': 'English',
'zh': 'Chinese (Simplified)',
'de': 'German',
'pl': 'Polish',
'ja': 'Japanese'
}
---
・pgAdminの起動と設定変更
追加したフォルダのパスや設定が間違っていたりするとpgAdminが上手く起動しません。私は↑の設定ファイルでカンマを付け忘れて数時間悩みましたw
pgAdmin4を起動し、上部メニューの「File」-「Preferences」より設定変更を行います。
「Miscellaneous」-「User Language」で使用する言語を選択します。 ここでは config.pyファイルに記述した言語が候補になります。
変更はすぐには反映されませんので、「File」-「Reset Layout」します。
なにやら警告が。pgAdminから実行中の処理がある場合は気をつけましょう。
日本語になりました!
ところで、最初から日本語に対応した版が出るのはもう少し先になりそうです。そして今は日本語化しても英語交じりで「ん?!」って感じです。パッと見は確かに日本語なのですが、実用的なメニューではどこが変わったのよ?!という方に日本語版おススメポイントを1つ挙げておきます。
ちょっとマニアックな部分ですが、SQL実行する「Query Tool」では(そもそも「Query Tool」を日本語にしてよ、という意見が・・・(∩゚д゚)アーアーきこえなーい)
雷アイコンでユーザーが書いたSQLを実行します。が、ん?実行どこ?!って思ってました。昔のpgAdmin3は雷アイコンが黄色かったのでなんとなく目についたんですよね。
「どれだよ実行~」と思って探していくと、雷アイコンのマウスオーバーでちゃんと日本語ヘルプが出ますし、複数の実行オプションがあるのですが、こういうマニアックなメニューが日本語になっているのは嬉しい気がします。
(「EXPLAIN ANALYZE」はSQLコマンドなのでこのまま英語表記かな・・・)
以上です。
明日はやっさん(yassan168)さんがデータ分析でPostgreSQLな話をしてくれるそうです!
2017年12月1日金曜日
コピペで試す PG10 の 3ステップレプリケーション!
本記事は PostgreSQL Advent Calendar 2017 の1日目です。
のっけから23時台の更新とひやひやさせてすみません。
■何を書くか
11/3に開催されたPostgreSQL Conforence 2017 JapanでPostgreSQLのレプリケーションに関するチュートリアルを受け持ったのですが、その準備中、PG10でレプリケーション作成の簡単さにびっくりしたのです。当然チュートリアル内でもその簡単さを熱く語ったわけですが、それを書こうと思います。
PostgreSQLレプリケーション(pgcon17j_t4) from Kosuke Kida
■PG10のレプリケーションはこうなった!
・パラメータのデフォルト値が変わって、簡単にレプリケーションできるようになった
・ロジカルレプリケーションができるようになった
その他にも細かい話題はあるにせよ、このうち、ロジレプの陰で感動するほど簡単になったレプリケーション作成を取り上げたいと思います。
■ステップ0:インストール
AWSでEC2インスタンス(CentOS)にPostgreSQLをインストールします。
■PG10のレプリケーションはこうなった!
・パラメータのデフォルト値が変わって、簡単にレプリケーションできるようになった
・ロジカルレプリケーションができるようになった
その他にも細かい話題はあるにせよ、このうち、ロジレプの陰で感動するほど簡単になったレプリケーション作成を取り上げたいと思います。
■ステップ0:インストール
AWSでEC2インスタンス(CentOS)にPostgreSQLをインストールします。
$ sudo
yum install wget
$ wget
https://download.postgresql.org/pub/repos/yum/testing/10/redhat/rhel-7-x86_64/pgdg-centos10-10-2.noarch.rpm
$ sudo
rpm -ivh pgdg-centos10-10-2.noarch.rpm
$ sudo
yum install postgresql10 postgresql10-server
postgresql10-contrib postgresql10-devel
$ sudo
vi /usr/lib/systemd/system/postgresql-10.service
---起動スクリプトのパスを変更---
# Location of
database directory
# Environment=PGDATA=/var/lib/pgsql/10/data/
Environment=PGDATA=/home/postgres/data/
--
$ su
- postgres
$
vi .bash_profile
---ユーザーの環境変数を変更---
### edit for PostgreSQL10
export PGDATA=/home/postgres/data
export PATH=/usr/pgsql-10/bin:.:$PATH
---
プライマリのデータベースを作成しておきます。
$ sudo
systemctl start postgresql-10.service
$ sudo
systemctl status postgresql-10.service
$ su
– postgres
$ createuser -d -r
-l -P demo
$ createdb -O demo
demodb
サンプルデータも入れておきましょう。
$ psql
-U demo demodb
demodb=>
create table sample (a int,b text);
demodb=>
insert into sample values (1,'test1');
■ステップ1:プライマリ側の設定
レプリケーション用のユーザーを作成し、ユーザーのアクセス制御をpg_hba.confに書きます。
$ createuser
--replication rep_user
$
vi $PGDATA/pg_hba.conf
----pg_hba.confを編集---
TYPE
|
DB
|
USER
|
CIDR-ADDRESS
|
METHOD
|
host
|
replication
|
rep_user
|
192.168.10.0/24
|
trust
|
host
|
all
|
rep_user
|
0.0.0.0/0
|
reject
|
このように、アクセス許可設定はもちろのこと、拒否設定もすると良いと思ってます。
レプリケーション用のパラメータを修正します。ここがポイント。デフォルト値が変更になり、(最小ではパラメータ変更なしでも)レプリケーションできます。
$
vi $PGDATA/postgresql.conf
パラメータ
|
設定
|
説明
|
listen_addresses
|
*
|
(通常はDB作成後にほぼ必須で実施)
|
wal_level
|
replica
|
レプリケーションに必要なWAL情報を生成
|
max_wal_senders
|
10
|
起動可能なwal senderプロセスの上限
|
max_replication_slots
|
10
|
作成可能なレプリケーションスロットの上限
|
synchronous_standby_names
|
任意
|
同期スタンバイの名前を指定
|
synchronous_commit
|
on
|
同期レベルを指定
|
hot_standby
|
on
|
自身がスタンバイの時に参照可能とする
|
hot_standby_feedback
|
on
|
自身の情報をプライマリに送信
|
自分がスタンバイになったときのために、サンプルのrecovery.confを作成しておきます。
$
vi $PGDATA/recovery.conf.node1
パラメータ
|
設定
|
説明
|
standby_mode
|
on
|
起動時にスタンバイモードになる
|
primary_conninfo
|
プライマリへの接続情報
|
|
primary_slot_name
|
slot2
|
プライマリのレプリケーションスロット名
|
recovery_target_timeline
|
latest
|
最新のマスターに追従する設定
|
■ステップ2:スタンバイの作成
スタンバイ側でpg_basebackupを使ってプライマリのバックアップを取得します。取得したデータはそのまま展開され、スタンバイのデータディレクトリになります。
$ pg_basebackup
-U rep_user -h <node1_ip>
-p 5432 -D /home/postgres/data
$ ls
-ltr $PGDATA
drwx------.
3 postgres postgres 60 Oct 28 15:44 pg_wal
drwx------.
6 postgres postgres 54 Oct 28 15:44 base
drwx------.
2 postgres postgres 4096 Oct 28 15:44 global
drwx------.
2 postgres postgres 32 Oct 28 15:44 log
-rw-------.
1 postgres postgres
22844 Oct 28 15:44 postgresql.conf
-rw-------.
1 postgres postgres 88 Oct 28 15:44 postgresql.auto.conf
-rw-rw-r--.
1 postgres postgres 169 Oct 28 15:44 recovery.conf.node1
-rw-------.
1 postgres postgres 4760 Oct 28 15:44 pg_hba.conf
:
スタンバイサーバーの設定もプライマリから取得したバックアップに含まれているのでほぼそのまま流用できます。変更すべき点は、recovery.confです。これはレプリケーション時にスタンバイがプライマリに接続しにいくための設定ですので、常にレプリケーションの相手ノードの情報が書いておくものだからです。
$
cp $PGDATA/recovery.conf.node1
$PGDATA/recovery.conf.node2
$ vi $PGDATA/recovery.conf.node2
primary_conninfo
|
プライマリへの接続情報
|
|
primary_slot_name
|
slot1
|
プライマリのレプリケーションスロット名
|
この2行をプライマリへの接続情報に変更します。
■ステップ3:スタンバイを起動
レプリケーションスロットを使う場合、スタンバイ起動時に、必ずレプリケーションスロットを作成、または状態を確認するようにします。(レプリケーションスロットで運用する場合。)
recovery.confに書いた primary_slot_name がまさにそのための設定で、ここで指定したスロットがマスター側に存在しないとレプリケーションできません。
プライマリでレプリケーションスロットを作成します。
$
su - postgres
$ psql
-h <node1_ip>
-U postgres postgres
postgres=#
select pg_create_physical_replication_slot('slot1');
スタンバイでデータベースを起動します。
$
sudo systemctl
start postgresql-10.service
$ su
- postgres
$ ps
x
11089 ? Ss
0:00 /usr/pgsql-10/bin/postmaster -D /home/postgres/data/
11093 ? Ss
0:00 postgres: startup process recovering 000000010000000000000005
11097 ? Ss
0:00 postgres: wal
receiver process streaming 0/5000140
:
ほら、レプリケーションできました!
今回は、AWSでたぶん一番簡単にPostgreSQLをインストールするところから、レプリケーションができるまでをコピペして使えるそのままのコマンドで紹介しました。
(書式がぐっちゃぐちゃで申し訳なさすぎます。見栄えはあとでコソッと変更します。)
明日は、Morihayaさんが担当してくださいます。
2017年10月24日火曜日
PostgreSQLでサンプルデータを生成する
PostgreSQLカンファレンスのチュートリアルトラックに向けて準備して(後輩のお世話、というか半強制的に「やります!」と言わせたセッションなので手を差し伸べて)るんだけど、インデックスチューニングの話をするのに美しくランダムなデータが欲しいわけじゃん。
というわけで、Let's PostgreSQLの記事を参考にいろんな型のサンプルデータを作るSQLを作成した。
というわけで、Let's PostgreSQLの記事を参考にいろんな型のサンプルデータを作るSQLを作成した。
SELECT * FROM (SELECT i ,(random()*1000)::int%80 num_sample ,(now()-(random()*1000)::int%365*interval'1day')::date date_sample ,(now()-(random()*1000000000)::int*interval'1microseconds')::timestamp ts_sample ,lpad(string_agg(str1,''),8) text_sample1 ,lpad(string_agg(str2,''),4) text_sample2 ,string_agg(str3, '') mbyte_sample FROM ( SELECT i, chr(97+(random() * 1000)::int % 26 ) as str1, chr(97+(random() * 10)::int % 3 ) as str2, chr(12354+(random() * 1000)::int % 85 ) as str3 FROM generate_series(1,20) length ,generate_series(1,1000) num(i) )t GROUP BY i) datagen ;
2016年12月20日火曜日
PostgreSQL 9.6 パラレルクエリに関する小ネタ
よく考えたら当たり前なんだけど、いざ気付くと「えーっ!」となった衝撃的な話。
要はDMLはダメと言っているように見える。じゃあマテビュー作るのもダメじゃね?という当然の話ではあるのですが。夜間に巨大なテーブルの加工や集計(そんな処理を数十分とか、数時間かけて)した結果をマテビューにして、なんとか日中はサクサク動くように頑張るわけじゃないですか。
で、パラレルクエリが入ったときに、「くそ重いロックをとるマテビューのリフレッシュが、2時間かかってたやつが1分になったら(キラキラ)」などと夢をいだいたものです。ところがそれが現状のパラレルクエリでは何も変わらないのです。がびーん。
そんなわけで、実際に試してみることにします。 わざわざ実行計画を見るのはめんどいマテビューでは内部で実行されたクエリの実行計画は覆い隠されてしまうので、単に所要時間で(psqlの\timingで)パラったか判断します。
今回使う表(今回、表の中身は重要ではない。件数と所要時間だけ注目)
というわけで、何かそれに代わる策がないか試してみます。
「パラレルクエリは、マテリアライズド・ビューのリフレッシュに使えない」疑惑
パラレルクエリの制限事項って結構あるんですね。そのうちの1つを9.6.1時点のマニュアルから抜粋するとクエリがデータを書き込むか、データベースの行をロックする場合。 クエリがデータ更新操作をトップレベルあるいはCTE内で含むと、そのクエリに対するパラレルプランは生成されません。 これは現在の実装の制限で、将来のリリースでは解除される可能性があります。
要はDMLはダメと言っているように見える。じゃあマテビュー作るのもダメじゃね?という当然の話ではあるのですが。夜間に巨大なテーブルの加工や集計(そんな処理を数十分とか、数時間かけて)した結果をマテビューにして、なんとか日中はサクサク動くように頑張るわけじゃないですか。
で、パラレルクエリが入ったときに、「くそ重いロックをとるマテビューのリフレッシュが、2時間かかってたやつが1分になったら(キラキラ)」などと夢をいだいたものです。ところがそれが現状のパラレルクエリでは何も変わらないのです。がびーん。
そんなわけで、実際に試してみることにします。 わざわざ実行計画を見るのは
今回使う表(今回、表の中身は重要ではない。件数と所要時間だけ注目)
demo=# \timing demo=# SELECT count(*) FROM tab1; count ---------- 28864000 (1 行) 時間: 669.988 ms --- パラレルの時は1秒以下、非パラレルだと4秒強かかることを確認済み。 /* 実行計画はこんな感じ */ demo=# EXPLAIN ANALYZE demo-# SELECT count(*) FROM tab1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------- Finalize Aggregate (cost=333435.96..333435.97 rows=1 width=8) (actual time=1015.604..1015.604 rows=1 loops=1) -> Gather (cost=333435.33..333435.94 rows=6 width=8) (actual time=1015.510..1015.594 rows=7 loops=1) Workers Planned: 6 Workers Launched: 6 -> Partial Aggregate (cost=332435.33..332435.34 rows=1 width=8) (actual time=1008.983..1008.983 rows=1 loops=7) -> Parallel Seq Scan on tab1 (cost=0.00..320408.67 rows=4810667 width=0) (actual time=0.043..622.811 rows=4123429 loops=7) Planning time: 0.126 ms Execution time: 1017.730 ms /* テーブルサイズはこれぐらい */ demo=# SELECT pg_size_pretty(pg_relation_size('tab1')); pg_size_pretty ---------------- 2127 MB
マテリアライズド・ビューを作成してリフレッシュ
demo=# CREATE MATERIALIZED VIEW mv1 AS demo-# SELECT count(*) FROM tab1; SELECT 1 時間: 4510.077 ms --- む、この時間は・・・。 demo=# REFRESH MATERIALIZED VIEW mv1; REFRESH MATERIALIZED VIEW 時間: 4451.020 ms --- む、この時間は・・・。うーん、パラられない。残念でならない!
というわけで、何かそれに代わる策がないか試してみます。
VIEWの場合
demo=# CREATE VIEW v2 AS SELECT count(*) FROM tab1; CREATE VIEW 時間: 2.151 ms demo=# SELECT * FROM v2; count ---------- 28864000 (1 行) 時間: 682.303 ms --- viewならOKだよねうん。まあ、これは大丈夫か。SELECTだけだし。でも結果を保存して再利用できない。
普通の表をつくってINSERT SEELCT
demo=# CREATE TABLE t3 (count numeric); CREATE TABLE demo=# INSERT INTO t3 demo-# SELECT count(*) FROM tab1; INSERT 0 1 時間: 4506.088 ms --- だめだめ。デスヨネー。
外部テーブルを介してMビュー。
外部テーブルで問合せた先は、リモートのサーバー側でよしなに実行計画を作ってくれるはずなので淡い期待を込めてやってみるが・・・demo=# CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw demo-# OPTIONS (dbname 'demo',host 'localhost',port '5432'); -- 自分自身を見に行くFDW CREATE SERVER 時間: 1.331 ms demo=# CREATE USER MAPPING FOR public SERVER loopback demo-# OPTIONS (user 'postgres',password 'postgres'); CREATE USER MAPPING 時間: 0.914 ms demo=# IMPORT FOREIGN SCHEMA public LIMIT TO (tab1) demo-# FROM SERVER loopback demo-# INTO loopback; IMPORT FOREIGN SCHEMA 時間: 7.039 ms demo=# EXPLAIN ANALYZE demo-# SELECT * FROM loopback.tab1 WHERE sales_id = 0; -- 一応ループバックしてFOREIGN SCANしてることを確認 QUERY PLAN ---------------------------------------------------------------------------------------------------------------- Foreign Scan on tab1 (cost=100.00..117.59 rows=3 width=112) (actual time=396.211..4527.848 rows=1152 loops=1) Planning time: 0.286 ms Execution time: 4528.583 ms (3 行) 時間: 4529.705 ms demo=# CREATE MATERIALIZED VIEW mv2 AS demo-# SELECT count(*) FROM loopback.tab1; SELECT 1 時間: 71687.426 ms ---パラレルかどうかよりaggrigate push downが効いてないせいかな。 demo=# REFRESH MATERIALIZED VIEW mv2; REFRESH MATERIALIZED VIEW 時間: 68418.958 msダメ。むしろダメ。Aggrigateのpush downはまだ実装されてないんだった。全件もってきて集計してるんだとしたら、仮にリモートでパラレルで動いてくれても、ネットワークが重すぎる。
\copy と COPY
demo=# \copy (SELECT count(*) FROM tab1) TO demo.copy; COPY 1 時間: 4518.860 ms --ダメ demo=# COPY (SELECT count(*) FROM tab1) TO '/home/enterprisedb/demo.copy'; COPY 1 時間: 4524.612 ms --ダメだめ。うーん、これぐらいはイケると思ったんだが。
\oでファイルに出力
demo=# \o demo2.copy demo=# SELECT count(*) FROM tab1; -- OK 時間: 699.871 msOK!うれしい!
psqlの引数としてクエリを投げ、結果をファイルに出力
[postgres@demo ~]$ time psql -U postgres demo -At -c 'SELECT count(*) FROM tab1' -o demo3.copy real 0m0.725s -- OK user 0m0.003s sys 0m0.009s [postgres@demo ~]$ psql -U postgres demo -At -c "COPY t3 FROM '/home/enterprisedb/demo3.copy'" -- これで格納もできる。(t3は上部検証で作成したやつ) -- もちろん、集計ずみの1行だけ格納するので一瞬で終わる。OK!うれしい!これだ感!これができればシェルとかプログラムで簡単にマテビューチックなことができる、はず。
2016年12月16日金曜日
PostgreSQL 9.6のパラレルが間違う件!
本投稿は、PostgreSQL Advent Calendar 2016 の 12/16 担当です。
昨日は「.NET Core で PostgreSQL を使ってみたら結構いけていたという話」を @creativewebjp さんが書いてくださいました。
さて、Ver9.6の登場までポスグレ界の話題を総ざらいしていたパラレルクエリですが、みなさん使ってますか?
大量データの領域で真価を発揮するものあって、なかなか現実的なサイズで試せていないのが現状で、採用に二の足を踏んでしまう、なんて話もちらほら聞いています。私は仕事でEnterpriseDB社が開発するPostgreSQLの商用版、EDB Postgresを担当していますが、そのβテストを兼ねてOSS版PostgreSQLのパラレル動作をみていたのですが、やはり試してみてわかることが結構ありました。
採用するかどうか迷っている方がいたら、とっかかりは非常に簡単なので、是非トライして、自身のアプリケーションでどの程度使えるかを評価してみるべきだと思います。バッチリとハマったときの効果は抜群ですよ。
とは言え、良いときの性能評価はそこそこ出回っていると思うので、今回は、案外うまくいかないパラレルクエリの話を紹介します。
前提知識として、PostgreSQLのパラレル・クエリは、表のサイズで並列度が決定されます。
適当な例で言うなら、10GBの表は8並列、500MBの表は4並列ぐらいで動きます。
んまあ、これはあくまでも単一の表をFROM句に書いてSELECTした場合に、これぐらいの並列度が良さそうと判断されたにすぎないです。
10GBを
これが、JOINの場合はどうやら変わってくるようです。
2つの表をパラレルで読んで、Gatherするとなると、 そのオーバーヘッドの方が大きくなってしまって、せっかくパラレルにしたのに所要時間はむしろ長くなってしまいます。
どちらもパラレルが効くぐらい大きい表なので、適切にパラレル処理して欲しいところではありますが、単純に表のサイズだけでは決められず、どちらか一方だけパラレルに動いてくれた方が良い結果になるのです。もちろんそういう目論見で、プランナはどちらか一方の表をパラレルで読もうとします。
問題は、PostgreSQLだと「どちらの表をパラレルで処理するか?」の調整が非常に難しいこと。約10GBのlineoder表と、約500MBのpart表をごく単純に結合してみると、このような実行計画になりました。
今回のケースでは、どうしても小さい方の表をパラレルにしたがる(先に読むからだろうか?)で、大きい方の表をシーケンシャルに読んでしまいました。
効果は未確認ですが、解決策としては、ALTER TABLEでそのテーブルを読むときに使うワーカーの数を指定できるので、それでpartテーブルの方は0に固定してしまうことでしょうか。
ただし、「こっちクエリではpart表をパラレルに読んでほしいんだよね~」という要望に応えるのが難しくなります。
そこでEDB Postgresです。EDB Postgresでは、オプティマイザ・ヒントを指定でき、2017年1月にリリース予定のEDB 9.6ではパラレル・ヒントに対応します。そこで実行計画をいじくるとこんな感じです。
狙った表をパラレルスキャンさせることで、PostgreSQLで生じていた問題をバッチリ解決することができました。
PostgreSQLでは、Windows関数つかって、それでないと1クエリで得ることがなかなか難しい複雑な集計を任せることができます。
とは言えその複雑さはなかなかなもので、さぞかしプランナは頭を悩ませることでしょう。
以前に本ブログで書いた「PostgreSQLらしい集計クエリ」で紹介しているテーブルで、データを何重にもいれまくって、2GBぐらいの表を用意しました。それを集計してみます。
お、パラレルになった。
しかし、これ、Disk Sortになっていますね。メモリソートになればもっと早くなるんじゃ。SET work_mem to '2GB' などとして再実行
あれ?
しかもこれ、上記のJOINのケースのようにパラレルさせないという選択ではなく、パラレルして欲しい方向なので、これ以上PostgreSQLでは調整のやりようがないです。困った。
そんなときはEDB Postgres
ヒントを使って・・・・
悩むこと数十分。サブクエリの側にヒントを入れて、無事解決しました。
結論から言うと、いろいろ頑張ったけど思ったより早くならないのでプランナが正しかった!説があります。以下のクエリを実行してみると、PostgreSQLで一向にパラレルになってくれません。
パラレルクエリで効果を得づらいケースを3パターン紹介しました。
これはたまたま私が検証する中で気付いたもので、他にもあるかもしれないし、賢い解決策があるのかもしれません。
本投稿では触れていませんが、同じ検証の中で、1クエリ実行するのに50分かかっていた処理が、パラレルクエリとテーブル・パーティショニングの組合せで8秒まで短縮していますから、ハマれば効果は抜群です。
しかしながら、間違うケースもこうやってポンポン出てきたということは、これから採用を考えている人は、自身のアプリケーションでどの程度性能がでるか、どんな書き方をすれば効果がでるか、(そしてEDBにすればどう変わるか。ぐへへ!)是非試してみて欲しいと思います。
策をいろいろ考えるのはパズルみたいで楽しかったです。
明日はぬこ@横浜さんが書いてくれます。楽しみです!
昨日は「.NET Core で PostgreSQL を使ってみたら結構いけていたという話」を @creativewebjp さんが書いてくださいました。
■パラレルクエリと戯れて
さて、Ver9.6の登場までポスグレ界の話題を総ざらいしていたパラレルクエリですが、みなさん使ってますか?
大量データの領域で真価を発揮するものあって、なかなか現実的なサイズで試せていないのが現状で、採用に二の足を踏んでしまう、なんて話もちらほら聞いています。私は仕事でEnterpriseDB社が開発するPostgreSQLの商用版、EDB Postgresを担当していますが、そのβテストを兼ねてOSS版PostgreSQLのパラレル動作をみていたのですが、やはり試してみてわかることが結構ありました。
採用するかどうか迷っている方がいたら、とっかかりは非常に簡単なので、是非トライして、自身のアプリケーションでどの程度使えるかを評価してみるべきだと思います。バッチリとハマったときの効果は抜群ですよ。
とは言え、良いときの性能評価はそこそこ出回っていると思うので、今回は、案外うまくいかないパラレルクエリの話を紹介します。
■いろいろ検証して得られた「間違うケース」
間違うケース1 でかい表同士をJOINするとき
前提知識として、PostgreSQLのパラレル・クエリは、表のサイズで並列度が決定されます。
適当な例で言うなら、10GBの表は8並列、500MBの表は4並列ぐらいで動きます。
んまあ、これはあくまでも単一の表をFROM句に書いてSELECTした場合に、これぐらいの並列度が良さそうと判断されたにすぎないです。
10GBを
1プロセスで読むコスト > 8プロセスで分担して、後でGather(集約)するコストと判断したので、そういう実行計画が選択されるということですね。
これが、JOINの場合はどうやら変わってくるようです。
2つの表をパラレルで読んで、Gatherするとなると、 そのオーバーヘッドの方が大きくなってしまって、せっかくパラレルにしたのに所要時間はむしろ長くなってしまいます。
どちらもパラレルが効くぐらい大きい表なので、適切にパラレル処理して欲しいところではありますが、単純に表のサイズだけでは決められず、どちらか一方だけパラレルに動いてくれた方が良い結果になるのです。もちろんそういう目論見で、プランナはどちらか一方の表をパラレルで読もうとします。
問題は、PostgreSQLだと「どちらの表をパラレルで処理するか?」の調整が非常に難しいこと。約10GBのlineoder表と、約500MBのpart表をごく単純に結合してみると、このような実行計画になりました。
/* 実行したクエリ(ごく単純なJOIN) */ EXPLAIN ANALYZE SELECT lo_partkey,sum(lo_quantity) FROM lineorder JOIN part ON lo_partkey = p_partkey WHERE p_name LIKE 'k%' GROUP BY lo_partkey;
/* いかにもパラレルが効きそうなlineorderが普通にSeqScanされてる */ -> Hash Join (cost=28661.34..3682122.56 rows=1111820 width=9) (actual time=67.061..57496.965 rows=1286337 loops=1) -> Seq Scan on lineorder (cost=0.00..3214594.56 rows=114066256 width=9) ★(actual time=0.010..27750.208 rows=114066226 loops=1) -> Hash (cost=28490.76..28490.76 rows=13646 width=4) (actual time=66.904..66.904 rows=15731 loops=1) Buckets: 16384 Batches: 1 Memory Usage: 682kB -> Gather (cost=1000.00..28490.76 rows=13646 width=4) (actual time=0.342..61.640 rows=15731 loops=1) Workers Planned: 3 Workers Launched: 3 -> Parallel Seq Scan on part (cost=0.00..26126.16 rows=4402 width=4) ★(actual time=0.029..59.519 rows=3933 loops=4) Filter: ((p_name)::text ~~ 'k%'::text) Rows Removed by Filter: 346067
今回のケースでは、どうしても小さい方の表をパラレルにしたがる(先に読むからだろうか?)で、大きい方の表をシーケンシャルに読んでしまいました。
効果は未確認ですが、解決策としては、ALTER TABLEでそのテーブルを読むときに使うワーカーの数を指定できるので、それでpartテーブルの方は0に固定してしまうことでしょうか。
ただし、「こっちクエリではpart表をパラレルに読んでほしいんだよね~」という要望に応えるのが難しくなります。
そこでEDB Postgresです。EDB Postgresでは、オプティマイザ・ヒントを指定でき、2017年1月にリリース予定のEDB 9.6ではパラレル・ヒントに対応します。そこで実行計画をいじくるとこんな感じです。
/* EDB版の実行したクエリ(パラレル・ヒントで各表の並列度を指定) */ EXPLAIN ANALYZE SELECT /*+ PARALLEL(part 0) PARALLEL(lineorder 4) */ lo_partkey,sum(lo_quantity) FROM lineorder JOIN part ON lo_partkey = p_partkey WHERE p_name LIKE 'k%' GROUP BY lo_partkey;
/* スキャンの並列度が変わり、Hash Joinまでパラレル実行している */ HashAggregate (cost=2624706.98..2636997.29 rows=983225 width=36) (actual time=17414.085..17421.061 rows=11272 loops=1) Group Key: lineorder.lo_partkey -> Gather (cost=39151.57..2619147.88 rows=1111820 width=9) (actual time=181.809..16260.314 rows=1286337 loops=1) Workers Planned: 4 Workers Launched: 4 -> Hash Join (cost=38151.57..2506965.88 rows=1111820 width=9) ☆(actual time=233.851..16886.113 rows=257267 loops=5) Hash Cond: (lineorder.lo_partkey = part.p_partkey) -> Parallel Seq Scan on lineorder (cost=0.00..2359097.64 rows=28516564 width=9) ★(actual time=0.034..8187.008 rows=22813245 loops=5) -> Hash (cost=37981.00..37981.00 rows=13646 width=4) (actual time=233.525..233.525 rows=15731 loops=5) Buckets: 16384 Batches: 1 Memory Usage: 682kB -> Seq Scan on part (cost=0.00..37981.00 rows=13646 width=4) ★(actual time=0.026..225.321 rows=15731 loops=5)
狙った表をパラレルスキャンさせることで、PostgreSQLで生じていた問題をバッチリ解決することができました。
間違うケース2 Window関数なのか、ソートなのか。原因不明(ヲイ!)
PostgreSQLでは、Windows関数つかって、それでないと1クエリで得ることがなかなか難しい複雑な集計を任せることができます。
とは言えその複雑さはなかなかなもので、さぞかしプランナは頭を悩ませることでしょう。
以前に本ブログで書いた「PostgreSQLらしい集計クエリ」で紹介しているテーブルで、データを何重にもいれまくって、2GBぐらいの表を用意しました。それを集計してみます。
/* window関数 */ EXPLAIN ANALYZE SELECT *, rank() OVER (PARTITION BY "ブランド" ORDER BY "売上" DESC) AS "順位" FROM ( SELECT sales_date::date as "売上日" ,shop as "ブランド" ,sum(price) as "売上" FROM tab1 GROUP BY "売上日","ブランド") AS "集計"; /* 結果イメージ。ブランド毎に売上の多かった日ランキング */ 売上日 | ブランド | 売上 | 順位 ------------+------------+-----------+------ 2016-05-11 | DIESEL | 632141504 | 1 2016-07-31 | DIESEL | 622096896 | 2 2016-11-29 | DIESEL | 620818688 | 3 : 2016-04-06 | Dior | 962435003 | 1 2016-03-23 | Dior | 889015872 | 2 :
/* この時の実行計画がこちら。パラレルになってる。約9秒。 */ WindowAgg (cost=3430456.39..3454473.19 rows=1200840 width=52) (actual time=9776.175..9777.199 rows=1095 loops=1) -> Sort (cost=3430456.39..3433458.49 rows=1200840 width=44) (actual time=9776.165..9776.300 rows=1095 loops=1) Sort Key: "集計"."ブランド", "集計"."売上" DESC Sort Method: quicksort Memory: 134kB -> Subquery Scan on "集計" (cost=3115228.89..3235312.89 rows=1200840 width=44) (actual time=9764.055..9773.283 rows=1095 loops=1) -> Finalize GroupAggregate (cost=3115228.89..3223304.49 rows=1200840 width=44) (actual time=9764.054..9773.062 rows=1095 loops=1) Group Key: ((tab1.sales_date)::date), tab1.shop -> Sort (cost=3115228.89..3133241.49 rows=7205040 width=44) (actual time=9764.012..9765.509 rows=7665 loops=1) Sort Key: ((tab1.sales_date)::date), tab1.shop Sort Method: quicksort Memory: 1270kB -> Gather (cost=1064650.75..1851272.12 rows=7205040 width=44) (actual time=6412.121..9757.054 rows=7665 loops=1) Workers Planned: 6 Workers Launched: 6 -> Partial GroupAggregate (cost=1063650.75..1129768.12 rows=1200840 width=44) (actual time=6518.572..9655.286 rows=1095 loops=7) Group Key: ((tab1.sales_date)::date), tab1.shop -> Sort (cost=1063650.75..1075676.95 rows=4810477 width=19) (actual time=6516.858..8298.955 rows=4123429 loops=7) Sort Key: ((tab1.sales_date)::date), tab1.shop ☆Sort Method: external merge Disk: 125680kB -> Parallel Seq Scan on tab1 (cost=0.00..332432.96 rows=4810477 width=19) ★(actual time=0.071..1195.131 rows=4123429loops=7)
お、パラレルになった。
しかし、これ、Disk Sortになっていますね。メモリソートになればもっと早くなるんじゃ。SET work_mem to '2GB' などとして再実行
あれ?
/* 実行計画がこちら。メモリソートになったらなぜか非パラレルに。約20秒かかっている。 */ WindowAgg (cost=1000838.70..1024855.50 rows=1200840 width=52) (actual time=21083.366..21084.609 rows=1095 loops=1) -> Sort (cost=1000838.70..1003840.80 rows=1200840 width=44) (actual time=21083.347..21083.521 rows=1095 loops=1) Sort Key: "集計"."ブランド", "集計"."売上" DESC Sort Method: quicksort Memory: 134kB -> Subquery Scan on "集計" (cost=849559.20..879580.20 rows=1200840 width=44) (actual time=21075.482..21080.224 rows=1095 loops=1) -> HashAggregate (cost=849559.20..867571.80 rows=1200840 width=44) (actual time=21075.480..21080.024 rows=1095 loops=1) Group Key: (tab1.sales_date)::date, tab1.shop -> Seq Scan on tab1 (cost=0.00..633087.75 rows=28862860 width=19) ★(actual time=0.064..7888.289 rows=28864000 loops=1) Planning time: 0.234 ms Execution time: 21097.712 msなんと、ソートが軽量に済むやいなや、パラレル動くことをやめちゃいました。なんてこった。ディスクソートしていたときのほうが圧倒的に早いので、このケースこそプランナが間違えてるとしか思えません。
しかもこれ、上記のJOINのケースのようにパラレルさせないという選択ではなく、パラレルして欲しい方向なので、これ以上PostgreSQLでは調整のやりようがないです。困った。
そんなときはEDB Postgres
ヒントを使って・・・・
EXPLAIN ANALYZE SELECT /*+ PARALLEL(tab1 6) */ *, rank() OVER (PARTITION BY "ブランド" ORDER BY "売上" DESC) AS "順位" FROM ( SELECT sales_date::date as "売上日" ,shop as "ブランド" ,sum(price) as "売上" FROM tab1 GROUP BY "売上日","ブランド") AS "集計";あれ。
悩むこと数十分。サブクエリの側にヒントを入れて、無事解決しました。
/* EDBでヒントをいれてみる。正解はこの位置 */ EXPLAIN ANALYZE SELECT *, rank() OVER (PARTITION BY "ブランド" ORDER BY "売上" DESC) AS "順位" FROM ( SELECT /*+ PARALLEL(tab1 6) */ sales_date::date as "売上日" ,shop as "ブランド" ,sum(price) as "売上" FROM tab1 GROUP BY "売上日","ブランド") AS "集計":
/* EDBでヒントを使うと、パラレルスキャンし、メモリソートもできた */ WindowAgg (cost=1327297.76..1351662.32 rows=1218228 width=56) (actual time=9667.291..10095.048 rows=441334 loops=1) -> Sort (cost=1327297.76..1330343.33 rows=1218228 width=48) (actual time=9667.270..9746.242 rows=441334 loops=1) Sort Key: "集計"."ブランド", "集計"."売上" DESC Sort Method: quicksort Memory: 46768kB -> Subquery Scan on "集計" (cost=1176747.00..1204157.13 rows=1218228 width=48) (actual time=8986.449..9216.731 rows=441334 loops=1) -> Finalize HashAggregate (cost=1176747.00..1191974.85 rows=1218228 width=48) (actual time=8986.447..9129.055 rows=441334 loops=1) Group Key: tab1.sales_date, tab1.shop -> Gather (cost=357488.67..1103653.32 rows=7309368 width=48) (actual time=3751.793..5775.296 rows=3081355 loops=1) Workers Planned: 6 Workers Launched: 6 -> Partial HashAggregate (cost=356488.67..371716.52 rows=1218228 width=48) (actual time=3736.607..4506.717 rows=440194 loops=7) Group Key: tab1.sales_date, tab1.shop -> Parallel Seq Scan on tab1 (cost=0.00..320408.67 rows=4810667 width=23) (actual time=0.041..870.314 rows=4123429 loops=7)ぶっちゃけactual timeを見ると元々9秒だったのが10秒と、むしろ若干悪いのだが・・・ディスクソートを排除したいという欲望を満たすべく試したらこうなった、と言う話。たぶんデータ量とかでこの辺はセンシティブに最適解は変わってしまうのだと思います。
間違・・・わないケース3 グループ集計(GROUPING SETS)
結論から言うと、いろいろ頑張ったけど思ったより早くならないのでプランナが正しかった!説があります。以下のクエリを実行してみると、PostgreSQLで一向にパラレルになってくれません。
/* グループ集計のクエリ */ EXPLAIN ANALYZE SELECT shop AS "ブランド" ,kind AS "種類" ,count(*) AS "数量" FROM tab1 GROUP BY GROUPING SETS (("ブランド"),("種類"),()); /* 結果のイメージ */ ブランド | 種類 | 数量 ------------+------------+---------- DIESEL | | 8448000 Paul Smith | | 15488000 dior | | 4928000 | | 28864000 | シャツ | 15488000 | ジャケット | 4224000 | パンツ | 9152000
/* PostgreSQLでの実行計画 パラレルにならない */ GroupAggregate (cost=4137591.06..8147200.27 rows=14 width=26) (actual time=27350.631..48641.465 rows=7 loops=1) Group Key: shop Group Key: () Sort Key: kind Group Key: kind -> Sort (cost=4137591.06..4209751.06 rows=28864000 width=18) (actual time=22549.349..26069.469 rows=28864000 loops=1) Sort Key: shop Sort Method: quicksort Memory: 2953433kB -> Seq Scan on tab1 (cost=0.00..560942.00 rows=28864000 width=18) (actual time=0.025..5461.728 rows=28864000 loops=1) Planning time: 0.160 ms Execution time: 49108.929 msEDBでヒントをいれてみます。
GroupAggregate (cost=6784457.73..10794066.94 rows=14 width=26) (actual time=45959.909..70152.520 rows=7 loops=1) Group Key: shop Group Key: () Sort Key: kind Group Key: kind -> Sort (cost=6784457.73..6856617.73 rows=28864000 width=18) (actual time=39595.620..44537.665 rows=28864000 loops=1) Sort Key: shop Sort Method: quicksort Memory: 2953433kB -> Gather (cost=1000.00..3207808.67 rows=28864000 width=18) (actual time=0.442..18630.270 rows=28864000 loops=1) Workers Planned: 6 Workers Launched: 6 -> Parallel Seq Scan on tab1 (cost=0.00..320408.67 rows=4810667 width=18) (actual time=0.052..1111.439 rows=4123429 loops=7) Planning time: 0.130 ms Execution time: 70680.047 msパラレル度が上がったが早くならないケースもある。ということで。
■総評
パラレルクエリで効果を得づらいケースを3パターン紹介しました。
これはたまたま私が検証する中で気付いたもので、他にもあるかもしれないし、賢い解決策があるのかもしれません。
本投稿では触れていませんが、同じ検証の中で、1クエリ実行するのに50分かかっていた処理が、パラレルクエリとテーブル・パーティショニングの組合せで8秒まで短縮していますから、ハマれば効果は抜群です。
しかしながら、間違うケースもこうやってポンポン出てきたということは、これから採用を考えている人は、自身のアプリケーションでどの程度性能がでるか、どんな書き方をすれば効果がでるか、(そしてEDBにすればどう変わるか。ぐへへ!)是非試してみて欲しいと思います。
策をいろいろ考えるのはパズルみたいで楽しかったです。
明日はぬこ@横浜さんが書いてくれます。楽しみです!
登録:
投稿 (Atom)
PostgreSQL11のJITコンパイリングを試す
llvm-postgres 開発中のPostgreSQL11でJIT(Just In Time=実行時)コンパイリングを行い、クエリ性能の高速化を期待する新機能が登場した。 本記事では 構築方法を確認したので紹介。JITコンパイ...
-
# pgAdmin4とは PostgreSQLの簡単な運用管理やクエリ作成に使える標準のGUIツールです。 以前に英語のみ提供されていたpgAdmin4を日本語化する方法を紹介しました。 [pgAdmin4を日本語化する](http://kkida-galaxy.blogs...
-
こんにちは、kkidaです。 沖縄合宿2日目です。 昨日に引き続き、他DBからPostgreSQLへの移行の話を聞いていただいた方へ、是非この機会に紹介しておきたいのが、OracleまたはMySQLからテーブル定義・データを抽出してPostgreSQL用に書き換え、ロードま...
-
この記事は PostgreSQL Advent Calendar 2017 の13日目です。昨日はkmoriさんの「 .psqlrcの話 」でした。 本記事では、私もメンバーの一員として取り組んでいるpgAdmin4の日本語化について書きます。 pgAdmin4とは? ...