2019の25日目の記事です。昨日は@atmitaniさんのPostgreSQLの統計情報を可視化の話でした。Wrapper)は、PostgreSQLが外部のリソースに対して、あたかもPostgreSQLにデータがあるかのようにアクセスできる機能です。oracle_fdw、mysql_fdwなど様々なFDWがありますが、PostgreSQLにはpostgres_fdwといって、外部にあるPostgreSQLサーバからデータを取ってくるためのプログラムが付属されています。PostgreSQLがPostgreSQLと連携することは一見使い道がなさそうに思えますが、例えばテーブル・パーティショニング機能と連携することでシャーディングのようなことができたり、postgres_fdwを経由して異なるバージョンのPostgreSQLからデータを取ってくることでバージョンアップにも使えたりします。SawadaFDWを使うと透過的に外部にあるリソース(サーバやサービスなど)にアクセスできるため、FDWを利用するユーザとしてはあたかも1台のPostgreSQLに接続しているかのように振る舞うことを期待します。現在のpostgres_fdwではいくつかの(トランザクションに関連する)機能が不足しているため、思いがけない結果を取得することがあります。実際に確認してみましょう。使用するクラスタの構成は次の通りです。クライアント(C)はPostgreSQLサーバ(S)にSQLを送ります。PostgreSQLサーバはFDW(postgres_fdw)を介して外部のサーバ(S1とS2)に繋がっています。S1とS2もPostgreSQLサーバで、それぞれが持っているテーブルa1とテーブルa2には1000行ずつ入っていています。postgres_fdwでは、実行計画からSQLを生成しそれを外部サーバに送ることでデータを取得します。(a2テーブル:1000行入ってる)読み取り結果の異常1では、クライアントから以下のように2つのトランザクションを実行してみましょう。#列は実行順序を表し、結果列にはSELECTによって得られた結果を書いています。COMMIT上記の結果は期待通りの結果でしょうか?試しにFDWを使用せず上記の手順を実行してみてください。以下のような結果(FDWを使わないときの結果列)になると思います。COMMIT実はFDWを使う時と使わない時で結果が異なります。Bでコミットされたデータがその後の(6での)SELECTで見えていません。2回のSELECT(2と6)では同じ結果を返しています。Readに設定します1。これは、クエリが複数のテーブルスキャンを外部サーバでで行う際に、確実に全てのスキャンにおいて一貫した結果を取り出すために必要なのですが、一方で上記のようなFDWを使わなかった時(単一サーバで実行した時)きでは起こり得ない結果が起こります。読み取り結果の異常2次に以下のように2つのトランザクションを実行してみましょう(#列は実行順序を示しています)。COMMITすでにFDWを使わないときの結果列とFDWを使ったときの結果列に記載しているように、これもFDWを使った場合とそうでない場合で結果が異なります。Bの更新結果が見えてしまっています。postgres_fdwでは、SQLを受け付けたサーバが初めて外部サーバにSQLを送るときにトランザクションを開始します。上記の例では、2で片方のサーバ(S1サーバ)でトランザクションが開始され、6でもう片方のサーバ(S2サーバ)でトランザクションが開始されます。BによるDELETEがコミットされているため、削除後の状態が見えています。上記の例では一つのSQLで一つの外部サーバからデータを取得しているため、どのタイミングで外部サーバでトランザクションが開始されるかは比較的わかりやすいですが、結合をするクエリではユーザはFDWを介した外部サーバへの接続順序やSQL実行順序は全くわかりません。そのため、この「初めて外部サーバにSQLを送るときにトランザクションを開始する」というFDW(postgres_fdw)挙動は、期待していない結果を返す可能性があるため注意が必要です。最後に以下のようなケースを考えてみます。クライアントはトランザクションを開始し2つのテーブルを更新しコミットをしましたが、片方のサーバからのコミットが完了した直後にクラッシュしてしまいました。ローカルサーバがクラッシュしたのでAbortこのような状況が起きた場合、片方の外部サーバではトランザクションが成功し、もう片方では失敗するため、2つのサーバ間でのデータの整合性が崩れてしまいます(各サーバに閉じれば整合性は保たれています)。postgres_fdwでは複数サーバに跨るアトミックなコミットをサポートしていません、FDWを介して複数サーバ上のテーブルを更新した場合、COMMIT時に外部サーバで開いているトランザクションを一つずつコミットしていきます。ある外部サーバでのコミット中にそのサーバが故障してしまうかもしれませんし、もしくはこれからコミットする必要のある外部サーバがダウンしてしまう可能性もあります。このような場合に、postgres_fdwでは「一方のサーバではコミットされたけど、もう一方のサーバではアボートした」という状況が起こり得ます。まとめFDWを使った時にトランザクションの観点から注意するべき点について書きました。一言でいうとFDWはまだreadとwriteが混じったグローバルなトランザクションに対応していません。postgres_fdwを使った場合、外部サーバはPostgreSQLなので各サーバに閉じれば整合性は保たれていますし、一度コミットされたデータが消失することはありませんが、複数のサーバで構成された一つの大きなクラスタとして見ると、期待とは異なる挙動をしているように見えたり、データの整合性が保たれない事があります。いえ、read-onlyなワークロードや、そこまで結果の正確性を求めない場合など、ユースケースによってはFDWは大きな武器になるため、FDWを本格的に使っていこうという方はぜひその辺りも注意して検討してみてください。ちなみにFDWはPostgreSQLコミュニティでも活発に開発が続けられている機能の一つです。上記の問題を解決するような機能もいくつか提案されているので今後の進化にも期待ですね2。ローカルトランザクション(ユーザが実行したトランザクション)がSerializableの場合は外部サーバでもSerializableでトランザクションを開始します。読取り異常の方はタイムスタンプを使った一貫性のあるスナップショットを用いてデータを見に行く方法。@masahiko_sawada。