[pgsql-jp: 35220] Re: pgpoolとLargeObject

Tatsuo Ishii t-ishii @ sra.co.jp
2005年 4月 9日 (土) 19:38:23 JST


石井です.

> こんばんは。お返事遅くなってしまってすみません。加澤です。
> 
> 皆さん貴重なご意見本当にどうもありがとうございます。その後の顛末をご報告
> しますね。
> 
> // また長いメールになってしまいました。
> 
> 水野さん wrote:
> > どうしてもpgpoolでないとダメでしょうか。
> > 単なる冗長化という目的であれば、うちではdb_mirrorを使っています。
> > #mirrorのタイムラグ分、双方の不一致が発生しますが。
> 
> 今回想定しているシステムの要件に「計画停止は許容出来るが非計画停止は原則
> ダメ」「データロストは原則ダメ」というものが含まれておりまして (まぁどち
> らもそれほど厳密なものではないのですが)、そのために dbmirror のような非
> 同期レプリケーションは選考から落としていました。

というか,dbmirrorやSlony-Iのようなトリガベースのreplicationではそもそ
もラージオブジェクトは原理的にreplicationできません.ラージオブジェク
トはトリガの対象にはできませんから.

> また人の手を介さない failover を行おうとすると、たぶん結局 pgpool の
> master/slave モードなどを併用する必要があるのだと理解しているのですが、
> それならば pgpool だけで完結させられればシンプルで良いと考えた、というこ
> ともありました。
> 
> 
> 谷田さん wrote:
> > そうです。代用としてはbytea型を使う方法があります。
> 
> bytea 型を用いる代替案、いくつか試してみました。このメールの最後で書きま
> すね。
> 
> > 私の知る限り、ラージオブジェクトを正しくレプリケーションできるソリューショ
> > ンはないです。なぜなら
> > 
> > - 問い合わせベースだと、OIDの同期が取れない
> > - トリガベースだと、システムテーブルにトリガーが張れない
> 
> とても参考になります。いつもどうもありがとうございます。
> 
> 
> 石井さん wrote:
> > これは,マスタとセカンダリで違うoidがアサインされてしまっているからな
> > んですね.
> 
> はい。
> 
> 
> > おっしゃる通りで,同時セッションが1ならば問題なく動作しますが,複数セッ
> > ションを動かすときはシーケンスと同様,何らかの方法でマスタとセカンダリ
> > でoidの生成タイミングの同期を取らないと上のようなエラーになります.
> > 
> > 回避策としては,ラージオブジェクトの生成を行う前に,pg_databaseのよう
> > なshared catalogにテーブルロックをかけて同期を保障する方法があります.
> > 詳細はインプレスさんの「丸ごとPostgreSQL」に書いたので,そちらをご覧下
> > さい.
> 
> インプレスさんの「丸ごと!PostgreSQL」、たまたま知人が持っていたのを発見
> しまして、早速読ませていただきました。LargeObject の生成時に「LOCK TABLE
> pg_database」して oid の生成を master/secondary で同期させるのが肝なので
> すね。
> 
> 早速上記コードをアプリケーションに組み込みテストしてみたところ、最終的に
> うまく動作させることが出来ました!本当にどうもありがとうございます。
> 
> ただ、うまく動作するようになるまでにいくつか落とし穴がありまして、
> 
> ・OID を更新する処理は LargeObject の生成以外にもたくさんあるため、それ
> らが起こらないようにシステムを設計する必要がある。
> →当初、明示的に WITHOUT OIDS しないでテーブルを作成していたためにアプリ
> ケーションで用いる全てのテーブルに暗黙の OID 列が存在し、そのために
> LargeObject の生成だけでなく単なるレコードの INSERT でも OID の更新が発
> 生、同期が崩れてしまった。
>  →アプリケーションで用いる全てのテーブルを「WITHOUT OIDS」付きとした。

そうなんですよね.メールを送信してから気が付きました.

>  →system table にも同じように定期的に OID を更新するようなものがないか
> どうか調べてみましたが、DDL などを流さない限りとりあえず大丈夫そうでした
> (ほんと?)。少なくとも通常の SELECT や DML、VACUUM などでは問題ないよう
> です。stat 系もそれなりに取っていますが、とりあえず大丈夫そう。でも
> ちょっと不安…。

私の知る限り,これで合っていると思います.

> ・OID は「DB」に対してではなく「DB Cluster」に対して存在するものであるの
> で、利用しようとする DB Cluster 上に別のアプリケーションが利用する DB が
> あったり、OID を更新するような処理のものが存在すると同期が崩れてしまう。
> →ただし、LOCK する pg_database テーブルは DB Cluster で一つだけ存在する
> テーブルのようなので、たとえ利用しようとしている DB Cluster に複数の DB
> が存在したとしても、全て同じアプリケーションから利用するのであれば (つま
> り LargeObject 生成時に必ず pg_database テーブルを LOCK することが保証さ
> れているのであれば) 問題なく動作しそう。

