T

Postgres-R

Twitter経由で知ったのだが、Postgres-Rというレプリケーション用の拡張がある。面白そうなので概要を読んだ。以下のように説明されている。

Postgres-R概要

Postgres-Rクラスタの構造

Postgres-Rは高速に相互接続された「何も共有しない」クラスタ群として設計されています。そのためギガビットイーサネットで接続されたAMD Opteronマシンのようなありふれたハードウェアで構築することができるクラスタになっています。Postgres-Rノード間の通信は全てレプリケーショングループを提供するグループコミュニケーションシステム(GCS)を介して行われます。ひとつのデータベースが各グループにレプリケーションされることになります。

グループに追加された最初のノードはまずレプリケーションを開始し、最初のデータを提供します。その後で他のノードがグループからのレプリケーションを受け付けることができるようになります。各ノードは稼動状態になる前にリカバリシステムにより初期化されます。リカバリ中にのみリカバリ提供側とリカバリを受ける側のノードとの違いを認識することができますが、通常は全てのノードは同じ内容です。

Postgres-Rノードのコンポーネント

Postgres-Rの主なコンポーネントはレプリケーションマネージャです。これはPostgres本体とは別に追加されたプロセスで、主にメッセージの調整を行い、グループコミュニケーションシステムやトランザクションを実行するバックエンドとの接続を調整します。

Postgresにはトランザクションを扱うバックエンドのプロセスがあります。それぞれのバックエンドは一度にひとつのトランザクションしか扱うことができません。リモートのノードからのトランザクションをリプレーするために、レプリケーションマネージャはレプリケーションマネージャの管理下にありクライアントとの接続はない「リモートバックエンド」というものを開始します。それに対して、ローカルのトランザクションを扱っているバックエンドのプロセスたちは「ローカルバックエンド」と呼ばれ、クライアントと直に接続されています。

レプリケーションされたトランザクションのライフサイクル

Read-onlyのトランザクションはローカルで扱われ、通常のPostgresの単一ノードの操作と何ら変わらないものとみなされます。トランザクションがデータを書き込むと(UPDATE、INSERTまたはDELETEなどのSQLコマンド)、新しいデータがすぐに書き込みセットとして集められます。ローカルバックエンドはトランザクションを処理し続け、クライアントからコミットリクエストを受け取るまで書き込みセットに変更を集め続けます。

クライアントにコミットを返す前に、ローカルバックエンドは書き込みセットをレプリケーションマネージャに送ります。レプリケーションマネージャは順に並べられたグループコミュニケーションシステムのチャンネルを使ってそれを順々に全ての他のノードに送信します。トランザクションを開始したローカルバックエンドは、書き込みセットが戻されたところでコミットできるようになります。自分でもリプレイするように書き込みセットを受け取った他のノードでは、レプリケーションマネージャがリモートバックエンドのプロセスを開始して書き込みセットをそれに引き渡します。するとリモートバックエンドは書き込みセットにあるデータからトランザクションを自分でもリプレイします。

衝突の扱い

Postgres-Rでは書き込みトランザクションはシリアライズされて元と同じ順序でレプリケーションクラスタを共有する各ノードに配信されます。これによりノード間の同期と一貫性が保証されます。トランザクションがあるノードに正常にコミットされたならば、他の全てのノードにも同じくコミットされます。そのため、他のノードの処理を待つことなくそれぞれのノードがフルスピードで稼動することが可能になります。ネットワークのトラヒックを可能な限り抑えるためにトランザクションの変更だけが転送されます。

この手法はシリアライズ可能なPostgresのトランザクションレベルで最大の効果を発揮します。READ COMMITTEDモードでは通常の単一ノードの運用時と同じトランザクションセマンティクスを提供するためにロックもレプリケーションされます。そのためレプリケーションするデータベースシステムのほとんどでボトルネックとなるであろうネットワークトラヒックの増大を引き起こします。

というわけで、実際の動作をさせてみないとなんともいえないが、面白そうな存在なのでちょくちょくチェックする。

Posted by on 7月 17, 2008 in PostgreSQL

