Postgres-XC gtmの多重化(改訂2版:2011.01.01)
Postgres-XCの単一障害点であるgtmを多重化するmgtmを作ったので公表する。
2010年12月現在、最新版はver0.9.3だが、mgtmはver0.9.2で動作する。
なお、機能には大きな制約がある:
- 基本的にver0.9.2でpgbenchを実行することを目標とし、シーケンスなどはサポートしていない。
- coordinatorやdatanodeには"autovacuum=off"を設定しなければならない。
(2016.2.11) 多重化gtmが動くVagrant boxを作成して公開:
[Vagrant box|
GitHub]
したので、興味があればどうぞ。
多重化の分類
便宜上、以下のように分類する。
- プロセス多重化: メッセージの相互通信
- Active Replication(能動的多重化): Multi-Master
仮実装のみ行った
- Passive Replication(受動的多重化): Master-Slave
N重化、2重化の実装を行った
- データ多重化:メモリ上のデータを共有
今回は見送り。
概念
概念図を以下に示す。
gtmの多重化の概念図
|
GTM間通信
一般的な方法
プロセス間で同期メッセージの交換によるデータの一貫性を確保する場合の、一般的な通信方法は以下のとおりである。
- Active Replication(能動的多重化)
Total Order Broadcast(Atomic Broadcastともいう)
- Passive Replication(受動的多重化)
View Synchronous Communicationなど
これらのプロトコルは一般的に重い。
手元ですぐに使えるこれらプロトコルをサポートするライブラリはコンセンサス問題を複数回(3、4回)解かなければならず、処理が非常に重い。
具体的に書くと、コンセンサス問題は一回の実行でメッセージ交換(ラウンドという)を3回程度繰りかえす。
それを複数回(3、4回)解くということはGTM間で9回から12回のメッセージ交換(9〜12ラウンド)を行って、
やっと1つのメッセージがGTM間で共有されるということである。
もちろん、コンセンサス問題を(明示的には)利用しないTotal Order Broadcastプロトコルも存在するが、
3ラウンドのメッセージ交換が必要になる。
今回試した方法
Passive Replication(受動的多重化)に限るが、以下に示すプロトコルを使った。
- N重化: Causal Order Broadcast (あるGTMが送信したメッセージについて因果関係を保持する通信)
原理的にはCausal Orderよりも制約の緩いFIFO Orderで大丈夫だが手元のライブラリはCausal Orderしかなかったのでこちらを使った。
- 2重化: Lazy Reliable Broadcast
こちらもFIFO Orderでよいのだが、よりパフォーマンスのよいこちらを使った。
これらのプロトコルは、2回程度のメッセージ交換(2ラウンド程度)で、1つのメッセージがGTM間で共有できる。
軽いプロトコルを使った代わりに、mgtmとmgtm_proxyで障害リカバリ機構を組み込んだ。
リカバリ機構とは以下のようなものである:
- GTM側:
正常なGTM間でだけ"同一状態を保てば良い"
- Proxy側:
GTMに届かなかったメッセージを再送する機構を持つ
今回、Active Replication(能動的多重化)については、効率のよいTotal Order Broadcastを実装/用意できなかった。理由は次の項に示す。
実装
実装はJavaでフルスクラッチした。Java版のgtmとgtm_proxyで公開したものを
コアにしている。
また、 GTM間の通信ライブラリは
Introduction to Reliable and Secure Distributed Programmingの教育用ライブラリ:
を使った。教育用なのでメモリリークやメッセージロストがある。実用的なものでなく、あくまで検証用のライブラリである。
JGroupを使わなかったのは、(1)所詮試作なので性能を求めなかったこと、
(2)問題がある場合にAppiaのほうがソースコードの総量が少ないので対処可能と考えた、(3)マニュアルにTotal Order Broadcastの記述がないこと、(4)(まったく別の理由から)フルスクラッチを目指していたから。しかし、実際に実装してみるとappiaの(Total Order Broadcastの)数々の不具合に泣かされたので戦略ミスだったかもしれない。
デモンストレーション
Passive Replicationのデモンストレーション画像(29M)を用意した。
画像内でのgtm, coordinator1, coordinator2の配置
|
- 0:00 〜 0:18 : mgtm起動
mgtm-1, mgtm-2, mgtm-3を順次起動
- 0:19 〜 0:28 : リーダ選出
mgtm間でリーダ選出、mgtm-1がリーダ。mgtm-1の画面に"I'm Leader"と表示されている。
- 0:29 〜 0:47 : mgtm_proxy起動
- 0:48 〜 0:57 : coordnatorとdatanode起動
- 0:58 〜 1:13 : pgbench設定
データベースpgbenchを作成し、"pgbench -i"を実行
- 1:14 〜 2:23 : coordinatorデータ共有
pgxc_ddlでcoordinator間のデータを共有
- 2:24 〜 3:10 : pgbenchの実行
coordinator1,coordinator2でpgbenchを実行
- 3:11 〜 3:25 :mgtm-1をダウン
mgtm-1をダウンさせてみる。
mgtm_proxyはmgtm-1のダウンを検出し、2秒待つ。
その間にリーダ選出機構が起動し、mgtm-2が新しいリーダになる。
次いで、mgtm_proxyはmgtm-2に接続し、障害リカバリを行った後に処理を継続する。
最終的にpgbenchは破綻なく結果を返す。
- 3:26 〜 3:55 : 動作確認
リーダがmgtm-2になって動作確認の意味でpgbenchを実行。問題無く動作している。
- 3:56 〜 4:20 : mgtm-2をダウン
mgtm-2をダウンさせてみる。以降も問題無くmgtm-3にリーダが切り替わり、pgbenchは破綻なく動作を続ける。
ベンチマーク
QuadCoreマシン上にXenで3台の仮想サーバを用意し、pgbenchを実行した。比較のためにオリジナルのgtm、およびJava版のgtmも計測した。
結果の解釈であるが、HDDを全DBが共有しているなど、ベンチマーク結果そのものにはあまり意味がなく、mgtmやオリジナルとの比較でのみ意味がある数値である。
オリジナルgtmとJava版のgtmはまだまだ余裕がある(pgbenchでgtmに負荷をかけきれていない。DB側がボトルネックになっている可能性が高い)。
多重化版mgtmはオリジナルgtmの1/3以下の性能であり、mgtmがボトルネックになっていることは確実である。
ただし、試作の段階でこの性能なので許容範囲ではないかと考えている。
なお、Active Replication版はappiaライブラリが異常に遅く、計測不可能であった。代わりに測定環境を変えて
Total Order Broadcastを使った場合のデモ画像を用意した。Etherealでパケットをキャプチャした様子も含まれている。appiaライブラリが教育用である旨よく判ると思う。
|
|
|
|
|
|
オプション |
接続コスト |
mgtm (3重化) |
dgtm (2重化) |
jgtm (Java版) |
gtm (オリジナル) |
Min – Max |
Min – Max |
Min – Max |
Min – Max |
[-c 4 -T 10]x 1 |
含む |
53.34 – 78.61 |
78.52 – 94.99 |
136.54 – 236.03 |
147.28 – 188.12 |
含まない |
56.36 – 82.84 |
82.45 – 99.75 |
137.34 – 237.4 |
183.78 – 235.35 |
[-c 4 -T 10]x 2 |
含む |
20.9 – 28.2 |
12.73 – 15.29 |
57.23 – 79.41 |
55.37 – 114.62 |
含まない |
22.29 – 29.92 |
13.4 – 16.13 |
57.65 – 79.91 |
69.24 – 142.71 |
[-c 8 -T 10] x 2 |
含む |
18.68 – 44.65 |
11.54 – 26.66 |
79.4 – 110.16 |
55.14 – 88.43 |
含まない |
19.92 – 47.17 |
12.22 – 28.13 |
79.6 – 111.37 |
91.6 – 126.45 |
mgtm(多重化版)とjgtm(Java版)のプロファイルを示す。
mgtmは内部通信のための処理(PipedStream, WaitNotifyなど)が多くの時間を占めている。
jgtmのほとんどが入力待ち(SocketInputSteam.read())に費されていることと対照的である(この数値のみ大きいのが"jgtmには余裕がある"との根拠である)。
内部メッセージ処理の高速化など施す余裕があると思われるので、少なくとも2倍程度の性能向上は見込めると考えている。さらにパフォーマンスを高めるための方策も多々あると思う。
mgtm(多重化版)のプロファイル
CPU TIME (ms) BEGIN (total = 2088312)
rank self accum count trace method
1 20.65% 20.65% 102 310332 java.net.SocketInputStream.read
2 16.72% 37.37% 71 310215 java.net.PlainSocketImpl.accept
3 8.15% 45.51% 561 310755 java.net.SocketInputStream.read
4 7.95% 53.46% 1118 311286 java.io.PipedInputStream.read
5 7.91% 61.38% 10 304520 java.lang.Object.wait
6 7.91% 69.28% 3142 304530 java.lang.ref.ReferenceQueue.remove
7 7.35% 76.63% 506 306181 java.lang.Object.wait
8 7.31% 83.94% 1118 310836 java.io.PipedInputStream.read
9 7.21% 91.16% 5 306839 appia.TimerManager.goToSleep
10 3.47% 94.63% 2 310692 java.net.PlainSocketImpl.accept
11 3.08% 97.71% 1 310567 jp.interdb.jgtm.utils.WaitNotify.Wait
12 0.76% 98.47% 1108 311434 java.io.PipedInputStream.read
13 0.66% 99.13% 559 311284 jp.interdb.jgtm.utils.WaitNotify.Wait
14 0.04% 99.17% 559 311281 java.io.PipedOutputStream.flush
|
jgtm(Java版)のプロファイル
CPU TIME (ms) BEGIN (total = 129099)
rank self accum count trace method
1 79.79% 79.79% 4258 302409 java.net.SocketInputStream.read
2 3.62% 83.41% 2 302282 java.net.PlainSocketImpl.accept
3 1.00% 84.41% 3808 302584 jp.interdb.jgtm.gtm.GTM_Transactions.GTM_GetTransactionSnapshot
4 0.76% 85.17% 3808 302610 jp.interdb.jgtm.gtm.GTM.ProcessGetSnapshotCommandMulti
5 0.52% 85.69% 19376 302599 java.nio.Bits.putIntL
6 0.52% 86.21% 19165 302591 java.nio.Bits.putIntB
7 0.48% 86.69% 38541 302593 java.nio.HeapByteBuffer.putInt
8 0.46% 87.14% 7741 302559 jp.interdb.jgtm.gtm.GTM_Transactions.GTM_GXIDToHandle
9 0.43% 87.57% 22836 302440 java.nio.Bits.getIntB
10 0.40% 87.97% 45814 302557 java.util.AbstractList$Itr.next
11 0.39% 88.37% 4256 302517 jp.interdb.jgtm.gtm.GTM.command
12 0.34% 88.70% 131564 302486 java.nio.HeapByteBuffer._put
13 0.33% 89.03% 37022 302577 jp.interdb.jgtm.gtm.GTM_Transactions.GlobalTransactionIdPrecedes
14 0.32% 89.35% 126460 302438 java.nio.HeapByteBuffer._get
|
簡単なまとめ
- GTM多重化のフィージビリティスタディを行った
- Passive Replication方式での多重化で、現実的に利用可能な処理速度を有していることを確認した
- FIFO Order,もしくはCuasalOrderと障害リカバリの組み合わせでディペンダブルなGTM多重化を構築できた
- 未解決事項
- Active Replication方式は、適当な通信ライブラリを準備できず仮測定のみ。
- (上記のMessagePassingと異なる)データ共有方式は未検討
議論
高信頼通信の利用について
今回試作したシステムでは、GTM間でメッセージを送信する際に高信頼通信(Reliable Broadcast)を使った。
図。
通信に必要な性質は以下のとおり。
(1)高信頼通信
* 正常なGTM間で:
a)(eventuary)必ずメッセージが到達すること
b)1回のみ送信されること
c)勝手にメッセージをつくらないこと
(2)FIFOオーダー
あると非常に便利レベルは以下。
(3)GTM間でデータの一致を保証
(4)性能を出したいのなら、非同期
単純な
「もっと簡単に実装できるのでは?」という意見に対して、
高信頼通信を使う理由を説明する。
まず、典型的な自前Broadcastをみてみよう。
図
3台以上のGTM間でデータを一致させたいのだから、同期通信(ランデブー通信)でメッセージを送るのがよいだろう。
すると、
(1)スレーブの障害で、timeoutまで全体の処理が待たされる
(2)マスタの障害時、複数のスレーブ間でデータの不一致が生じる可能性がある。
これは2相コミットでの問題と同根である。
図(2相コミットの問題)。
Sync Rep Design
http://archives.postgresql.org/pgsql-hackers/2010-12/msg02484.php
Last-modified: 2012-10-28