Pgunconf 20121212-postgeres fdw

45
PostgreSQL Unconference (2015-12-12) postgres_fdw を 久々に使ってみた ぬこ@横浜 (@nuko_yokohama)

Transcript of Pgunconf 20121212-postgeres fdw

PostgreSQL Unconference(2015-12-12)

postgres_fdw を久々に使ってみた

ぬこ@横浜 (@nuko_yokohama)

自己紹介

ぬこ@横浜です面倒なのでググッてください

今日のお題

postgres_fdw といろんな機能の組み合わせ

と、その前にpostgres_fdw の簡単な説明を。

postgres_fdwhttp://www.postgresql.jp/document/9.4/html/postgres-fdw.html

主開発者の花田=サンとPostgreSQL コミュニティの

タツジンの見事なワザマエが光る一品。

実際スゴイ!

postgres_fdw外部の PostgreSQL サーバに格納されたデータを

自分の DB 内のテーブルであるかのようにアクセスするために使用する、外部データラッパ

類似モジュールとして dblink があるが、postgres_fdw のほうが、より自然なクエリが書ける。

参照だけでなく、更新 (insert/update/delete) も可。

PostgreSQL 9.3 からサポートされている。と、古事記 PostgreSQL 文書にも書かれている。

postgres_fdw を使って別データベース上の表と

結合ヤッター!

で、全文検索

ぬこは激怒した。かの邪智暴虐の

AWS RDS PostgreSQL で日本語全文検索を

せねばならぬと決意した。

AWS RDS PostgreSQL はDB 管理の手間が省けて便利な

サービスだけど、使える拡張機能が限定&独自の

拡張の追加ができない。

AWS RDS PostgreSQLpg_trgm は入っているpg_bigm は入ってないtextsearch_ja も入ってない

AWS RDS PostgreSQL 上で日本語全文検索が

できねーじゃねーか( pg_trgm は実質的に日本語は使えないので)

ということでpostgres_fdw の出番ですよ

リモートサーバ側

リモートサーバにpg_bigm をインストール

検索対象とするテキスト列にpg_bigm が提供する

全文検索インデックスを設定

ローカルサーバ側

postgres_fdw をインストールFOREIGN SERVER を定義FOREIGN TABLE を定義

FOREIGN TABLE に対して全文検索クエリを実行

リモートサーバの設定

test=# \d sangokushi テーブル "public.sangokushi" 列 | 型 | 修飾語 ------+---------+--------------------------------------------------------- id | integer | not null default nextval('sangokushi_id_seq'::regclass) data | text | インデックス: "meros_gin" gin (data gin_bigm_ops)

sangokushi テーブルの data 列に対してpg_bigm による N-gram インデックス

を設定している。

ローカルサーバの設定test=# \des sv2_test 外部サーバー一覧 名前 | 所有者 | 外部データラッパー ----------+--------+-------------------- sv2_test | nuko | postgres_fdw(1 行 )

test=# \d sangokushi 外部テーブル "public.sangokushi" 列 | 型 | 修飾語 | FDWオプション ------+---------+--------+--------------- id | integer | | data | text | | Server: sv2_test

外部サーバ (sv2_test) 上に外部表 sangokushi を定義する

検索してみる

test=# SELECT data FROM sangokushi WHERE data LIKE '% 兀突骨%' LIMIT 3; 「ここから東南《たつみ》の方、七百里に、一つの国がある。烏戈国《うかこく》といて、国王は兀突骨《ごつとつこつ》という者です。五穀を食《は》まず、火食せず、猛獣|蛇魚《だぎょ》を喰い、身には鱗《うろこ》が生えているとか聞きます。また、彼の手下には、藤甲軍《とうこうぐん》と呼ぶ兵が約三万はおりましょう」 「なるほど、それでは無敵だろう。ひとつ兀突骨《ごつとつこつ》に会ってこの急場をんでみよう」  議にも及ばず、兀突骨は「よろしい」と大きくうなずいた。即座に三万の部下は藤甲着こんで、洞市《どうし》に集まった。

検索はできたけど、インデックスはきちんと使っているのか?

EXPLAIN 結果

test=# EXPLAIN ANALYZE VERBOSE SELECT data FROM sangokushi WHERE data LIKE '%兀突骨%' LIMIT 3; Limit (cost=100.00..128.29 rows=1 width=32) (actual time=0.516..0.518 rows=3 loops=1) Output: data -> Foreign Scan on public.sangokushi (cost=100.00..128.29 rows=1 width=32) (actual time=0.515..0.515 rows=3 loops=1) Output: data Remote SQL: SELECT data FROM public.sangokushi WHERE ((data ~~ '%兀突骨%'::text)) Planning time: 0.091 ms Execution time: 0.821 ms

リモート側に条件は渡されている

リモート側の auto_explain 結果

