トリアエズぶろぐ
フリーランスでやってる77世代のWEBプログラマが福岡からのんびりとお送りいたします。
<< 最高の行動をとれた時の心理状態 | main | PostgreSQLをクラスタリングで負荷分散で試行錯誤でorz その4 >>
PostgreSQLをクラスタリングで負荷分散で試行錯誤でorz その3


何処からか天の声が聞こえてきたので書いてみます。

前回までの記事
PostgreSQLをクラスタリングで負荷分散で試行錯誤でorz
PostgreSQLをクラスタリングで(ry その2

前回の記事でpgpoolに落ち着いた…と思わせといて、実は断念していました。
理由は、どうしてもSERIAL型の整合がつかなかった為。
レプリケーションの都合上、同じ問合せで違う結果を返すもの(RANDOM()、シーケンス(SERIAL)、CURRENT_TIMESTAMPなど)はレプリケーションできない。

例えば、テーブル[users]にSERIAL型のフィールド[id]があった場合、
DB1
 INSERT INTO users (name) VALUES ('hoge');
DB2
 INSERT INTO users (name) VALUES ('hoge');
と同じクエリを送信しても、クエリの問合せが混雑しているときには
DB1
 id => 10,
 name => 'hoge'
DB2
 id => 11,
 name => 'hoge'
となってしまう可能性がある。これじゃまずい。信用できないDBなんて意味が無い。

しかしながら、pgpoolの設定には insert_lock というディレクティブがあり、INSERTのクエリを実行する際にテーブルをロックし、SERIAL型の数値の整合性をつけましょう、という便利なものがある。INSERTクエリの際には自動で
BEGIN;
LOCK TABLE foo;
INSERT INTO foo ・・・
END;
としてくれるのだ。(たぶん)

これで一安心!と、タカをくくっていたのだが、運用後1時間が経つ前に
kind mismatch between backendsserver closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
というエラーで縮退運転に。
見てみると、
DB1
 id => 10,
 user_id => 100,
 blog => 'hoge'
DB2
 id => 11,
 user_id => 100,
 blog => 'hoge'
こんな状態に。
ひぃぃぃぃ…整合性取れてないYO. こんなのレプリケーションじゃなーい!!

こういう場合、pgpoolのMLに報告とかするべきなんだろうけど、ちょっぴりしり込みして入会していません。ごめんなさい。
こうなったら、アプリ側でなんとか整合とれるようにするしか!と思い、ちょっとバッドノウハウ的な実装をしてみました。

pgpoolでDB1とDB2をレプリケーションしているという前提です。
INSERTの際、pgpoolではなく直接DB1にシーケンス値をもらい、SERIALのフィールドに直接入れ込んでしまおうというもの。折角SERIALでテーブル組んでるので、そのまま利用しようという目論見です。
※コードは適当、見易さ重視。
// DB1から直接シーケンス受け取り
$ID = SELECT NEXTVAL('blog_id_seq');
// ダミーでDB2からも直接シーケンスを受け取る
$dummy = SELECT NEXTVAL('blog_id_seq');
// pgpoolにINSERTを投げる
pg_query("INSERT INTO blog (id, comment) VALUES ({$ID}, 'hoge')");
これでどうよ。
これだと、シーケンスから確実にユニークなIDを取りつつ、間違いなく同じIDでINSERTしてます。っていうか原始的な方法に戻っただけなんだけど。
おっと?これだとinsert_lockディレクティブ要らないんじゃない?わざわざlockして性能を落とさなくてもいいんでない?と思ったので思い切って
insert_lock = false
に。これで様子見。

1時間後…
kind mismatch between backendsserver closed the connection unexpectedly・・・
ってまたかよ!

見てみるとユーザのブログリストをSELECTしている時にDB1のリストとDB2のリストが食い違っている。といっても以前のような同じエントリでIDが違うのではなく、DB1/DB2それぞれに違うエントリが入っている・・・。そうか、insert_lockしていないからINSERTがどちらかで終わり、どちらかで終わらない状態の時にSELECTされて違うリストを出してしまったのか・・・と、勝手に推測。

んー、やっぱり自分のようなヘナチョコプログラマにはレプリケーションの敷居は高いのか・・・。現在 insert_lock = true に戻して様子を見ているところです。

つづく。かも。



■番外編
はてなの真似して、DBまるごとRAMディスクに突っ込んでpgbenchやってみました。結果、130%くらいの性能アップを確認しましたYo.
元々メモリが1Gしかなくてそのまま継続運用はできなかったのですが、将来pgpool-IIを導入して3台以上のレプリケーション&負荷分散するときには、まるごとメモリDBを1台入れてボトルネックを極力なくした状態でほとんどのSELECTをそいつに投げてもいいんじゃないかな、と思いました。


■追記 2006/11/13
どうやら DB1・DB2 と同じINSERTクエリが行くときに、どちらかの処理が追いつかずにTIMEOUTしてDB1だけにレコードがあったりDB2だけにレコードがあったりして整合つかずに縮退運転に切り替わっていることも原因にあるようだ。
DB1:INSERT OK
DB2:TIMEOUT
の時に、自動でpgpoolが
DB1のINSERTを取り消したりしてくれると嬉しいんだけどそういう機能はないだろうなぁ。
解決or回避策は、pgpoolIIにしてDB台数を増やして、1台の負荷ができるだけ少なくなるようにするとか、そもそも自アプリケーション側に耐障害の仕組みを+αするとか。

■追記 2006/11/18
いわゆる
http://ml.postgresql.jp/pipermail/pgsql-jp/2005-April/018836.html
の問題かな、と思った。やはり自アプリ側でもケアが必要とのこと。。。
スポンサーサイト


akihikoさん、お久しぶりです。
はてな並にメモリをファイルシステムマウントしてパフォーマンスアップとは流石です!!

ちなみに僕はDB2(UDB)を使った差分レプリケーションのDRorpRというのを普段仕事で使ったりしています(^-^)
しまんと | 2006/11/09 06:22
>しまんとさま
ご無沙汰です!
DRorpRはわからないですー。ググッても???
宜しければ、参考になるサイトとかないですか???
akihiro | 2006/11/09 10:32
すみません!!
つづりまちがってました(>_<)
(疲れていたのでしょう・・)
正しくは DPropR です(^O^)

http://www-06.ibm.com/jp/software/data/developer/library/techdoc/dpropr.html
しまんと | 2006/11/14 08:19
COMMENT









Trackback URL
http://akihiro.jugem.jp/trackback/134
TRACKBACK
 SQLServer/PostgreSQL(test)で動作可能なシステムで、PostgreSQLの分散化(parallel)を調査/テストしていたのですが、昨日のレビューで、全店参照だけでもパラレル構成にしたかったのですが、色々厳しい突っ込みを頂き、parallelモードの採用は見送ることとなりました