[翻訳] "Tips on Converting to Galera"（Galeraに移行する際のポイント）

MariaDBのナレッジベースに、既存のDBから「Galera Cluster」に移行する際の注意点をまとめているページがあったので、翻訳してみました。
（そのうちGroup Replicationとの比較とかもやりたいなあ…）

※ 誤訳などがあったらごめんなさい。。
※ 自分で翻訳してみて、「Yakst」の方々の英語力凄いと痛感しました

【翻訳元】
"Tips on Converting to Galera"
https://mariadb.com/kb/en/mariadb/tips-on-converting-to-galera/

===== 以下、翻訳文 =====
### <font color="red">★ 結論（忙しい方向け）</font>

#### ■ データベース設計者向け
・ストレージエンジンはInnoDBのみ、全テーブルにPKを持たせること

#### ■ 開発者向け
・COMMIT後であっても、エラーチェックを行うこと
・トランザクションは適切なサイズに保つこと（巨大すぎるのはNG）
・AUTO_INCREMENTを「シーケンシャル」と決めてかからないこと（飛び飛びになる）
・"critical reads"（厳密なREAD処理、整合性が重要なSELECT）への対応方法が異なること
・Read/Write の振り分けは不要だが、将来的に基本構造が変化する可能性を考慮しておくこと

#### ■ DBA向け
・サーバ構築（ビルド）の方法は異なっています
・ALTER文の扱い方が異なっています
・TRIGGER や EVENT を使用する場合は注意が必要です
・レプリケーションの小技 (eg, BLACKHOLE) は使用できません
・いくつかのパラメータ変数の設定を変更する必要があります

### <font color="red">★ 詳細</font>

#### ◆ Galeraは様々なソフトで利用できます
Galera Clusterは現在、以下のソフトウェアで利用できます。
・Percona XtraDB Cluster
・MariaDB Enterprise Cluster (MariaDB 10.1 からはGaleraが標準バンドルされています)
・MySQL （手動でGaleraを組み込む）

#### ◆ cross-colo writing（複数拠点に跨るWRITE処理）の概要

※下記の内容は、同じデータセンタ内にDBが存在する場合でも該当します（課題はレイテンシーです）
※ colo = colocation ≒ datacenter（データセンター）と解釈しています

Garela における cross-colo （複数拠点に跨る処理）レイテンシーは、従来のレプリケーションのそれとは異なります。
しかし、だからといってGaleraの良し悪しを決めるものではありません。
Galeraのレイテンシーは従来のレプリケーション遅延とは異なるタイミングで発生する、つまり別物だからです。

【従来のレプリケーション】
・mysqlクライアントは、Masterとやり取りを行います。この時、クライアントとMasterが異なる拠点にある時、
  遅延が発生する可能性があります
・Masterに発行されるSQLによっても遅延は起こりえます（単純なCOMMITだとしても）。
・Slaveへのレプリケーションは非同期なので、クライアントの処理には影響をあたえません
・レプリケーションが非同期なため、クライアントからはスレーブのデータ整合性は保証されません。
  そのため、"critical read"（整合性が重要なSELECT）はマスタに発行しなければならない、という制限があります。

【Galeraレプリケーション】
・クライアントは、どのMaster（ノード）ともやり取りできますが、複数拠点の遅延は存在します。
  （クライアントとノードを同じサーバに同居させれば、遅延は回避できます）
・COMMITが発行された時、Galeraはその命令を全ノードに伝達します
・他ノードが同じ行を先に更新してしまった場合は、COMMITに失敗します
・COMMITの失敗はクライアントに伝えられます。その場合、クライアントはトランザクションを
  BEGIN（最初）からリトライすべきでしょう
・トランザクションが問題なく適用された場合は、他ノードにも伝播されます
・"critical read" に関しては後述

N-statement（複数ステートメント）を含むトランザクションの場合は以下の通りとなります。

【従来のレプリケーション】
・レイテンシーの程度は、0 （無し） か N （ステートメント数） に比例します。
  その有無は、クライアントとマスタが同一サーバかどうかで決まります
・レプリケーション遅延が "critical read" の制限に繋がってしまいます

【Galeraレプリケーション】
・クライアントがノードの近くにあれば、レイテンシーは 0 です
・COMMIT によって 1 レイテンシー発生します
・"critical read" の場合はレイテンシーは 0 です

まとめると、「クライアントがどこにあるか」「トランザクション（BEGIN...COMMIT）に含まれるクエリ数」により、
Galera は従来のMySQLレプリケーション（WAN環境）よりも "速く" も "遅く" もなる可能性があります。

#### ◆ WRITE処理のスケールアウト

