postgres_fdw
モジュールは、外部のPostgreSQLサーバに格納されたデータをアクセスするために使用する、postgres_fdw
外部データラッパを提供します。
実質上、本モジュールの提供する機能は以前のdblinkモジュールが提供する機能と重複していますが、postgres_fdw
はリモートのテーブルにアクセスするためにより透過的で標準に準拠した構文を利用できるほか、多くの場合においてより良い性能を得る事ができます。
postgres_fdw
を使用したリモートアクセスを準備するには:
CREATE EXTENSIONを使用してpostgres_fdw
拡張をインストールします。
CREATE SERVERを使用して、接続しようとする各リモートデータベースを定義する外部サーバオブジェクトを作成します。
user
およびpassword
を除く接続パラメータを、外部サーバオブジェクトのオプションとして指定します。
CREATE USER MAPPINGを使用して、外部サーバへのアクセスを許可するデータベースユーザごとにユーザマッピングを作成します。
ユーザマッピングのuser
およびpassword
オプションを使用してリモートユーザのためのユーザ名とパスワードを指定します。
CREATE FOREIGN TABLEもしくはIMPORT FOREIGN SCHEMAを使用して、アクセスしたいリモートテーブルごとに外部テーブルを作成します。 外部テーブルのカラム定義は被参照側のリモートテーブルに一致していなければなりません。 しかしながら、外部テーブルのオプションとして正しいリモートの名前を外部テーブルのオプションに指定すれば、テーブルおよびカラム名はリモートのものと異なった名前を付ける事ができます。
今のところ、リモートテーブルに格納されているデータにアクセスするには少なくとも外部テーブルに対するSELECT
権限が必要です。
また、INSERT
やUPDATE
、DELETE
を使用してリモートテーブルを操作する事もできます。
(もちろん、ユーザマッピングで指定されたリモートユーザは、これらの操作を実行する権限を有している必要があります)
postgres_fdw
は今のところ、ON CONFLICT DO UPDATE
句のあるINSERT
文をサポートしていないことに注意して下さい。
しかし、一意インデックスの推定の指定を省略しているならば、ON CONFLICT DO NOTHING
句はサポートされます。
postgres_fdw
は、パーティションテーブルで実行されたUPDATE
文により引き起こされる行の移動をサポートしますが、移動した行を挿入するよう選択されたリモートパーティションが後で更新されるUPDATE
対象のパーティションでもある場合は、今のところ扱わないことにも注意してください。
一般的な推奨事項として、可能であれば外部テーブルのカラムを、被参照側のリモートテーブル側のカラムと全く同一のデータ型および照合順序によって定義してください。
postgres_fdw
は必要に応じてデータ型の変換を行いますが、リモートサーバがローカルサーバとは少々違ったWHERE
句の解釈を行うため、データ型や照合順序が一致していないと、時には予期しない結果を得る事があるかもしれません。
リモートテーブルより少ないカラム数で、あるいは異なった順序であっても外部テーブルを定義できる事に留意してください。 リモートテーブル側のカラムとの対応付けは、その位置ではなく、名前によって行われます。
postgres_fdw
外部データラッパを使用する外部サーバは、以下に記す許されていないものや特別な取り扱いのものを除き、33.1.2に記載されているlibpqが接続文字列としてサポートするものと同一のオプションを使用する事ができます。
user
、password
およびsslpassword
(これらは代わりにユーザマッピングのオプションの中で指定するか、サービスファイルを使用します)
client_encoding
(これはローカルサーバのエンコーディングが自動的にセットされます)
fallback_application_name
(自動的にpostgres_fdw
とセットされます)
sslkey
とsslcert
- これは、接続とユーザマッピングのどちらか一方、または両方に現れます。
両方に存在する場合、ユーザマッピングの設定が接続設定に優先します。
スーパーユーザのみがsslcert
やsslkey
の設定のあるユーザマッピングを作成したり修正したりできます。
スーパーユーザのみが外部サーバに対してパスワードなしの認証で接続できます。
したがって、非特権ユーザのユーザマッピングにはpassword
を必ず指定するようにして下さい。
ユーザマッピングオプションpassword_required 'false'
を設定することで、スーパーユーザはこのユーザマッピング単位の検査を無効にできます。例えば以下の通りです。
ALTER USER MAPPING FOR some_non_superuser SERVER loopback_nopw OPTIONS (ADD password_required 'false');
非特権ユーザが、postgresサーバが動作しているunixユーザの認証権限を悪用して、スーパーユーザ権限へ昇格するのを防ぐため、スーパーユーザのみがユーザーマッピングでこのオプションを設定できます。
これが、CVE-2007-3278やCVE-2007-6601のように、マップされたユーザがマップされたデータベースにスーパーユーザとして接続するのを許可しないことを確実にするよう注意が必要です。
public
ロールではpassword_required=false
を設定しないでください。
postgresサーバが動作しているシステムユーザのunixのホームディレクトリにある、任意のクライアント証明書や.pgpass
、.pg_service.conf
などをマップされたユーザは潜在的には利用可能なことを心に留めておいてください。
peer
やident
認証のような認証モードで付与された信頼関係を使うこともできます。
これらのオプションによりリモートのPostgreSQLサーバに送出されるSQL文で使用される名前を制御する事ができます。 外部テーブルがリモートテーブルとは異なった名前で定義されている場合、これらのオプションは必須です。
schema_name
外部テーブルに対して指定できるこのオプションは、リモートサーバ上のリモートテーブルのスキーマ名を与えます。 省略された場合、外部テーブルのスキーマ名が使用されます。
table_name
外部テーブルに対して指定できるこのオプションは、リモートサーバ上のリモートテーブル名を与えます。 省略された場合、外部テーブルのテーブル名が使用されます。
column_name
外部テーブルのカラムに対して指定できるこのオプションは、リモートサーバ上のカラム名を与えます。 省略された場合、外部テーブルのカラム名が使用されます。
postgres_fdw
はリモートサーバに対するクエリを実行しリモートのデータを受信します。したがって、理想的には外部テーブルをスキャンする推定コストは、それをリモートサーバで実行するコストと通信オーバーヘッドの和となります。
この推定を行うための最も信頼できる方法は、リモートサーバに問い合わせを行い、その結果にオーバーヘッド分を加算する事ですが、小さいクエリではコスト推定を得るための追加的な問い合わせに要するコストに見合わないかもしれません。
そこで、どのようにコスト推定を行うかを制御するため、postgres_fdw
は以下のようなオプションを提供します。
use_remote_estimate
外部テーブルまたは外部サーバに指定できるこのオプションは、コスト推定を得るためにpostgres_fdw
がリモートのEXPLAIN
コマンドを発行するかどうかを制御します。
外部テーブルに対する設定は、関連付けられた外部サーバに対する設定を上書きしますが、その効果は当該外部テーブルに限定されます。
デフォルト値はfalse
です。
fdw_startup_cost
外部テーブルまたは外部サーバに指定できるこのオプションは、当該外部サーバに関連付けられた全ての外部テーブルスキャンの推定開始コストに加算される数値です。
これは、接続の確立、リモート側でのクエリのパース・最適化など、追加的なオーバーヘッドを表現します。
デフォルト値は100
です。
fdw_tuple_cost
外部サーバに指定できるこのオプションは、このサーバでの外部テーブルのスキャンにおいて、各タプル毎に発生する追加的なコストとして使用される数値です。
これは、サーバ間のデータ転送における追加的なオーバーヘッドを表現し、リモートサーバへのネットワーク遅延の高低を反映するためにこの数値を増減することができます。
デフォルト値は0.01
です。
use_remote_estimate
がtrue
の時、postgres_fdw
はリモートサーバから行数とコスト推定値を取得し、それをfdw_startup_cost
とfdw_tuple_cost
に加算します。
一方、use_remote_estimate
がfalse
の時、postgres_fdw
はローカルの行数とコスト推定値を取得しfdw_startup_cost
とfdw_tuple_cost
をコスト推定値に加算します。
このローカルな推定は、リモートテーブルの統計情報のローカルコピーが利用可能でないと、正確である見込みはほとんどありません。
ローカルな統計情報を更新するには外部テーブルに対するANALYZEを実行します。これはリモートテーブルに対するスキャンを実行し、あたかもローカルなテーブルであるかのように統計情報の計算と保存を行います。
ローカルな統計情報を保存する事で、クエリの度にリモートテーブルの実行計画を作成するオーバヘッドを削減する事ができます。
しかしながら、リモートテーブルの更新頻度が高ければローカルの統計情報はすぐに実態を反映しなくなるでしょう。
デフォルトでは、組み込みの演算子および関数を使ったWHERE
句のみがリモートサーバでの実行を考慮されます。
組み込みでない関数を含む句は、行が取得された後、ローカルで検査されます。
そのような関数がリモートサーバで利用でき、かつローカルで実行するのと同じ結果を生成すると信頼できるときは、そのようなWHERE
句をリモートでの実行のために送出することでパフォーマンスを向上することができます。
この動作は以下のオプションを使うことで制御できます。
extensions
このオプションは、PostgreSQLの拡張で、ローカルとリモートの両方に、互換のバージョンがインストールされているものの名前のリストです。 IMMUTABLEで、列挙された拡張に属する関数と演算子は、リモートサーバに送出可能とみなされます。 このオプションは外部サーバについてのみ指定可能で、テーブル毎の指定ではありません。
extensions
オプションを使用する場合、列挙する拡張が存在し、かつローカルとリモートのサーバで同一の動作をするようにすることはユーザの責任です。
そうでない場合、リモートの問い合わせは失敗したり、期待と異なる動作をするかもしれません。
fetch_size
このオプションは、postgres_fdw
が1回のフェッチの動作で何行のデータを取得するかを指定します。
これは外部テーブルあるいは外部サーバに対して指定できます。
テーブルに対して指定されたオプションは、サーバに対して指定されたオプションよりも優先します。
デフォルトは100
です。
デフォルトではpostgres_fdw
を使用する全ての外部テーブルは更新可能であると想定されます。以下のオプションにより、この挙動を上書きする事ができます。
updatable
このオプションは、postgres_fdw
がINSERT
、UPDATE
あるいはDELETE
コマンドを使用して外部テーブルを操作する事を許可するかどうかを規定します。
外部テーブルで指定されたオプションは、外部サーバにおいて指定されたオプションを上書きします。
デフォルト値はtrue
です。
もちろん、リモートテーブルが実際には更新可能ではなかった場合、いずれにしてもエラーが発生するでしょう。このオプションを使用することで、リモートサーバへの問い合わせを行う事なくローカルでエラーを発生させることができます。
また、information_schema
ビューは、このオプションの値に従ってpostgres_fdw
管理下の外部テーブルを更新可能(あるいは不可能)であるとレポートする事に留意してください。
リモートサーバ側のチェックは一切行われません。
postgres_fdw
はIMPORT FOREIGN SCHEMAを使って、外部テーブルの定義をインポートすることができます。
このコマンドは、リモートのサーバ上に存在するテーブルあるいはビューとマッチする外部テーブルの定義をローカルサーバ上に作成します。
インポートするリモートのテーブルにユーザ定義のデータ型の列がある場合、ローカルサーバにも同じ名前の互換性のある型がなければなりません。
インポートの動作は以下のオプションでカスタマイズできます(IMPORT FOREIGN SCHEMA
コマンドで指定します)。
import_collate
このオプションは、列のCOLLATE
オプションが、外部サーバからインポートする外部テーブルの定義に含まれているかどうかを制御します。
デフォルトはtrue
です。
リモートサーバとローカルサーバで照合順序の名前の集合が異なる場合は、この設定を無効にする必要があるでしょう。
リモートサーバが異なるOSで動作しているなら、そういうことがありそうです。
import_default
このオプションは、列のDEFAULT
式が外部サーバからインポートされる外部テーブルの定義に含まれているかどうかを制御します。
デフォルトはfalse
です。
このオプションを有効にする場合は、ローカルサーバとリモートサーバで異なる計算をされるデフォルトに注意して下さい。
nextval()
はよくある問題の一つです。
インポートされるデフォルト式がローカルには存在しない関数または演算子を使っていた場合、IMPORT
は失敗します。
import_not_null
このオプションは、列のNOT NULL
制約が、外部サーバからインポートされる外部テーブルの定義に含まれているかどうかを制御します。
デフォルトはtrue
です。
NOT NULL
以外の制約は決してリモートのテーブルからインポートされないことに注意して下さい。
PostgreSQLは外部テーブルのCHECK
制約をサポートしていますが、それを自動的にインポートする予定はありません。
なぜなら、制約の式はローカルとリモートのサーバで異なる評価をされる危険があるからです。
CHECK
制約でそのような一貫しない動作があると、問い合わせの最適化で検知するのが難しい誤りが発生するかもしれません。
そのため、CHECK
制約をインポートしたい場合は、それを手作業で実行する必要があり、またその一つ一つの意味を注意深く確認するべきです。
外部テーブルのCHECK
制約の取扱いについて、詳しくはCREATE FOREIGN TABLEを参照して下さい。
他のテーブルのパーティションであるテーブルや外部テーブルは、自動的に除外されます。 パーティション化されたテーブルは、他のテーブルのパーティションでない限り、インポートされます。 パーティション化階層のルートであるパーティションテーブルを介してすべてのデータにアクセスできるため、この方法では余分なオブジェクトを作成せずにすべてのデータにアクセスできます。
postgres_fdw
は、外部サーバに関連付けられた外部テーブルを参照するクエリを最初に実行する際に、外部サーバへの接続を確立します。
この接続は保持され、同じセッションで以降の問い合わせのために再利用されます。
しかし、外部サーバへのアクセスに対して複数のユーザ識別子(ユーザマッピング)が使用される場合には、接続はユーザマッピング毎に確立される事になります。
外部サーバ上のリモートテーブルを参照する際に、まだトランザクションが開始されていなければpostgres_fdw
はリモートサーバ上でトランザクションを開始します。
ローカルのトランザクションがコミット、あるいはアボートした時、リモートのトランザクションも同様にコミット、あるいはアボートします。
セーブポイントも同様に管理され、リモート側に関連付けられたセーブポイントが作成されます。
ローカルトランザクションがSERIALIZABLE
分離レベルを用いている時、リモートトランザクションもSERIALIZABLE
分離レベルを使用します。
それ以外の場合にはREPEATABLE READ
分離レベルを使用します。
これは、あるクエリが複数のテーブルスキャンをリモート側で行う際に、確実に全てのスキャンにおいて一貫したスナップショットで結果を取り出すためです。
その結果、別の要求によってリモートサーバ側で競合する更新が発生したとしても、あるトランザクション内の問い合わせはリモートサーバからの一貫したデータを参照する事となります。
ローカルのトランザクションがSERIALIZABLE
あるいはREPEATABLE READ
分離レベルを用いている場合、この動作は期待通りのものでしょう。
一方、ローカルのトランザクションがREAD COMMITTED
分離レベルを使用している場合には、予想外の動作かもしれません。
将来のPostgreSQLリリースではこれらのルールに変更が加えられるかもしれません。
postgres_fdw
は今のところ、二相コミットのためのリモートトランザクションの準備をサポートしていないことに注意して下さい。
外部サーバからのデータ転送量を削減するため、postgres_fdw
はリモート問い合わせを最適化しようと試みます。
これは問い合わせのWHERE
句をリモートサーバに送出する事、およびクエリで必要とされていないカラムを取得しない事により行われます。
問い合わせの誤作動のリスクを下げるため、組み込みあるいは外部サーバのextensions
オプションに列挙されている拡張に属するデータ型、演算子、関数だけを用いたものでない限り、リモートサーバにWHERE
句は送出されません。
また、そのようなWHERE
句で使われる演算子と関数はIMMUTABLE
でなければなりません。
UPDATE
あるいはDELETE
の問い合わせについては、リモートサーバに送出できないWHERE
句がなく、問い合わせにローカルな結合がなく、対象のテーブルにローカルな行レベルのBEFORE
あるいはAFTER
トリガーや格納された生成列がなく、親ビューからのCHECK OPTION
制約がないのであれば、postgres_fdw
は問い合わせ全体をリモートサーバに送出することで、問い合わせの実行の最適化を図ります。
UPDATE
では、問い合わせの誤った実行のリスクを低減するため、対象列への代入式では組み込みのデータ型、IMMUTABLE
演算子、IMMUTABLE
関数のみを使わなければなりません。
同一の外部サーバ上の外部テーブルの間の結合がある場合、postgres_fdw
はその結合全体を外部サーバに送出します。
ただし、何らかの理由で各テーブルから個別に行を取得する方が効率的だと思われる場合、あるいは結合に含まれるテーブルの参照が異なるユーザマッピングに従う場合を除きます。
JOIN
句を送出するにあたり、WHERE
句に関して上で説明したことと同じ注意が払われます。
リモートサーバでの実行のために実際に送出される問い合わせはEXPLAIN VERBOSE
を用いて調べる事ができます。
postgres_fdw
が開いたリモートセッションでは、search_pathパラメータはpg_catalog
にだけ設定されますので、スキーマで修飾しなければ組み込みオブジェクトだけが可視です。
postgres_fdw
自身が生成した問い合わせでは、常にそのような修飾を行ないますので、これは問題になりません。
しかし、リモートテーブルのトリガやルールによってリモートサーバ上で実行された関数にとっては問題の原因となり得ます。
例えば、リモートテーブルが実際にはビューであれば、そのビューで使われている関数はすべて制限された検索パスで実行されるでしょう。
期待される検索パス環境を確立できるよう、そのような関数では名前はすべてスキーマ修飾するか、そのような関数にSET search_path
オプション(CREATE FUNCTION参照)を付けることをお薦めします。
postgres_fdw
は、同様に様々なパラメータでリモートセッション設定を確立します。
TimeZoneはUTC
に設定されます。
DateStyleはISO
に設定されます。
IntervalStyleはpostgres
に設定されます。
extra_float_digitsはリモートサーバが9.0以上では3
に設定され、それより古いバージョンでは2
に設定されます。
これはsearch_path
ほど問題にはならないでしょうが、もし必要になったら関数のSET
オプションで処理してください。
上のパラメータのセッションレベルの設定を変更することで、この振舞いを覆すことはお薦めしません。
postgres_fdw
が正常に動作しない原因となるでしょう。
postgres_fdw
のリモートサーバにはPostgreSQL 8.3以降のバージョンを使用する事ができます。
読み取り専用であれば、8.1以降のバージョンまで可能です。
一方、postgres_fdw
はIMMUTABLE
属性を持った組み込みの演算子と関数が外部テーブルのWHERE
句に含まれる場合、リモート側で実行しても安全であると仮定します。そのため、リモートサーバのリリース後に追加された関数が実行のために送出されるかもしれず、結果として「関数が見つかりません」あるいは類するエラーを発生させる事になります。
この種の問題は問い合わせの書き換えによって対処する事ができます。
例えば、最適化を妨げるため、外部テーブルへの参照をOFFSET 0
を付けて副問い合わせに埋め込み、問題のある関数や演算子を副問い合わせの外に配置するなどの方法があります。
これはpostgres_fdw
で外部テーブルを作成する例です。
まず、拡張をインストールします。
CREATE EXTENSION postgres_fdw;
次に、CREATE SERVERを使って外部サーバを作成します。
この例では、ホスト192.83.123.89
でポート5432
を監視しているPostgreSQLサーバに接続します。
接続されるデータベースはリモートサーバ上でforeign_db
という名前です。
CREATE SERVER foreign_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host '192.83.123.89', port '5432', dbname 'foreign_db');
リモートサーバで使われるロールを特定するためにユーザマッピングも必要です。ユーザマッピングはCREATE USER MAPPINGで定義されます。
CREATE USER MAPPING FOR local_user SERVER foreign_server OPTIONS (user 'foreign_user', password 'password');
これでCREATE FOREIGN TABLEにより外部テーブルが作成できるようになりました。
この例では、リモートサーバのsome_schema.some_table
という名前のテーブルにアクセスします。
対応するローカルの名前はforeign_table
です。
CREATE FOREIGN TABLE foreign_table ( id integer NOT NULL, data text ) SERVER foreign_server OPTIONS (schema_name 'some_schema', table_name 'some_table');
CREATE FOREIGN TABLE
で宣言した列のデータ型やその他の属性は、実際のリモートテーブルと一致していることが必須です。
リモートテーブルでどのような名前なのかを個々の列に対してcolumn_name
オプションで指定しない限り、列名も一致していなければなりません。
多くの場合、外部テーブルの定義を手作業で作成するよりも、IMPORT FOREIGN SCHEMAを使用する方が望ましいです。
花田 茂 <shigeru.hanada@gmail.com>