はい,そうですね.

> などが検証中に問題となりました (他にも何かあるかな?)。
> 
> ***
> 
> LargeObject ではなく、bytea 型を用いてデータを格納する方法を2種類ほど試
> してみました。
> 
> その1.単一のレコードに全データを入れてしまう実装
> その2.pg_largeobject テーブルのように、データを細切れのチャンクに分割
> して格納する実装
> 
> まずその1.ですが、JDBC 経由のアプリケーションの場合、ある程度以上大き
> なデータ (数 Mbytes 程度) で極端に処理速度が遅くなってしまったり、JavaVM
> の Heap 消費量が激増してしまったりしたために早々に断念しました (当初はこ
> の案でいくつもりだった)。
> 
> その2.については、そこそこうまく、特に読み出しと削除についてはほとんど
> LargeObject と遜色ない程度にちゃんと動いたのですが、なぜか書き込み
> (INSERT の大量発行) が LargeObject を用いた場合と比べて数 Mbytes のデー
> タで5倍ほど遅く、実用になるかどうかぎりぎりのところだと思いました。チャ
> ンクサイズをいろいろ変えて (LargeObject と同じ 2K から 64K 程度まで) 試
> してみましたが、それほど劇的には改善されませんでした。
> (逆に一つのチャンクに収まるような小さいデータの場合は LargeObject を用い
> るよりも高速に処理できました。)
> 
> その2.は書き込みが5倍遅い、というデメリットがある反面、非常に generic
> なコードで実現出来る (PostgreSQL への依存度が少なく、トリッキーなダウン
> キャストや system table の LOCK などを用いる必要がない。権限管理も容易)
> という大きなメリットがあるため、LargeObject を用いた方法が最終的にうまく
> ない場合に利用可能な「保険」的に今のところは考えています。
> 
> ところで、JDBC ドライバのコードもざっと眺めてみても、LargeObject に対し
> て bytea を用いるその2.案が5倍遅い理由がよく分かりませんでした。この
> 理由について心当たりのある方いらっしゃいますか?(マニアックな話で恐縮で
> す…。)
> 
> それではまた。

JDBCのことは良く分かりませんが,ちょっとした実験をしてみたので興味のあ
る方はどうぞ.

そもそも,OIDの生成自体にロックをかけることができれば,pg_databaseをロッ
クするなどのトリックは必要なくなります.そこで,OIDを生成している関数
GetNewObjectId(void)を調べたところ,

	LWLockAcquire(OidGenLock, LW_EXCLUSIVE);

というのがあって,OIDのロックはOidGenLockを使っていることが判明.そこ
で

	LWLockAcquire(OidGenLock, LW_EXCLUSIVE);
	LWLockRelease(OidGenLock);

を呼び出すユーザ定義関数oidlock(下記参照)を作って実行してみたところ
([1][2]は別々のセッションを示します),

[1]BEGIN;
[1]SELECT oidlock(true);
[2]INSERT INTO foo VALUES(1); <---ここでブロック
[1]SELECT oidlock(false); <-- ここで[2]のブロックが解消

となり,うまくいったと喜んだのもつかの間,すぐにボロが出ました:-)

1) SELECT oidlock(false)を呼び出さずに[1]のセッションでEND;としても,
   ロックが外れない(ABORTならば外れる).

2) 同じセッションの中で,
  SELECT oidlock(true);
  INSERT INTO foo VALUES(1)
  を実行すると,そのセッションは完全に固まり,kill -9でバックエンドプロ
  セスを叩ききるしかなくなってしまう.
-----

というわけで,単純にLWLockAcquire/LWLockReleaseを使うのでは駄目で,テー
ブルロックや行ロックでやっているようなロック管理に組み込まないと駄目な
ようです.

話は変りますが,ラージオブジェクトの識別子がOIDである必然性はなく,デー
タベースの中でユニークな識別子ならなんでもよいのですね.また,その方が
クリティカル資源であるOIDをロックする必要がなくなるので良いと思います.

今後は,この2点を解決する方法を考えて,うまくいけばcurrentにコミットで
きるようにしたいと思います.

--------------------------------------------------------------
Datum
oidlock(PG_FUNCTION_ARGS)
{
	bool lock;

	lock = PG_GETARG_BOOL(0);

	if (lock)
		LWLockAcquire(OidGenLock, LW_EXCLUSIVE);
	else
		LWLockRelease(OidGenLock);

	PG_RETURN_BOOL(lock);
}



pgsql-jp メーリングリストの案内