従来のレプリケーション（特にSTATEMENT形式）の場合、全てのWRITE処理（更新クエリ）はマスタに対して
実行され、それが順序通りに全スレーブで適用されるという形式でした。
マルチマスタ構成を採用した場合でも、WRITEは全てマスタに向けなければいけません。

そのため、WRITE処理のスケールアウトは実質不可能であり、1台のマスタが処理できる限界を超えた
大量のWRITE処理がきても為す術ありませんでした。
（※データ種別によりマスタを分けるなどの物理的シャーディングを実施する場合を除く）

しかし、GaleraにはWRITE処理のスケールアウトを実現するための要素が揃っています。

・全てのノードにおいて、並列してWRITE処理を実行できます
・WRITE処理は"ROW形式"で他ノードに伝播します（ROW形式は単一行の操作より高速です）
・WRITE処理は、スレーブ（他ノード）では複数スレッドで適用されます（wsrep_slave_threads変数で設定）
・WRITE処理に付随するREAD（SELECT）も、全ノードで実行できます

なお、wsrep_slave_threadsの最適な設定値は環境によって異なります。設定値はCPUのコア数や
クライアントからの接続数などに依存してきます。基本的に設定値は大きければ大きいほど有効ですが、
もしあなたのシステムがI/Oバウンド（Diskがボトルネック）であれば、それがWRITEスケールアウトの
限界になるかもしれません。

#### ◆ AUTO_INCREMENT

wsrep_auto_increment_control = ON の時、auto_increment / auto_increment_offset の値は、
ノードが追加/削除される度に変動するようになります。

もしあなたが、Galera構築のファーストステップとして、既存環境（Non-Galera）のスレーブとして
1台構成のGalera Cluster を構築したとする。
その環境でAUTO_INCREMENTに依存した複数行をINSERTしたとすると、AUTO_INCREMENTカラム
の値に差異が生じるかもしれません（STATEMENT形式の場合）。
→ PXC5.6では修正済み

また、プロキシソフトの中には、「READ/WRITE分散」昨日のため、スレーブに LAST_INSERT_ID() を
送るものもあります。

※ なお、従来のような連続値となるAUTO_INCREMENTをGaleraで使用したい場合は、
　 wsrep_auto_increment_control = OFF にして下さい（ただし、WRITE処理は必ず1ノードに行う構成にする）
　 https://www.percona.com/forums/questions-discussions/percona-xtradb-cluster/11265-autoincrement-with-xtradb-cluster

#### ◆ InnoDB のみ

Galeraレプリケーションのため、InnoDBのみ使用できます。
そのため、MyISAMテーブルを使っている場合は、InnoDBに移行が必要となります。
（特に以下の機能を使っている場合は注意して下さい）

・フルテキストインデックス （全文検索） ※
・SPATIAL index （地理情報のインデックス） ※
・セカンドカラムとしてのPK
※ MySQL5.7からはInnoDBでも対応可

なお、他ノードへの同期がされないだけで、MyISAM も MEMORY も利用は可能です。
また、場合によっては、"START TRANSACTION READONLY"を使用すべきでしょう。

#### ◆ COMMIT後のチェック

COMMIT を発行した後は、エラーチェックを行って下さい。他ノードに更新がかかっている場合、
「デッドロック」が発生する可能性があります。

上記チェックを回避したい場合は、Galera環境を従来の「Master-Slave」構成と同じように扱ってください。
WRITE処理を1ノードのみで行うようにすれば、COMMITは常に成功するはずです。

「autocommit=1」を活用にしたい場合は、wsrep_retry_autocommit 変数を設定すると、エラーとなった時に
シングルステートメントをN回分リトライするようになります。
そのため、上記デッドロックの可能性を減らすことが出来ます。上記変数のデフォルト値は「1」ですが、
大抵の場合はこれで問題ないでしょう。

#### ◆ 常にPRIMARY KEYを持たせる

Galera は「ROW形式」のレプリケーションを使用するため、全てのテーブルに明示的なPKが必要となります。
レプリケーションの対象とならないテーブル（eg, MyISAM） は、PKを持たなくても構いません。

#### ◆ トランザクションの"サイズ"

※ この項目はGaleraノードが各拠点に分散していることを前提としています

様々な議論はありますが、更新クエリを一括してトランザクションにまとめてしまうことが推奨されます。
COMMIT もしくは autocommit 毎に1レイテンシーとなります。つまり、クエリをまとめてしまった方が、
レイテンシーを引き下げることが出来るのです。
一方で、巨大すぎるトランザクション、例えば1トランザクションで数百万行を更新するようなものを
使用するのは推奨されません。

上で述べたCOMMITのエラーに対応するため、不要なデータをいじることなくトランザクションを再実行
できるよう、コードを設計してください。例えば、"normalization"（標準化）のためのクエリをメインとなる
トランザクションから外出しするなどです。もし、メインとなるコードがロールバックされても、外出しした
処理までロールバックする必要はないはずです。
（もっとも、何が"正しい"かは、ビジネス/システム要件が規定するもので、机上通りにはいかないものですが…）

