PostgreSQL 楽ちん FDW Foreign Table 作成

PostgreSQL には他のDBのテーブルを参照(更新もできる)できるようにする Foreign Data Wrapper (FDW) という機能が搭載されています。

これよりも前に dblink がありましたが、仕組み上パフォーマンス的に厳しく使いにくさがありました。FDW ではかなり良くなっています。PostgreSQL 以外の DB と接続することも可能です。

PostgreSQL9.3 開発と運用を支える新機能
http://enterprisezine.jp/dbonline/detail/5620

PostgreSQL 標準の replication は DB 全体での同期しかなく、レプリカは参照しかできませんから分析用のサーバーとして使おうにも書き込むことができません。将来的には論理 replication ができるようになりそうです。特定のテーブルだけとか。
http://www.postgresql.org/docs/9.4/static/logicaldecoding.html
WAL を decode して必要なものだけ取り出す感じ。Oracle の LogMiner 的な。

DB まるごと FDW でアクセスしたいと言われた場合、テーブル一個ずつ手作業で作成するのは非常にしんどいので便利なツール転がってないかなと調べてみたらやっぱりありました。感謝感謝。
http://www.postgresonline.com/journal/archives/322-Generating-Create-Foreign-Table-Statements-for-postgres_fdw.html

それではこの便利ツールを使った場合の流れをメモ

概要

例として apple と orange というデータベースを作成し、それぞれDBと同じ名前のユーザーを作成。
apple には pgbench を使ってテーブルを作成(どんなテーブルを作るか考えるの面倒なので)。
orange には apple のテーブルを参照する FOREIGN TABLE を作成する
環境は CentOS 7 と PostgreSQL 9.4

PostgreSQL と pgbench (contrib) をインストール&起動

$ sudo yum install -y http://yum.postgresql.org/9.4/redhat/rhel-7-x86_64/pgdg-centos94-9.4-1.noarch.rpm
$ sudo yum install -y postgresql94-server postgresql94-contrib
$ sudo -u postgres /usr/pgsql-9.4/bin/initdb -E utf8 --no-locale -D /var/lib/pgsql/9.4/data
$ sudo systemctl enable postgresql-9.4
$ sudo systemctl start postgresql-9.4

ユーザーとデータベースの作成

$ sudo -u postgres /usr/pgsql-9.4/bin/psql -c "create user apple password 'ringo'"
$ sudo -u postgres /usr/pgsql-9.4/bin/psql -c "create user orange password 'mikan'"
$ sudo -u postgres /usr/pgsql-9.4/bin/createdb -E utf8 -O apple apple
$ sudo -u postgres /usr/pgsql-9.4/bin/createdb -E utf8 -O orange orange

/var/lib/pgsql/9.4/data/pg_hba.conf の書き換えと反映。
初期設定ではローカルからのアクセスにパフワードが不要となっているので postgres ユーザ以外は必要にしておく。

# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             postgres                                trust
local   all             all                                     md5
# IPv4 local connections:
host    all             postgres        127.0.0.1/32            trust
host    all             all             127.0.0.1/32            md5
# IPv6 local connections:
host    all             postgres        ::1/128                 trust
host    all             all             ::1/128                 md5
$ sudo systemctl reload postgresql-9.4

apple DB にテーブルを作成する

pgbench の初期化モードで。

$ /usr/pgsql-9.4/bin/pgbench -i -U apple apple
$ /usr/pgsql-9.4/bin/psql -U apple
apple=> \d
             List of relations
 Schema |       Name       | Type  | Owner 
--------+------------------+-------+-------
 public | pgbench_accounts | table | apple
 public | pgbench_branches | table | apple
 public | pgbench_history  | table | apple
 public | pgbench_tellers  | table | apple
(4 rows)

orange ユーザーが apple のテーブルを SELECT できるようにする

$ /usr/pgsql-9.4/bin/psql -U apple
apple=> GRANT SELECT ON pgbench_accounts, pgbench_branches, pgbench_history, pgbench_tellers TO orange;
GRANT

orange DB にて FOREIGN TABLE を使う準備をする

CREATE EXTENSION は postgres (特権) ユーザーで実行する必要がある

$ sudo -u postgres /usr/pgsql-9.4/bin/psql orange
orange=# CREATE EXTENSION postgres_fdw;
CREATE EXTENSION
orange=# \dew
                      List of foreign-data wrappers
     Name     |  Owner   |       Handler        |       Validator        
