9.4新機能?覗き見(ニッチなFDW編)
このエントリはPostgreSQL Advent Calendar 2013の12/12分です。
相変わらずのニッチなFDWネタです。みんなに便利な機能は誰かまじめな人が書いてくれるはず。
前口上
9.2でPostgreSQL対応、9.3で書き込み可能になった外部データラッパ(FDW)ですが、現在開発中の9.4でも機能追加が試みられています。というわけで、まだコミットされていない機能ですが紹介してみたいと思います。「いいね!」と思ったら、開発コミュニティに「欲しがっている人いるで〜」とプッシュいたしますので是非コメントをください。
Custom Scan Provider
「Custom Scan Provider」という名前からはどんな機能か分かりづらいですが、ひとことでいうと「独自のスキャン処理を定義」できるようになります。スキャン処理と言えば全ページなめる「Seq Scan」やインデックスを使う「Index Scan」がありますが、それと同レベルの独自処理をプランナーに追加の選択肢として提供することができるというものです。プランナーは標準的なスキャンと「Custom Scan」をコスト値で比較し最も速そうなものを選択します。
なお、この機能はSE-PostgreSQLやWritable-FDWを開発された海外さんが提案されています。最新版パッチでは、Proof-of-Concept(PoC)として、CTIDスキャンの高速版がついてきます。これは、
SELECT * FROM table WHERE ctid > '(100,10)'::tid; -- 100ページ目の10個目より後のデータを検索
といったCTIDシステム列を条件にした検索で、不要なブロックを読み飛ばすというものです。上記の例では、通常のPostgreSQLでは先頭ページから地道に読んでいくところを、99ページスキップしていきなり100ページ目から読み始めます。これはPoCなので実用性がどこまであるかは?なところもありますが、リファレンス実装としてはなかなかのものだと思います。
え?これが何でFDWに絡むの?とお思いでしょう。私も思いました。外部データのスキャンだけならもうFDWでできていますからね。このパッチのすごいところは、結合処理もカスタマイズできるところです。「Custom Scan」ならぬ「Custom Join」ですね。この機能はもちろん普通のテーブルにも使えるんですが、外部テーブルで使うと、なんと外部テーブル同士の結合をリモートサーバ側で実行できるようになります。
(以前私も外部テーブル同士の結合サポートを提案したんですが、見事にrejectされました)
このパッチに含まれる改造版postgres_fdwで定義した、同じサーバ上の外部テーブル同士を結合すると…
9.3のpostgres_fdw
postgres=# EXPLAIN VERBOSE SELECT count(*) FROM pgbench1_branches b JOIN pgbench1_accounts a ON a.bid = b.bid WHERE aid < 100; QUERY PLAN ---------------------------------------------------------------------------------------------------- Aggregate (cost=6454.00..6454.01 rows=1 width=0) Output: count(*) -> Hash Join (cost=201.21..6453.81 rows=77 width=0) Hash Cond: (a.bid = b.bid) -> Foreign Scan on public.pgbench1_accounts a (cost=100.00..6351.54 rows=77 width=4) Output: a.aid, a.bid, a.abalance, a.filler Remote SQL: SELECT bid FROM public.pgbench_accounts WHERE ((aid < 100)) -> Hash (cost=101.15..101.15 rows=5 width=4) Output: b.bid -> Foreign Scan on public.pgbench1_branches b (cost=100.00..101.15 rows=5 width=4) Output: b.bid Remote SQL: SELECT bid FROM public.pgbench_branches (12 rows)
Custom Scan Provider対応のpostgres_fdw
postgres=# EXPLAIN VERBOSE SELECT count(*) FROM pgbench1_branches b JOIN pgbench1_accounts a ON a.bid = b.bid WHERE aid < 100; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------- Aggregate (cost=101.60..101.61 rows=1 width=0) Output: count(*) -> Custom Scan (postgres-fdw) (cost=100.00..101.43 rows=71 width=0) Remote SQL: SELECT NULL FROM (public.pgbench_branches r1 JOIN public.pgbench_accounts r2 ON ((r1.bid = r2.bid))) WHERE ((r2.aid < 100)) (4 rows)
といったように、二つのテーブルを結合するSQLを生成し、その実行結果をスキャン結果として使えるのです!もちろん、別のサーバ上にある外部テーブルやローカルのテーブルとはローカルで結合してくれます。この「Join push-down」という手法によってサーバ間のデータ転送量やローカルでの結合処理に必要なCPU演算量などが減るので、処理の大幅な高速化が期待できます。
仕組みに興味がある方は、開発者Wikiに海外さんの整理した解説(英語)があるのでぜひ。
外部テーブルの継承サポート
まだ正式なパッチは出ていませんが、外部テーブルを子テーブルとして使えるようにする機能も提案されています。これが実現すると、パーティションテーブルとして外部テーブルが使えるようになるので、シャーディングが実現でき処理のオフロードも期待できます。ちなみに、これを使うとこんなSQLが実行できるようになります。
postgres=# \d List of relations Schema | Name | Type | Owner --------+----------------------+---------------+---------- public | pgbench1_accounts | table | postgres public | pgbench1_accounts_c1 | foreign table | postgres public | pgbench1_accounts_c2 | foreign table | postgres public | pgbench1_accounts_c3 | foreign table | postgres public | pgbench1_accounts_c4 | foreign table | postgres public | pgbench1_accounts_c5 | foreign table | postgres (6 rows) postgres=# \d+ pgbench1_accounts Table "public.pgbench1_accounts" Column | Type | Modifiers | Storage | Stats target | Description ----------+---------------+-----------+----------+--------------+------------- aid | integer | | plain | | bid | integer | | plain | | abalance | integer | | plain | | filler | character(84) | | extended | | Child tables: pgbench1_accounts_c1, pgbench1_accounts_c2, pgbench1_accounts_c3, pgbench1_accounts_c4, pgbench1_accounts_c5 Has OIDs: no postgres=# \d pgbench1_accounts_c1 Foreign table "public.pgbench1_accounts_c1" Column | Type | Modifiers | FDW Options ----------+---------------+-----------+------------- aid | integer | | bid | integer | | abalance | integer | | filler | character(84) | | Check constraints: "accounts_c1_bid_check" CHECK (bid = 1) Server: pgbench1 FDW Options: (table_name 'pgbench_accounts_c1') Inherits: pgbench1_accounts
このように外部テーブルを子テーブルとしてbid別に定義して、パーティションキー(ここではbid列)にチェック制約を定義しておくと…
postgres=# postgres=# EXPLAIN (COSTS false) SELECT * FROM pgbench1_accounts; QUERY PLAN -------------------------------------------- Append -> Seq Scan on pgbench1_accounts -> Foreign Scan on pgbench1_accounts_c1 -> Foreign Scan on pgbench1_accounts_c2 -> Foreign Scan on pgbench1_accounts_c3 -> Foreign Scan on pgbench1_accounts_c4 -> Foreign Scan on pgbench1_accounts_c5 (7 rows) postgres=# EXPLAIN (COSTS false) SELECT * FROM pgbench1_accounts WHERE bid = 1; QUERY PLAN -------------------------------------------- Append -> Seq Scan on pgbench1_accounts Filter: (bid = 1) -> Foreign Scan on pgbench1_accounts_c1 (4 rows) postgres=# EXPLAIN (COSTS false) SELECT * FROM pgbench1_accounts WHERE bid < 3; QUERY PLAN -------------------------------------------- Append -> Seq Scan on pgbench1_accounts Filter: (bid < 3) -> Foreign Scan on pgbench1_accounts_c1 -> Foreign Scan on pgbench1_accounts_c2 (5 rows)
Constraint Exclusionが効いて、必要な外部テーブルにだけ検索が実行されます。もし外部テーブルアクセスを非同期にできたら、なんちゃってパラレルクエリにできるのでは?なんて夢も広がります。
今のPostgreSQLではConstraint Exclusionの仕組みにチェック制約(検査制約)が必要なのですが、外部テーブルに検査制約をつけても完全な強制ができるわけではないので、通常のチェック制約と区別できる文法が必要なのでは?といった議論が進んでいます。
さいごに
このように、9.4でも外部テーブルに関する機能追加は地道に行われています(まだコミットはされてませんが)。もし興味を持った方がいたら、開発に参加してみませんか?(無理矢理