LOG: duration: 0.040 ms plan:Query Text: DECLARE c1 CURSOR FORSELECT data FROM public.sangokushi WHERE ((data ~~ '% 兀突骨%'::text))Bitmap Heap Scan on sangokushi (cost=14.03..21.81 rows=4 width=148) Recheck Cond: (data ~~ '%兀突骨%'::text) -> Bitmap Index Scan on meros_gin (cost=0.00..14.03 rows=4 width=0) Index Cond: (data ~~ '%兀突骨%'::text)

リモート側で pg_bigm の全文検索インデックスを使った

クエリが実行されている。

textsearch_ja も同様に

リモート側で textsearch_ja を入れてgin インデックス設定

ローカル側で FDW を設定。

EXPLAIN 結果test=# EXPLAIN ANALYZE VERBOSE SELECT * FROM sangokushi_tsj WHERE to_tsvector('japanese', data) @@ to_tsquery('japanese', '孔明') LIMIT 3; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------ Limit (cost=100.00..257.91 rows=3 width=36) (actual time=0.747..0.748 rows=3 loops=1) Output: id, data -> Foreign Scan on public.sangokushi_tsj (cost=100.00..468.45 rows=7 width=36) (actual time=0.746..0.746 rows=3 loops=1) Output: id, data Remote SQL: SELECT id, data FROM public.sangokushi_tsj WHERE ((to_tsvector('japanese'::regconfig, data) @@ '''孔明'''::tsquery)) Planning time: 0.099 ms Execution time: 1.152 ms(7 行 )

リモート側に条件は渡されている

リモート側の auto_explain 結果

LOG: duration: 0.343 ms plan:Query Text: DECLARE c1 CURSOR FORSELECT id, data FROM public.sangokushi_tsj WHERE

((to_tsvector('japanese'::regconfig, data) @@ '''孔明'''::tsquery))Bitmap Heap Scan on sangokushi_tsj (cost=7.62..352.41 rows=209 width=151) Recheck Cond: (to_tsvector('japanese'::regconfig, data) @@ '''孔

明'''::tsquery) -> Bitmap Index Scan on idx (cost=0.00..7.57 rows=209 width=0) Index Cond: (to_tsvector('japanese'::regconfig, data) @@ '''孔

明'''::tsquery)

リモート側で textsearch_ja のgin インデックスを使ったクエリが実行されている。

つまり

ぬこ「めうちゃんは日本語全文検索がしたい」めう「日本語全文検索したいめう」

ぬこ「でも RDS には pg_bigm がない」めう「 RDS だと日本語全文検索できないめう・・・」

ぬこ「だから EC2 に pg_bigm を入れる」めう「いれるめう」

ぬこ「 RDS 側から FDW 経由で EC2 に接続する」めう「接続するめう」

ぬこ「すると RDS から日本語全文検索できる」

めう「やっためう!すごいめう!」

RDS 上で pg_bigm と textsearch_jaが使えるのでは!

まあ RDS PostgreSQL がpg_bigm や textserach_ja を

サポートしてくれればこんなことしなくてもいいんですけどねw

で、 JSONB

例えば、外部テーブルのソースとなるサーバに以下のような

JSONB カラムを持つtest テーブルがあるとする。

bar=# \d test Table "public.test" Column | Type | Modifiers --------+-------+----------- data | jsonb | Indexes: "test_id_idx" btree ((data ->> 'id'::text)) "test_idx" gin (data)

ローカルな問い合わせならbtree 式インデックスも

gin インデックスも使える。bar=# EXPLAIN SELECT * FROM test WHERE data->>'id' = '10000'; QUERY PLAN ------------------------------------------------------------------------- Index Scan using test_id_idx on test (cost=0.42..8.44 rows=1 width=70) Index Cond: ((data ->> 'id'::text) = '10000'::text)(2 rows)

bar=# EXPLAIN SELECT * FROM test WHERE data @> '{"id":10000}'; QUERY PLAN -------------------------------------------------------------------------- Bitmap Heap Scan on test (cost=28.77..336.47 rows=100 width=70) Recheck Cond: (data @> '{"id": 10000}'::jsonb) -> Bitmap Index Scan on test_idx (cost=0.00..28.75 rows=100 width=0) Index Cond: (data @> '{"id": 10000}'::jsonb)(4 rows)

あからさまにインデックス検索なのだ!

しかし外部テーブル経由だと

@> 演算子 { キー : 値 } の場合WHERE 句を pushdown して、

リモート側でも GIN インデックスを使用foo=# EXPLAIN ANALYZE SELECT * FROM test WHERE data @> '{"id":10000}'; QUERY PLAN -------------------------------------------------------------- Foreign Scan on test (cost=100.00..128.29 rows=1 width=32) (actual time=0.579..0.579 rows=1 loops=1) Planning time: 0.052 ms Execution time: 0.867 ms(3 rows)

Time: 1.204 ms

外部テーブルから返却された時点で1件になっていることに注目重点!

実際速い!

->>' キー ' 演算子 値 の場合WHERE 句は pushdown されず、