また、Galera のトランザクション分離レベルは、"SERIALIZABLE" と "REPEATABLE READ" の中間のレベルです。
そのため、tx_isolation変数は無視されます。

wsrep_log_conflicts 変数をONにすると、MySQLエラーログにエラーが出力されるようになります。

XA transaction （2層トランザクション）はサポートされていません。

#### ◆ "Critical reads"

最も「単純」（制限なしではない）な方法は、接続スレッドが異なっていても「read-after-write」を保証することです。
そうすれば、必ず更新後のデータにSELECT文を実行できるでしょう。

上記以外の場合は、このようにwsrep_sync_wait変数をセッション内でだけ変更します。

SET SESSION wsrep_sync_wait = 1; SELECT ... ; SET SESSION wsrep_sync_wait = 0;

SELECT文以外の場合は、最初のSET文で「1」以外の数値を指定してください。
（詳細は wsrep_sync_wait のマニュアルを確認してください）

※ Galera 3.6 以前の場合は、 SET SESSION wsrep_causal_reads = ON ; になります

上記の方法を採用すると、SELECT文はその時点で存在する全ての更新クエリが完了するまで、実行は待たされます。
つまり、SELECT実行時点で全ての更新が適用されたことが保証されます。
この時、待機にかかるコスト（応答性能の劣化）は殆どの場合「0」です。しかしながら、巨大な（長時間の）更新クエリが
実行されていると、遅延に繋がる可能性があります。ただし、ROW形式レプリケーション、かつ並列処理が出来ますので、
従来のレプリケーションよりは遅延が抑えられているはずです。

アプリ的観点では、DBへの接続が確立された後に wsrep_sync_wait 変数を変更すれば、より実践的と考えられます。

#### ◆ MyISAM and MEMORY

既に述べた通り、GaleraではInnoDBしか使えません。ただし、厳密に言うと以下の通りです。
「MyISAM や MEMORYはレプリケーションされない」

MyISAMがレプリケーションされないことは、大きなメリットにもなりえます。
例えば、「CREATE TEMPORARY TABLE ... ENGINE=MyISAM」のように一時テーブルを作成しても、
それは他ノードに伝播されません。
上記のような一時テーブルから実テーブルにデータを移行した場合、ROW形式レプリケーションのため
他ノードにレプリケーションされます。

#### ◆ GRANT文のレプリケーション

GRANT文、および関連する操作はmysqlスキーマのMyISAMテーブルに対する操作となります。
GRANT文の場合、ステートメントはレプリケーションされますが、テーブルデータ自体の変更は
レプリケーションされないはずです。

#### ◆ ALTER文

大抵のDDLは、たとえ長時間かかる処理であっても、ダウンタイムなしで実行できます。

【 RSU vs TOI 】

・RSU （Rolling Schema Upgrade）
1ノードずつ順番にノードから切り離し（offline）、DDLを実行する
・TOI （Total Order Isolation）
DDLが同じタイミング（同じ順序）で全ノードに実行されるよう、Galeraが調整します

注意：
DDLの実行を、クライアントと同期させる（伝える）方法はないため、クライアント側では新旧どちらの
テーブル定義でもエラーとならないよう配慮する必要があります。そうしないと、クライアントのコードの
更新時にクラスタ全体を一時停止させる必要が発生します。

以下のような "高速な" DDLは、TOI形式で実行した方が効果的です。

・CREATE/DROP/RENAME DATABASE/TABLE
・DEFAULT値の変更
・ENUM or SET などの定義変更
・一部の PARTITION ALTER
・DROP INDEX  (PRIMARY KEY以外)
・ADD INDEX
・小さなテーブルに対するALTER
・MySQL5.6/5.7 で追加された ALTER ALGORITHM=INPLACE のケース

上記以外の場合は、RSU形式も選択肢となります。その場合は、各ノードに対して以下の作業を実施します。

1. SET GLOBAL wsrep_OSU_method='RSU';  （この実行と同時に、該当ノードはクラスタから切り離されます）
2. ALTER TABLE ... 
3. SET GLOBAL wsrep_OSU_method='TOI';  （該当ノードは再びクラスタに復帰します）

#### ◆ シングルマスタの設定

クライアントからの更新クエリを1ノードに限定させることで、Galeraを通常の「Master - Slave」構成と
同じように利用することが出来ます。

・COMMIT発行後にエラーチェックをする必要がなくなります
・Galeraにおけるレイテンシーの利点は失います
・WRITE処理のスケーラビリティは失います

#### ◆ DBA の助け

