象と戯れ

 | 

2010-03-23

libpqwalreceiverがひどく苦労されている件

03:29 | libpqwalreceiverがひどく苦労されている件 - 象と戯れ を含むブックマーク はてなブックマーク - libpqwalreceiverがひどく苦労されている件 - 象と戯れ

レプリケーションというよりは分散DBの可能性を探る目的で9.0のソースを調べていましたところ、ストリーミングレプリケーションの一部であるlibpqwalreceiverに行き着きました。以下調べた内容の備忘録。

WalReceiverというのはWalSenderと対になる機能で、レプリケーションを実現するためにその名の通りWALを送ったり受け取ったりします。このときの通信にはクライアントとバックエンドの通信を行うおなじみのlibpqをベースに実装されています。libpqのプロトコルにこっそりレプリケーション用のコマンドタグが追加されているのかと思いきや、PQgetCopyDataで適当にやりとりをしている様子です。

で実際に似たようなことをやろうとしてlibpqの機能を、つまりはPQconnectdbとかをバックエンドで呼び出そうとすると、ビルドできません。なぜかというとPQArgBlockなどの構造体がバックエンド側のlibpq部分(src/backend/libpq)に定義されており、クライアント側の定義(src/interfaces/libpq)とバッティングするからなのです。よしんば定義という意味において旨くインクルードすることで逃れたとしても、バックエンドにlibpqを静的リンクしてしまうとやっぱりうまくいかない。

レプリケーションでWALをやりとりするためにlibpqを使っているはずのところはどうなってるのかいな、と調べたところが本題で、解決策を一言で言えば共有ライブラリにすることで逃れているのでした。libpqwalreceiverの名が示すとおり、これは共有ライブラリになっていてバックエンドのバイナリオブジェクトファイルとは別れています。9.0アルファをソースからビルドしてインストールすると、$PGINSTALL/lib以下にしれっとlibpqwalreceiver.soさんが鎮座ましまされます。

共有ライブラリにすれば解決かというと事はそう簡単ではありません。まず共有ライブラリのシンボルを本体から参照できるよう、exportしなければなりません。定義は以下のようになっています。

src/include/replication/walreceiver.h

/* libpqwalreceiver hooks */
typedef bool (*walrcv_connect_type) (char *conninfo, XLogRecPtr startpoint);
extern PGDLLIMPORT walrcv_connect_type walrcv_connect;

typedef bool (*walrcv_receive_type) (int timeout, unsigned char *type,
												 char **buffer, int *len);
extern PGDLLIMPORT walrcv_receive_type walrcv_receive;

typedef void (*walrcv_disconnect_type) (void);
extern PGDLLIMPORT walrcv_disconnect_type walrcv_disconnect;

PGDLLIMPORTの行だけでよいような気がしますが、何故かtypedefで関数ポインタを変数として定義しています。空っぽの変数はどこで埋められるかというと、共有ライブラリが呼ばれたタイミングでロードイベントハンドラである_PG_init()が処理します。

src/backend/replication/libpqwalreceiver/libpqwalreceiver.c

/*
 * Module load callback
 */
void
_PG_init(void)
{
	/* Tell walreceiver how to reach us */
	if (walrcv_connect != NULL || walrcv_receive != NULL ||
		walrcv_disconnect != NULL)
		elog(ERROR, "libpqwalreceiver already loaded");
	walrcv_connect = libpqrcv_connect;
	walrcv_receive = libpqrcv_receive;
	walrcv_disconnect = libpqrcv_disconnect;
}

libpqrcv_*はファイルローカルなstatic関数として定義されています。これをやる理由はおそらくですが、本体側のメモリ空間に関数ポインタを用意しておかなければならないからだと思います。というのも、src/backend/replication/walreceiver.cの変数としてこれらの実体が宣言されているからです。といいつつ詳細は私の実力ではよくわかりません。。。(誰か教えてー)

ちなみにWAL転送に使う接続オブジェクトPGconnはlibpqwalreceiver.cのstatic変数として定義されていて、各関数を呼ぶだけで変数の整理整頓は外から気にしなくてよいようになっています。これはWalReceiverが専任の1プロセスで動いているからこそできる芸当のような気がします。さらにはlibpqrcv_connectの中を読むと接続オプション文字列の終端に"replication=true"という隠しオプションを追加していたりしてこっそり過ぎます。

かくして準備が整った共有ライブラリはWalReceiverの起動時にロードされます。bootstrap.cからWalReveiveMain()が呼ばれ、walreceiver.cへ処理が移ります。245行目あたりにおもむろに


	/* Load the libpq-specific functions */
	load_file("libpqwalreceiver", false);
	if (walrcv_connect == NULL || walrcv_receive == NULL ||
		walrcv_disconnect == NULL)
		elog(ERROR, "libpqwalreceiver didn't initialize correctly");

と書いてあり、ハードコーディングも厭わない漢っぷりです。このload_fileがライブラリをアタッチし、_PG_init()を呼び出し、グローバル変数に関数が登録され、共有ライブラリ内で動作するlibpqを自在に操れるというわけです。これでも色々端折って説明しているのですが途中途中に苦労の爪痕が見え隠れしてこれはこれで一つのドラマであります。

src/backend/replication/READMEを読むと簡単にlibpqwalreceiverの説明があり、締めくくりとして

This API should be considered internal at the moment, but we could open it

up for 3rd party replacements of libpqwalreceiver in the future, allowing

pluggable methods for receiveing WAL.

このAPIは今のところ内部的なものですが、サードパーティのために公開することもあり得ます。そうすれば、プラガブルなWAL受信を実装できるからです。

などと書いてあるのですが、そもそもlibpqが本体とリンクできないっていうのが最大の痛手でありますきっぱり。おやと思って考えてみましたがdblinkはcontribなのでもちろん共有ライブラリだし、SQL/MED実装のpgsql-fdwにしてもlibpq接続部分はやはりcontribにして逃げていたのでした。どうしてCoreにしないのかな、と思っていたのですがそういう経緯があったとは・・・WAL受信をプラガブルにする前にlibpq(または相当)をコアに持たないとこの先真っ暗な気がするのですが気のせいでしょうか。

JaydeeJaydee2011/08/22 04:19Kewl you sholud come up with that. Excellent!

bsxvckzdawbsxvckzdaw2011/08/27 01:02tRkNOU <a href="http://xryicybeneio.com/">xryicybeneio</a>

chnnvyzljwchnnvyzljw2011/08/31 21:53VVteiE , [url=http://kgyhhchzebcc.com/]kgyhhchzebcc[/url], [link=http://aiidaaugzuar.com/]aiidaaugzuar[/link], http://immvfagmhanu.com/

 |