--------------+----------+----------------------+------------------------
 postgres_fdw | postgres | postgres_fdw_handler | postgres_fdw_validator
(1 row)

CREATE SERVER と CREATE USER MAPPING で apple DB への接続情報を定義する。これも postgres ユーザーで行う。

ここでは誰でも FOREIGN TABLE を参照できるように public で USER MAPPING を指定しました。
USER MAPPING という名前からもわかるように、これは orange DB へのログインユーザーに対して apple DB へどのログインIDでアクセスするかを設定します。権限の異なるアカウントを使い分けることであるアカウントでは更新可能だが、別のアカウントでは更新不可などと制限できる。
orange ユーザーでなく apple ユーザーとしてアクセスさせることも可能で、そうすると今回の例の場合 FOREIGN TABLE 側から更新することも可能となる。(FOREIGN TABLE に対する GRANT も必要)

$ sudo -u postgres /usr/pgsql-9.4/bin/psql orange
orange=# CREATE SERVER apple FOREIGN DATA WRAPPER postgres_fdw OPTIONS (
orange(#     dbname 'apple',
orange(#     host 'localhost',
orange(#     port '5432'
orange(# );
CREATE SERVER
orange=# CREATE USER MAPPING FOR public SERVER apple OPTIONS (user 'orange', password 'mikan');
CREATE USER MAPPING

apple DB にて便利 function を定義する

# /usr/pgsql-9.4/bin/psql -U apple 
apple=> CREATE OR REPLACE FUNCTION script_foreign_tables(param_server text
apple(>  , param_schema_search text
apple(>  , param_table_search text, param_ft_prefix text) RETURNS SETOF text
apple-> AS 
apple-> $$
apple$> -- params: param_server: name of foreign data server
apple$> --        param_schema_search: wildcard search on schema use % for non-exact
apple$> --        param_ft_prefix: prefix to give new table in target database 
apple$> --                        include schema name if not default schema
apple$> -- example usage: SELECT script_foreign_tables('prod_server', 'ch01', '%', 'ch01.ft_');
apple$>   WITH cols AS 
apple$>    ( SELECT cl.relname As table_name, na.nspname As table_schema, att.attname As column_name
apple$>     , format_type(ty.oid,att.atttypmod) AS column_type
apple$>     , attnum As ordinal_position
apple$>       FROM pg_attribute att
apple$>       JOIN pg_type ty ON ty.oid=atttypid
apple$>       JOIN pg_namespace tn ON tn.oid=ty.typnamespace
apple$>       JOIN pg_class cl ON cl.oid=att.attrelid
apple$>       JOIN pg_namespace na ON na.oid=cl.relnamespace
apple$>       LEFT OUTER JOIN pg_type et ON et.oid=ty.typelem
apple$>       LEFT OUTER JOIN pg_attrdef def ON adrelid=att.attrelid AND adnum=att.attnum
apple$>      WHERE 
apple$>      -- only consider non-materialized views and concrete tables (relations)
apple$>      cl.relkind IN('v','r') 
apple$>       AND na.nspname LIKE $2 AND cl.relname LIKE $3 
apple$>        AND cl.relname NOT IN('spatial_ref_sys', 'geometry_columns'
apple$>           , 'geography_columns', 'raster_columns')
apple$>        AND att.attnum > 0
apple$>        AND NOT att.attisdropped 
apple$>      ORDER BY att.attnum )
apple$>         SELECT 'CREATE FOREIGN TABLE ' || $4  || table_name || ' ('
apple$>          || string_agg(quote_ident(column_name) || ' ' || column_type 
apple$>            , ', ' ORDER BY ordinal_position)
apple$>          || ')  
apple$>    SERVER ' || quote_ident($1) || '  OPTIONS (schema_name ''' || quote_ident(table_schema) 
apple$>      || ''', table_name ''' || quote_ident(table_name) || '''); ' As result        
apple$> FROM cols
apple$>   GROUP BY table_schema, table_name
apple$> $$ language 'sql';
CREATE FUNCTION

CREATE FOREIGN TABLE の生成

コピペしやすいように \t, \a で psql の出力を調整している。

$ /usr/pgsql-9.4/bin/psql -U apple
apple=> \t
Tuples only is on.
apple=> \a
Output format is unaligned.
apple=> SELECT script_foreign_tables('apple', 'public', '%', 'public.ft_');
CREATE FOREIGN TABLE public.ft_pgbench_accounts (aid integer, bid integer, abalance integer, filler character(84))  
   SERVER apple  OPTIONS (schema_name 'public', table_name 'pgbench_accounts'); 
CREATE FOREIGN TABLE public.ft_pgbench_branches (bid integer, bbalance integer, filler character(88))  
   SERVER apple  OPTIONS (schema_name 'public', table_name 'pgbench_branches'); 
CREATE FOREIGN TABLE public.ft_pgbench_history (tid integer, bid integer, aid integer, delta integer, mtime timestamp without time zone, filler character(22))  
   SERVER apple  OPTIONS (schema_name 'public', table_name 'pgbench_history'); 
CREATE FOREIGN TABLE public.ft_pgbench_tellers (tid integer, bid integer, tbalance integer, filler character(84))  
   SERVER apple  OPTIONS (schema_name 'public', table_name 'pgbench_tellers'); 
apple=> 

実際には次のようにファイルに書きだしたほうが便利 (psql 内での \o create_foreign_table.sql でも可)。

psql -At -c "SELECT script_foreign_tables('apple', 'public', '%',
'public.ft_')" > create_foreign_table.sql

script_foreign_tables の引数は

SELECT script_foreign_tables(‘apple’, ‘public’, ‘%’, ‘public.ft_’);

# 実行例の値 説明
1 apple CREATE FOREIGN TABLE で指定する SERVER
2 public apple DB での対象テーブルの schema
3 % apple DB での対象テーブルを絞るための LIKE 条件 (% の場合は全てとなる)
4 public.ft_ CREATE TABLE 時の prefix (public.ft_ であれば public schema に ft_{original_table_name} という TABLE が作成される)。 public. であれば同じ名前の TABLE が作成される

CREATE FOREIGN TABLE

postgres ユーザーで orange DB に貼り付けて (もしくはファイルを読み込ませて) FOREIGN TABLE を作成する

$ sudo -u postgres /usr/pgsql-9.4/bin/psql orange
orange=# CREATE USER MAPPING FOR public SERVER apple OPTIONS (user 'orange', password 'mikan');
CREATE USER MAPPING
orange=# CREATE FOREIGN TABLE public.ft_pgbench_accounts (aid integer, bid integer, abalance integer, filler character(84))  
orange-#    SERVER apple  OPTIONS (schema_name 'public', table_name 'pgbench_accounts'); 
CREATE FOREIGN TABLE
orange=# CREATE FOREIGN TABLE public.ft_pgbench_branches (bid integer, bbalance integer, filler character(88))  
orange-#    SERVER apple  OPTIONS (schema_name 'public', table_name 'pgbench_branches'); 
CREATE FOREIGN TABLE
orange=# CREATE FOREIGN TABLE public.ft_pgbench_history (tid integer, bid integer, aid integer, delta integer, mtime timestamp without time zone, filler character(22))  
orange-#    SERVER apple  OPTIONS (schema_name 'public', table_name 'pgbench_history'); 
CREATE FOREIGN TABLE
orange=# CREATE FOREIGN TABLE public.ft_pgbench_tellers (tid integer, bid integer, tbalance integer, filler character(84))  
orange-#    SERVER apple  OPTIONS (schema_name 'public', table_name 'pgbench_tellers');
CREATE FOREIGN TABLE
orange=# 

postgres ユーザーで作成しているため、必要なユーザーがアクセス可能なように GRANT して上げる必要がある。これも大量にあるとつらいので次のようなクエリで対応する (FOREIGN TABLE は pg_stat_user_tables から取得できなかった)

SELECT 'GRANT SELECT ON ' || relname || ' TO orange;'
  FROM pg_catalog.pg_class
 WHERE relkind = 'f';

PostgreSQL 9.5 から FDW がもっと便利に

IMPORT FOREIGN SCHEMA を使えば簡単に source database の schema をコピーして foreign table が作れるようになるみたいです。

PostgreSQL: Documentation: 9.5: IMPORT FOREIGN SCHEMA

以上

コメント

  1. […] PostgreSQL 楽ちん FDW Foreign Table 作成 ← まだ試していないが、このようなやり方を利用すれば比較的テーブルの定義は楽にできるかもしれない。 […]