リモート側でフルスキャン全件取得!アイエエエ!フルスキャン!

フルスキャンナンデ!foo=# EXPLAIN ANALYZE SELECT * FROM test WHERE data->>'id' = '10000'; QUERY PLAN ---------------------------------------------------------------------- Foreign Scan on test (cost=100.00..159.93 rows=7 width=32) (actual time=31.028..273.480 rows=1 loops=1) Filter: ((data ->> 'id'::text) = '10000'::text) Rows Removed by Filter: 99999 Planning time: 0.046 ms Execution time: 273.581 ms(5 rows)

そして外部テーブルからは全件 (10 万件 ) 返却されている!あからさまにフルスキャンなのだ!ヤンナルネ・・・

実際遅い!

postgres_fdw 外部テーブルに渡すWHERE 句の書き方によって

Pushdwon されたりされなかったりする。

おかしいと思いませんか?あなた

古事記 PostgreSQL 文書にはこう書かれている

F.31.4. リモート問合せの最適化

外部サーバからのデータ転送量を削減するため、postgres_fdw はリモート問合せを最適化しようと試みます。 これは問い合わせの WHERE 句をリモートサーバに送出する事、およびクエリで必要とされていないカラムを取得しない事により行われます。 問い合わせの誤作動のリスクを下げるため、ビルトインのデータ型、演算子、関数だけを用いたものでない限り、リモートサーバに WHERE 句は送出されません。また、 WHERE 句で使われる演算子と関数は IMMUTABLE でなければなりません。

カラム ->>' キー名 ' の結果に対する比較演算だとダメっぽい?

要するに、特定の条件式がPushdown されない問題は

「バグではない。いいね?」「アッハイ」

JSON 型 /XML 型のように関数インデックスを必要とする型とpostgres_fdw は相性は良くない?

で、この問題(制約)って9.6 では解消されてるのかな?

(本当は 9.6-dev でも試したかったけど時間切れ・・・)

おまけpostgres_fdw の細かい制約

その 1COPY

グワーッ!test=# COPY sangokushi FROM '/tmp/sangoku.txt' CSV;ERROR: cannot copy to foreign table "sangokushi"test=#

外部テーブルに直接 COPY はできない。

リモートサーバ

外部表

ローカルサーバ

COPY データ COPY

COPY の問題は以下のジツを使って回避可能

1. リモートテーブルに直接 COPY2. INSERT 文を使用

3. file_fdw+ バルク INSERT(3 . の方法の詳細は次ページで説明)

備えよう。

file_fdw+ バルク INSERTこんなアトモスフィアなシェル作成!

#!/bin/shDBNAME=$1FOREIGN_TABLE_ARG=$2LOAD_FILE_NAME=$3LOAD_TABLE_NAME=$4

CREATE_SERVER_SQL="CREATE SERVER __cp_server FOREIGN DATA WRAPPER file_fdw"psql ${DBNAME} -c "${CREATE_SERVER_SQL}"

CREATE_FOREIGN_TABLE_SQL="CREATE FOREIGN TABLE __cp_table ( ${FOREIGN_TABLE_ARG} ) SERVER __cp_server OPTIONS (filename '${LOAD_FILE_NAME}')"psql ${DBNAME} -c "${CREATE_FOREIGN_TABLE_SQL}"

INSERT_SQL="INSERT INTO ${LOAD_TABLE_NAME} (SELECT data FROM __cp_table )"psql ${DBNAME} -c "${INSERT_SQL}"

DROP_FOREIGN_TABLE_SQL="DROP FOREIGN TABLE __cp_table"psql ${DBNAME} -c "${DROP_FOREIGN_TABLE_SQL}"

DROP_SERVER_SQL="DROP SERVER __cp_server"psql ${DBNAME} -c "${DROP_SERVER_SQL}"

contrib/file_fdw の外部サーバ / 外部表を定義外部表を SELECT で全件検索した結果を

INSERT 文に流し込む。最後に外部表と外部サーバを爆発四散!

file_fdw+ バルク INSERT前ページのシェルを実行!

$ ./fdw_cp foo "data jsonb" "/tmp/json.txt" "test"CREATE SERVERCREATE FOREIGN TABLEINSERT 0 100000DROP FOREIGN TABLEDROP SERVER$

ゴウランガ!COPY で使うファイルをそのまま使って

外部表へロード可能!(COPY より遅いのは仕方がない。いいね?)

このシェルは postgres_fdw 専用というわけでなく挿入操作が可能な FDW なら使用可能だと思う

その 2TRUNCATE

アバーッ!test=# TRUNCATE sangokushi;ERROR: "sangokushi" is not a tabletest=#

外部テーブルに対するTRUNCATE はどの FDW でも不可っぽい

なのでリモートの実表に対してTRUNCATE が必要!

あるいは DELETE とかしなさい

ということでpostgres_fdw は便利ですが

使用時には注意重点な。という話でした。