Comments

  • aoshin より:

    Postgres-R使ってみてどうでしたか?僕は20081104のパッチと20081027のパッチを試しましたが、正常に動作しませんでした。ソースも見てみましたが、NULLアドレスにアクセスに行ってセグメンテーション違反で死んだり、無限ループ?で固まったり、自分なりに頑張りましたが動きません。。。まともに動くんだろうか?

  • y より:

    PostgreSQLのお勧めautoconfのバージョンが2.61なので手元のシステムに入ってないんですよね。

    とりあえず手順としては

    $ cvs -d :pserver:anoncvs@anoncvs.postgresql.org:/projects/cvsroot login
    * パスフレーズは「postgresql」
    $ cvs -z3 -d :pserver:anoncvs@anoncvs.postgresql.org:/projects/cvsroot co -P pgsql
    $ cd pgsql/
    $ cvs update -D 20081104
    $ wget http://www.postgres-r.org/downloads/postgres-r-20081104.diff.bz2
    $ bunzip2 postgres-r-20081104.diff.bz2
    $ patch -p0 < postgres-r-20081104.diff と、ここまでやってautoconfがバージョン違いで $ autoconf configure.in:25: error: Autoconf version 2.61 is required. Untested combinations of 'autoconf' and PostgreSQL versions are not recommended. You can remove the check from 'configure.in' but it is then your responsibility whether the result works or not. configure.in:25: the top level autom4te: /usr/bin/m4 failed with exit status: 1 と。バージョンチェックを落としてもいいんですが、なんとなくいやなので休み中に やろうかな、という感じです。

  • aoshin より:

    コメントありがとうございます。僕もだいたい同様の手順でやりました。あと、20081104のパッチだと、コンパイル時にrecovery.cでエラーが出るかも知れません。ちょっと修正が必要です。あと、ecgsを動かすときにもエラーが出て、どっかにimport structを書き加える必要があると思います。で、さらに、セグメントフォールトするソースを見ると、プライマリキーが存在することを前提に書いてある部分があって、僕はプライマリキーが無いテーブルで実験したんですが、ここでプライマリキーの情報を取ろうとして存在しないアドレスにアクセスしてセグメント違反しているみたいです。ようするに、全然使えなさそうなレベルのように感じました。悪いところを見つけて直してね、というつもりでリリースしたのでしょうか。

    pgclusterを触ってみようかな、、、と思っています。

  • y より:

    ちなみにpgclusterは以前動かしてみて苦労した記憶があります。レプリケーションが必要な場合は業務だといつもpgpoolですね。あと、書き込みの頻度が少なくて多少遅くてもいい場合はDRDBです。

    http://selfkleptomaniac.org/archives/16
    http://selfkleptomaniac.org/archives/511

    ただ、pgpoolもレプリケーションが切れる問題でハマると大変なので苦労が絶えません。。。

  • aoshin より:

    コメントはやっ。pgpoolですか、使っているのはIですか、IIですか?
    僕はpgpoolは触った事が無いのですが、HPの説明を読むと、次の問題が存在すると思っています。
    問題(1)更新コンフリクト時に同期が崩れる可能性がある。つまり、ほぼ同時に複数の更新クエリが同一タプルを更新すると、複数のデータベース間で異なる結果になってしまうことがある。
    問題(2)グローバルデッドロックが起きる可能性がある。つまり、複数のデータベースをまたがったデッドロックですね。これは検出が不可能なので、避けるしくみが欲しいですね。

    これらの問題はどう解決していますか?僕はPosrgres-Rの原論文を読んだところ、これらの問題を解決しているので使えるかも?と思った次第です。
    pgclusterもだめそうですか。。。

  • y より:

    今はpgpool-IIを使ってますが、更新コンフリクトを防ぐためにinsert lockという設定があって、INSERT処理の最中には当該テーブルへのロックがかかります。シンプルでなかなか素敵な解決方法ではあります。また、レプリケーションが壊れたことは、どうやらSELECTの処理時に何かのタイミングでレプリケーションされたデータベースサーバから戻る行数が異なるのを検知して判定しているようです。つまり、データの内容のズレについては保障されていないわけです。まあ、そのあたりは最終的には逐次処理されてテーブルロックで制御しているということなのでしょう。

    最新のバージョンだとserial型のカラムを含むテーブルの場合自動的に判断して整合性を保つようになったようです。

    それからpgpoolの開発チームの方はデッドロックの問題が得意みたいで、pgClusterの中の人もpgpoolの人からそのあたりのコードをもらったと話されてました。確かにpgpoolのデッドロックは今までお目にかかったことはないですね。もっとも、複数の分散されたサーバ上で同じ複数のデータベースに接続するpgpoolを動かすことで絶望的なデッドロックを発生させることはできました。

  • aoshin より:

    pgpoolの基本動作は、クライアントからのリクエストをpgpoolサーバが受け付けて、そのリクエストを複数のDBサーバへ同時に投げる、というのでよろしいですよね。ここが間違っていると大変。。。

    問題(1)
    INSERTの時はたいした問題だと思っていません。UPDATE、DELETEです。例えば、クライアントA、Bから同じタプルを更新するUPDATEがほぼ同時に発行されたとします。それぞれUPDATE x=10とUPDATE x=20だとします。pgpoolサーバは両方のUPDATEをほぼ同時に全てのDBサーバに投げます。DBサーバ1ではUPDATE x=10⇒UPDATE x=20の順で、DBサーバ2ではUPDATE x=20⇒UPDATE x=10の順で実行されたとします。READ COMMITTEDの場合、どちらのDBサーバでもUPDATEが成功しますが、最終的な結果はDBサーバ1では20、DBサーバ2では10となり、同期が崩れます。SERIARIZABLEの場合は後のUPDATEが失敗します。

    問題(2)
    クライアントA、BがそれぞれLOCK TABLE testtableをほぼ同時に投げたとします。pgpoolサーバは両方のLOCK文を全てのDBサーバへ投げます。DBサーバ1ではクライアントAのLOCKが実行され、DBサーバ2ではクライアントBのLOCKが実行されたとします。するとpgpoolサーバにはどのLOCK文に対しても全ての応答が返ってこないのでずっと待ち続けます。ここでデッドロック。

    間違ってますでしょうか?長文になってしまってすいません。。。

  • y より:

    pgpoolはバックエンドのPostgreSQLとクライアントの間でプロクシみたいに動作するっていう認識で合ってます。レプリケーションの他にロードバランシングとバックエンド側をクラスタDBとして扱うパラレルクエリというモードがあります。

    で、pgpoolはバックエンドのPostgreSQL(s)に対して同時にリクエストを送信しているわけではないです。レプリケーションの場合、マスタに投げた後で複数のスレーブに並列でクエリを発行しています。だからクエリの実行コストは基本的に2倍になります。

    (1)の場合、pgpoolには参照系のクエリもレプリケートするという設定オプションがあり、それによってトランザクションが複製されると考えれば、逐次性も保持されるので大丈夫そうな感じですが、実際そういった整合性が崩れるケースは経験したことがないですね。

    また、この場合Slony-Iを使った非同期レプリケーションを利用して、更新系クエリはマスタのPostgreSQLに投げて参照系はロードバランシングするという運用で対応もできます。が、マスタとスレーブの障害対応でマスタの切り替えやマルチマスタ構成にするとどうなるんだ、という頭の痛い問題が残されます。他にはDRDBを使ってデータを動機して、pgpoolによるロードバランシングを使うという手もありますが、更新系クエリのスピードは間違いなく低下します。こちらの場合keepalivedとかでDRDBのマスタとスレーブの切り替えまでできるようにすればなんとか運用できそうな気がしますが、実際に利用したことはないです。

    ええと、もっと調べてポインタを示すことができるといいのですが、実はこれから家族旅行なので(明け方に出発します)、続きは今度にさせてください。

    よい休日を!

  • aoshin より:

    お忙しいところ議論頂きましてありがとうございました。おかげさまで久しぶりに脳味噌が活性化しました。

    参照系のクエリもレプリケートするとどうして問題(1)が解決するのかわかりませんでしたが。たとえ解決しても、負荷分散できなくなるからDBサーバ増やしても性能が出ないという問題が発生しますねえ。。。

    隔離レベルはいつもREAD COMMITTEDを使ってらっしゃいますか?SERIALIZABLEだと、スナップショットまで同期する必要がありそうなので、より問題は複雑になりそうです。

    おお、我が家もミニ旅行でした。どうぞ、新型フルには気をつけて楽しんでください。

  • aoshin より:

    pgcluster試してみました。簡単なテストはOKですが、実プログラムを動かすと一応動きますが低負荷でも途中で落ちてしまいます。なぜか、
    WARNING: This query is not permitted without running replication serverという警告がたくさん出ます。。。pgreplicateも動いているし、PostgreSQL単体では問題ないクエリしか使っていないのに。
    以前pgclusterをお使いとのことですが、性能はいかがでしたか?
    DRDBとは名前も聞いたことが無いです。コメントから察するに、DRDBよりもpgpoolの方が性能が上ということですよね?
    pgclusterとpgpoolではどっちが上でしょうか?
    おっと、旅行の準備をしないと。。。

  • aoshin より:

    pgclusterの実験つづき。負荷が重くなると、pgreplicateが死ぬ様です。それを知らないクライアントはどんどんクエリを投げて、それを受け取った改造版postgresは「replication serverがいないよ」と警告を出すという訳でした。

    pgpoolは前記問題があるし。

    後は自分で作るしかないのかな。。。

  • y より:

    pgpool-IIからデフォルトでtrueになっていたためreplication_strictという設定があるのを失念していました。これはマスタの処理が完了してからバックエンドの処理を実行するという意味なのですが、これにより懸念されているような問題は回避出来るのではないかと思います。

    http://www2b.biglobe.ne.jp/~caco/pgpool/

    こちらの「レプリケーションとデッドロック」にちょこっと解説されています。pgpool-IIの場合、これがtrueになっています。

  • コメントを残す