・ノードをクラスタから一時的に切り離し、再度戻したときでも、同期は自動的にやってくれます
・試験やメンテナンスのため、一時的に切り離すことが手軽にできます
・ハードウェア/ソフトウェアのメンテナンスに関しても同様です

#### ◆ 注意すべきパラメータ変数

・auto_increment_increment
もし複数ノードに対して更新クエリを発行し、なおかつAUTO_INCREMENTを使用している場合は、
AUTO_INCREMENTの値はノード数に合わせて飛び飛びとなります
・binlog-do/ignore-db
使用は推奨されません
・binlog_format
「ROW」を設定する必要があります
・innodb_autoinc_lock_mode
「2」を設定してください
・innodb_doublewrite
通常は「ON」を推奨。FusionIOなどATOMICライトが保証されている場合は「OFF」の方が良い
・innodb_flush_log_at_trx_commit
「2」 か 「0」を設定してください。 もし不整合が発生しても、SST or IST で復旧可能です
・query_cache_size
・query_cache_type
 「0」 を設定して下さい。クエリキャッシュはGaleraにおいては使用できません
・wsrep_auto_increment_control
通常は「ON」が推奨されます
・wsrep_on
「ON」が推奨されます
・wsrep_provider_options
もしWAN環境を使用する場合は、様々な設定をチューニングする必要があるかもしれません
・wsrep_slave_threads
ノード間の並列レプリケーションに使用します
・wsrep_sync_wait （以前は wsrep_causal_reads）
"critical read"が必要な場合に使用します

#### ◆ その他

・つい最近までは、外部キー制約に多くのバグが含まれていました。
・LOAD DATA の内容はいくつかのチャンク（まとまり）に分けられ、各ノードに随時伝播されます
  （他ノードに全処理がまとめて伝わるわけではありません）
・DROP USER 文はレプリケーションされないかもしれません
・ロールバックの挙動が少し異なります。通常のInnoDBはより小さなトランザクションの単位でロールバックしますが、
  Galeraの場合は最後にロールバックします
・SET GLOBAL wsrep_debug = 1; を実行すると、エラーログに様々なデバッグ情報が書き込まれます
・巨大な UPDATEs / DELETEs は推奨されません。従来のDBMSでは有効な場合もありますが、Galeraにおいては問題となります
・WAN環境の場合は、右の変数をデフォルト値から変更すべきです： wsrep_provider_options = evs...
・Galeraに移行する場合は、MySQL/Perona 5.6 or MariaDB 10 以降が推奨されます

#### ◆ GTIDに関して

MySQL5.6以降、Galera 両方に「GTID」（Global Transaction ID）という概念が存在しますが、その内容は異なります。

・MySQL5.6以降のGTID、はserver_ UUID と シーケンシャル番号の組み合わせです
・GaleraのGTIDは、 cluster_ UUID と シーケンシャル番号の組み合わせです

#### ◆ クラスタ内にノードはいくつ持てるか

もし全サーバが同じ領域（サーバラックやデータセンター等）に存在するならば、奇数個のノードなら
いくつまでも保持できるでしょう。最小で3ノードからです。

拠点が分散しているならば、何か問題が発生しても、あなたは3以上のデータセンターが常に稼働している状態を
維持する必要があります。
もし2拠点しか残らなかった場合、一方のノードに障害が起きても、もう片方のノードから復旧が可能ですが、
それ以上はどうしようもありません。

もしあなたが 3 つ、もしくは 4 つの拠点を持っていた場合は、以下のようにノードを分配すると安全です。

・3ノードの場合
1 + 1 + 1 （各拠点に1ノードずつ）
・4ノードの場合
1 + 1 + 1 + 1 （3拠点の場合は4ノード構成は推奨しません）
・5ノードの場合
2 + 2 + 1,   2 + 1 + 1 + 1  （全拠点に公平に分配します）
・6ノードの場合
2 + 2 + 2,   2 + 2 + 1 + 1
・7ノードの場合
3 + 2 + 2,   3 + 3 + 1,   2 + 2 + 2 + 1,   3 + 2 + 1 + 1

これらの分配方法は、各拠点のノードの"weight"を意識しています。
スプリット・ブレイン対策として、どこかの拠点が落ちても、常に残った拠点が
「多数派」になるようにしなければいけません。

例えば、4ノードを3拠点に 2 + 1 + 1 と分配した場合、
(1/6+1/6) + 1/3 + 1/3 となり、2ノードの拠点が落ちると残った2拠点で
全体の過半数を保てなくなり、それらもダウンしてしまいます。

全ノード数の半分以上を、特定の拠点に固めないようにしましょう。

#### ◆ 更新履歴

Posted 2013; VARIABLES: 2015; Refreshed Feb. 2016

#### ◆ 参考

http://mysql.rjweb.org/