以下の情報を元に Galera
と GR
のレプリケーションの違いをまとめたもの。
- https://www.percona.com/blog/2017/02/24/battle-for-synchronous-replication-in-mysql-galera-vs-group-replication/
- http://lefred.be/content/group-replication-vs-galera/
- https://www.slideshare.net/StevenLi6/open-stack-ha-designampdeployment-kilo
- https://www.slideshare.net/Grypyrg/percona-xtradb-cluster-vs-galera-cluster-vs-mysql-group-replication
- http://mysqlhighavailability.com/performance-evaluation-mysql-5-7-group-replication/
- http://mysqlhighavailability.com/mysql-group-replication-a-quick-start-guide/
- http://mysqlhighavailability.com/mysqlha/gr/doc/background.html
- https://www.slideshare.net/satoshimitani71/mysql-group-replication-mysql-casual-talk-vol10
Galera Replication
- active/activeのマルチmaster方式。3rd partyが考案。
- mysql-5.5から利用可能。(実装: Codership, MariaDB, Percona)
Group Replication (GR)
- active/activeのマルチmaster方式。(parimaryとread-onlyのグループ構成)
- mysql-5.7から公式のプラグインとして利用可能。(正式リリースは5.7.17から)
- 既存の実績ある技術をベースに作成。(GTID, Multi-Source Replication, Multi-threaded Slave Applier, Binary Logs)
(理論)
- いずれも「The Database State Machine Approach (1999)」に基づいている。
- データへの更新を「writeトランザクション」として表現する。(行単位のバイナリログを利用)
- write set単位の処理によってレプリケーションを実現する。(write set replication = wsrep)
(構成)
- active/activeのマルチmaster方式。
- writeは複数のノードに対して行われ、全ノードが全データを持つ。
- readは自分のノード(ローカル)に対してのみ発生する。
- 外部のF/O機能が不要で、ノードの追加と削除が自動で可能。
- データの一貫性を持つ。
- 楽観的ロックを利用。(同時writeは最初のコミットが勝つ)
(制約)
- InnoDB でのみ動く。(※恐らく、トランザクションが必須のため)
- テーブル内のデータは主キーが必須。(※恐らく、行単位での変更として処理するため)
- GTID が必須。
- 1ノードに対してのみwriteするのが最も効率的。
- Group : クラスタのこと
- Member : ノードのこと
- Multi-Master
- 全ノードが対等で、常に同じデータを持つ。(データ的なF/O処理が発生しない。slave遅延も存在しない)
- どのノードに対してもread/writeができる。(とは言え、ノードを跨いだ排他ロックはデッドロックになる)
- 台数を増やしても、データ冗長性が上がるだけでwrite性能はスケールはしない。(むしろwriteは遅くなる)
- Multi-Master(primaryとread-onlyで構成)
- デフォルトでは1ノードだけがprimaryに昇格する。
- その他のノードは自動的に read-only になる。
- primary はクラスタ内での自動投票で決まる。
- primary にのみ書き込みができ、read-onlyノードに対してwriteした場合、以下のエラーが出る。
ERROR 1290 (HY000): The MySQL server is running with the --super-read-only option so it cannot execute this statement
- 台数を増やしても、primary が1つのためwrite性能はスケールしない。
- primaryを複数作ることはできるが、お互いを同期させる必要があるため結局writeはスケールしない。
- また、複数primaryでは、スキーマ変更やデータ更新時の競合問題が発生する。(そのためデフォルトでsingle primaryモードが推奨されている)
- クライアントが どこかのノード にwriteする
- 受け取ったノードは、その writeトランザクションを全ノードに配送する
- 全ノード から同意を得た後に、クライアントに成功を返す
- クライアントが primaryノード にwriteする
- 受け取ったノードは、その writeトランザクションを全ノードに配送する
- 大多数のノード から同意を得た後に、クライアントに成功を返す
例として、3ノードのクラスタで、1つのノードが以下のどちらかの状態になり、障害が発生したとする。
- 落ちた
- ネットワーク的に見えなくなった
- writeの適用は一旦pendingされる(TODO: クライアントへの返答は?)
- 「ノードが復活」or「ノードが切り離された(timeoutにて)」によってwriteが適用される
(masterが生存している場合)
- 残り2ノードでwriteを受け付ける
- 障害ノードがクラスタから切り離されなくても、自動的に2ノード体制で動きだす
(masterが見えなくなった場合)
- 障害ノード(master)は少数派のため、writeの受け入れをやめる
- 障害ノードがクラスタから切り離された後、残り2ノードの中で自動的に新masterを選出する(TODO: 切り離しは自動?)
- writeトランザクションは全ノードへの到達が保証されるため、可能性は極めて低い
- Percona XtraDB Clusterの場合、凄く運が悪いとロストする(TODO: Percona以外では?)
- 例: split brain発生→クラスタ再構築→新しいwriteを受ける→新クラスタが死ぬ
- ack直後に、大多数ノード群が全滅した場合、そのwriteデータは少数ノードには未配送のためロストする
- FreeBSD, Linux
- (Percona) Linuxのみ
- Linux, Windows, Solaris, OSX, FreeBSD
- InnoDBのみ
- primary keyが必須
- Percona XtraDB Cluster 5.7からは制約違反はテーブルの宣言時にエラーが出るようになった(それ以前は実行時エラー)
- テーブルロックができない
- write先は1箇所がよい。(複数ノードを跨いで排他ロックするとデッドロックする)
- 自前のGTIDの仕組みを持つ(MySQLのGTIDとは別物)
- MySQLのGTIDを有効にした場合、競合はしないが内部に2つのGTIDができることになる
- MySQLのGTID(5.6)を利用する
- クラスタメンバー毎に割当範囲を持つ(デフォルト:1M)
- 必要事項と制限: https://dev.mysql.com/doc/refman/5.7/en/group-replication-requirements-and-limitations.html
どちらも、「低レイテンシかつ高帯域」を前提にしている。
- WAN利用可。
- 「ノード間は地理的に近く、低レイテンシかつ高帯域が必要」とだけ公式発表されている。(※恐らく、WAN未対応) - https://dev.mysql.com/doc/refman/5.7/en/group-replication-frequently-asked-questions.html
- とは言え、MySQLの「メッセージ圧縮機能」で何とかできるかもしれない
- joiner : クラスタに参加しようとするノード
- doner : joinerにデータを提供するノード
転送方式にはISTとSSTの2つがある。(Gcacheの大きさによって自動的に決定される)
- IST: インクリメンタル転送(差分転送) # Incremental State Transfer
- 短期間だけクラスタから離れていたノードの復旧に便利。(writeキャッシュがノードにある場合のみ可能)
- SST: フル転送。 # State Snapshot Transfer
- 新規ノード追加時に便利。自動でsnapshotを作成して全データを入れてくれる。 - donerの負荷が大きいため、参照を受けない(or 優先度おとす)ような対応をすることが望ましい。(TODO:自動復旧の場合の対策は?)
- GTIDベースのバイナリログを利用。
- 全ログがノードにない場合、新規ノードの追加には別途snapshot(初期データ)が必要。(TODO: コールドバックアップから復旧?)
バイナリログが不足している場合はこういうエラーが出る。
2017-01-16T23:01:40.517372Z 50 [ERROR] Slave I/O for channel 'group_replication_recovery': Got fatal error 1236 from master when reading data from binary log: 'The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION = 1, but the master has purged binary logs containing GTIDs that the slave requires.', Error_code: 1236
- 全ログがある場合でも、復旧時は先にノードにデータを入れてからクラスタに追加することが推奨されている。(公式)
クラスタにあるノードをjoinさせる時、データ転送が失敗した場合のそのノードの挙動。
- 最初の失敗で諦め、自身のmysqldを停止させる。
- 別のdonerに対してリトライする
- 同じdoner に対しても
group_replication_recovery_retry_count
を超えるまではリトライし続ける - 最大リトライ(デフォルト: 10)の結果失敗した場合、mysqldは生き続けて、クライアントのリクエストを受けてしまう。(バグ?)
- クラスタ台数に応じて自動で即座に調整される。(以下、3ノードの場合)
auto_increment_increment
= 3auto_increment_offset
= 1~3(各ノードに依存)
wsrep_auto_increment_control
でこの機能を無効にできる。(値を手動設定できる)
- クラスタ台数と無関係に7で固定。
auto_increment_increment
= 7auto_increment_offset
= 1~7(各ノードに依存)- http://mysqlhighavailability.com/mysql-group-replication-auto-increment-configuration-handling/
- primaryが1台の場合でも7になりID空間を浪費するため、運用時には以下を正しく設定すること。
group_replication_auto_increment_increment
(TODO: 1にすればいい?)
- 同じDBのテーブルで並列レプリケーションするスレッド数を指定できる。(version 5.5から対応)
wsrep_slave_threads
で設定。(デフォルト: 1)- いつでも変更可能(減らした場合、既存の実行中のスレッドには影響を与えない)
- https://www.percona.com/doc/percona-xtradb-cluster/5.6/wsrep-system-index.html#wsrep_slave_threads
- 行ロックの衝突は潜在的にありえるがマルチスレッドで実行される。
- レプリケーショントランザクションを並列実行するslave用スレッド数を指定可能。(5.7から)
slave_parallel_workers
で設定。(デフォルト: 0 = 無効)- 0だと並列が無効になるため、1スレッドのみで実行される。
- 変更はいつでも可能。(即時性はなく、
START_SLAVE
に影響) - https://dev.mysql.com/doc/refman/5.7/en/replication-options-slave.html#sysvar_slave_parallel_workers
LOGICAL_CLOCK
スケジューラによる write 群の依存性を考慮した並列実行を行う。- 利用モードによっては、さらに効率的な実行が可能。
- http://mysqlhighavailability.com/zooming-in-on-group-replication-performance/
レプリケーションが遅いノードが出てきた時の挙動。
- 上限値に達した場合、クラスタの全writeがブロックされる。
gcs.fc_limit
で上限値を指定。(デフォルト: 16)- 自動的に値が増加する(上限: 3ノード時は28まで)
- https://www.percona.com/doc/percona-xtradb-cluster/5.7/wsrep-provider-index.html#gcs.fc_limit
- 各ノードが他のノードごとに統計値を持ち、上限値に達した場合、そのノードにだけwrite速度を弱める。
group_replication_flow_control_applier_threshold
で上限値を設定。(デフォルト: 25000)- トランザクションキューの受け入れ上限数。
- 変更はいつでも可能。(GRのリセットは不要)
- https://dev.mysql.com/doc/refman/5.7/en/group-replication-options.html#sysvar_group_replication_flow_control_applier_threshold
クラスタ内のノード間でネットワーク疎通が取れなくなった場合の挙動。
(多数派のノード)
- 新しいノードが接続してくるのを待つ。(TODO: その間はクライアントのリクエストをサーブする?blockする?)
(少数派のノード)
- クラスタに復帰するまではread/writeともにサーブしない。(設定で変更可能)
- ネットワーク復旧後は、データの同期とクラスタへの復帰が自動的に行われる。
(多数派のノード)
- クライアントのリクエストをそのまま処理する。
(少数派のノード)
- クライアントからのreadを受け付ける(dirty readが発生)
- クライアントからのwriteを受け付ける(が、そのまま刺さる)
- 切断後に再接続しても、自動的にクラスタへは復帰しない。
- Totem single-ring ordering and membership protocol を利用。
- HAクラスタの通信層制御を行うフリーソフトである Corosync で利用されている通信プロトコル。
- tokenリング型なのでノード数が増えるとオーバーヘッドが大きくなる。
- ノード数が3→9に増えると、write性能が1/4に落ち込む。
- Paxosアルゴリズム に基づいた XCOM を考案。(eXtended COMmunications)
- 全順序配送 : 全メンバー(ノード)にメッセージ(write)が同じ順序で配送されることを保証できる。
- 動的管理 : 所属メンバーの動的な変更を管理できる。 - 異常検知 : 異常メンバーと生存メンバーを決定できる。
- http://mysqlhighavailability.com/the-king-is-dead-long-live-the-king-our-homegrown-paxos-based-consensus/
- XCOM はコードネームで、MySQL的なモジュール名称は GCS。
- group-replication-0.5まではCorosyncを利用していたが、0.6からXCOMになった。
- Corosyncを辞めた理由 : Windows等で動かない。可逆暗号なのでセキュリティが心配。クラウド面でUDPよりTCPがいい。
- http://mysqlhighavailability.com/order-from-chaos-member-coordination-in-group-replication/
- ネットワーク通信が最適化されているため、こちらの方がパフォーマンスがよい。
- peer-to-peer型なのでノード数が増加してもコミット時間が一定。(Galeraは線形的にコミット時間が増える)
- ノード数が3→9に増えても、write性能は5~10%程度の劣化に収まる。
- status変数のみ利用。
- ノード内からクラスタ情報を参照できない。(他のノード情報やそのステータスが分からない)
- 但し、Perconaはperformance_schemaを利用している。(mysql-5.7から)
- Performance Schema を利用。
- このテーブルにクラスタ内の各ノードの情報が全て入っている。(ノードのメタ情報や状態)
- 通常のテーブルとして参照できるので、外部アプリからノードの死活監視などにも利用できる。
スキーマ定義DDLを実行したときの挙動。
2種類の更新方法がある。cnfまたはクライアントの接続時設定によって選択できる。
- Total Order Isolation (TOI) (デフォルト) - 他のトランザクションを全てブロックし、全サーバで一貫性を持って更新する。 - 可用性 < 一貫性
- Rolling Schema Upgrade (RSU) - 指定サーバをクラスタから切り離し更新する。レプリケーションは行われない。 - 可用性 > 一貫性
RSUではノード間のデータおよびスキーマの一貫性が崩れ、クラスタ全体の可用性が損なわれる可能性があるため、TOIが推奨されている。
- writeはブロックされない。(通常のInnoDBに同じ)
機能 | Galera | GR | (補足) |
---|---|---|---|
構成 | Multi-Master | Multi-Master | GR:Primary-ReadOnly |
台数 | 3~9台 | 3~9台 | |
OS | FreeBSD, Linux | 色々 | GR:Linux, Windows, Solaris, OSX, FreeBSD |
WAN利用 | ◯ | × | |
GTID | 独自GTID | MySQL(5.6) | |
コンセンサス | TSO | XCOM | TSO = Totem Single-ring Ordering |
write ack | 全ノード | 過半数 | |
同期用データ | Gcache | binlog | |
write 識別 | seqno | GTID | |
write ノード数 | 1 | 1 | Galera:デッドロックするので1 |
readの可用性 | 高い | 高い | |
writeの可用性 | 低い | 高い | Galera:1台落ちると刺さる |
readスケール | する | する | |
writeスケール | しない | しない | GR:将来的にグループを増やせる? |
データロスト | 極めて低い | 低い | |
slave遅延 | なし | ? | |
dirty read | なし | あり | |
split brain抑制 | ◯ | △ | GR: 少数派ではdirty readが発生 |
split brain復旧 | ◯(自動) | ×(手動) | GR: 少数派は手動でクラスタに戻す必要がある |
writeフロー制御 | 全台停止 | 速度緩和 | GR: ノード別に速度を変更する |
隔離レベル | 設定可能 | Read-Commited | Galera: SERIALIZABLEは非推奨(同一ノードでのみ動く) |
スキーマ変更 | writeブロック | ブロックなし | Galera: TOIの場合 |
write性能 | 遅(10~30%) | 中(60~84%) | 「非同期レプリケーション」との比較 |
repl性能 | 中 | 速い | |
repl動作 | ブラックボックス | 驚き最小 | |
同時write競合 | 楽観的ロック | 楽観的ロック | |
F/O処理 | 不要 | 自動 | GR: MySQL Routerも自動 |
ノード切り離し | 自動(timeout) | ? | |
ノード自動復旧 | 自動 | ×(バグあり?) | |
ノード新規追加 | 自動repl | 手動 | GR: 無停止はかなり面倒 |
自動リカバリ(差分) | ◯(IST) | △(binlogのみ) | |
自動リカバリ(全部) | ◯(SST) | × | |
LB | × | ◯(Router) | Galera: HAProxyなどが別途必要。Perconaはある |
モニタリング | △ | ◯ | Galera: wsrep系のstatus参照のみ可能 |
多機能 | 豊富 | 中 | |
実績 | 数年間 | なし | |
安定性(現時点) | ◯ | × | GR: split brain周りの不具合が多い |