MariaDB Galera Cluster Upgradeの考え方

# はじめに
止められないサービスをMariaDB Galera Clusterで運用中のみなさんこんにちは！

セキュリティ脆弱性対応や、バグ修正取り込みのために、OSSのupgradeを検討することはよくありますよね。

通常のOSSであればReleaseNoteを眺めて、非互換がないか確認して、テストをしてリリースになると思いますが、データベースの場合そう簡単にはいきません。

なお、本記事ではGalera Clusterで用いられる用語の説明はしません。Linkをしれっと貼っておくので適宜参照ください。

# MariaDB(MySQL)のUpgrade方法
MariaDB(MySQL)のUpgradeには大きくわけて2つの方法があります。MySQLバージョンアップのベストプラクティスを見てみましょう。

参考：[MySQLバージョンアップのベストプラクティス](https://yakst.com/ja/posts/907)
参考原文：[MySQL upgrade best practices](https://www.percona.com/blog/2014/09/19/mysql-upgrade-best-practices/?utm_campaign=mysql-upgrade-best-practices)

## インプレース方式
>where you use existing datadir against the new MySQL major version, with just running mysql_upgrade after binaries are upgraded,

新しいバージョンでも同じデータディレクトリを使用し、バイナリをupgradeしたあと、mysql_upgradeで差分を吸収する方式ですね。ドキュメントの後半で語られていますが、マイクロアップ(5.5.38->5.5.45)であればこの方法がとれます。サービス停止時間も最短でできますし、yumのupgradeと[mysql_upgrade](https://mariadb.com/kb/en/library/mysql_upgrade/)コマンドでお手軽です。

しかし、メジャーバージョンを跨いだupgradeでこの方式をするのは非常に危険です。

>Before anything, this is a serious upgrade, and a huge step over one major MySQL version. That is, it’s risky. 

SQLレベルで互換が保たれていたとしても、内部には非互換があり、それを吸収するのがmysql_upgradeコマンドなのですが、完全に吸収することは難しいでしょう。やはり後述するmysqldumpを通じて、SQLレベルで新バージョンにリストアするのが安全と言えます。

## SQLダンプ方式
>on an old version and then restore it on a new version (using mysqldump utility or alternatives, like mydumper).

[mysqldump](https://mariadb.com/kb/en/library/mysqldump/)などの、SQLレベルでのdumpを行い、新バージョンでそれをリストアする方式です。

データ容量が大きいと非常に時間がかかってしまうため、止められないサービスを運用している方にとっては非常に苦しいでしょう。一方で、もっとも安全な方法とも言えます。

# GaleraClusterにおけるupgrade
さて、データベース(特にMySQL or MariaDB)におけるupgrade方法は上記2種類があることが分かりました。GaleraClusterで(3台以上で)冗長化している場合はどうすればいいでしょうか？

## severalninesによるOnline Upgrade
次に、MariaDBのパートナーであるseveralnines社のブログから、Online Upgradeの方法を見ていきましょう。

参考：[Howto: Online Upgrade of MariaDB Galera Cluster 5.5 to MariaDB 10](https://severalnines.com/blog/howto-online-upgrade-mariadb-galera-cluster-55-mariadb-10)

上記ページでは、5.5から10へのupgradeをOnline(無停止)で行う方法が書かれています。この方法を使えば完全Onlineではないにしろ、瞬断レベルでupgradeが可能です。とはいえやはりリスクと停止時間はトレードオフになります。最も安全なのはOfflineである、SQLダンプ方式です。

Onlineの場合、[SST](http://galeracluster.com/documentation-webpages/statetransfer.html)での同期を避ける必要があると前提に書かれています。

>SST must be avoided during the upgrade so we must ensure each node’s gcache is appropriately configured and loaded prior to the upgrade.

[SSTのmethod](https://mariadb.com/kb/en/library/galera-cluster-system-variables/#wsrep_sst_method)によるのですが、rsyncの場合、実データそのままコピーするので、インプレース方式と同様になります。この場合、何かしらのトラブルや、性能劣化等が起きる可能性があるので、避けたほうがいいのでしょう。(mysqldumpだったら大丈夫なように思えますが、同期に時間がかかるようですね。)

ISTでの同期だったとしても、実データを保持したままyumでupgradeしているようなので、インプレースになることには変わりません。Onlineでやる代わりにやはりリスクがある方式です。(うまくいけばいいのですが。)

## Galera Cluster Documentation
次に、Galera Clusterの公式ドキュメントで案内されているUpgrade方法を見ていきます。

参考：[Upgrading Galera Cluster — Galera Cluster Documentation](http://galeracluster.com/documentation-webpages/upgrading.html)

ここでは3種類の方法があり、それぞれに利点と欠点があります。rolling upgradeは時間がかかりますが、クラスタを維持しながらできますし、bulk upgradeはupgrade自体は早く終わるものの、サービスを止めてしまいます。よく内容を理解し、もっとも適切なものを選びましょう。

### ROLLING UPGRADE
クラスタ状態を活かしたままupgradeするためにはROLLING UPGRADEを選択しましょう。クラスタ内の個々のノードを1台ずつDOWNさせながら、upgradeしていくので、その名の通りrolling upgradeとなります。

ただし、この方法では注意すべき点がいくつかあります。
#### rolling upgradeの注意点
##### 時間消費
データベースのサイズ(ノード数)とデータベースの容量に依存して、時間がかかってしまいます。データ転送にISTを使わない限り、SSTでの転送はデータサイズに依存し、時間がかかってしまいます。SSTを行っているときはノードは新規書き込みに追従しようとするので、同期を続けます。

##### ブロックノード
ノードがofflineから復帰するとき、もしSSTにmysqldumpを使っているのであれば、donorノードは転送をブロックします。実際、クラスタは2つのノード間で転送を行います。1つはDONORノードで、もう1つはそのノードへ更新を追い付こうとするノードです。
(ここで言いたいのは、rolling upgrade時のクラスタ加入時、[SST method](http://galeracluster.com/documentation-webpages/statetransfer.html)によってはDONORにブロックがかかるもの(mysqldump)があるので、その場合クラスタ可用性はさらに低下することを注意しなさい、ということだと思います。はて、rsyncはblockingだとリンク先にはありますが、このドキュメントではblockingを避けられるとありますね...どっちだろう。後々検証したい。)

##### クラスタ可用性
rolling upgradeの際にノードがダウンすると、クラスタの性能や可用性が低下します。クラスタ加入時にDONORがブロッキング状態になった場合は、平常時より2台少ない状態になるので、DOWNしてもサービスが保たれるノード数も減りますし、リクエストの許容量も低下するでしょう。

##### クラスタ性能
upgradeの後、ノードのバッファプールにキャッシュがたまるまでは一時的に性能が劣化するでしょう。

#### メジャーバージョン間でのRolling Upgrade
この内容は少し前に紹介したseveralnines社の事例と同じです。(Rolling Upgrade = Online Upgrade) メジャーバージョンを跨ぐ例での制限が記載されています。

1. メジャーバージョンが異なる場合SSTはサポートされていません。つまり、異なるメジャーバージョンのノード間で同じクラスタを組むことができません。

2. upgrade前に、gcache.sizeを拡大しましょう。upgradeを行っている間、ISTが維持できるだけの値に設定しましょう。

3. クラスタ内に複数のバージョンが混在している間、特定のバージョン特有のSQLを実行するのを避けましょう。例えば、新バージョンでのみ有効になっているDDL文などです。(DDL調べる

### BULK UPGRADE
upgradeにかかる総時間を短縮したいのであれば、この方式を選びましょう。この方式はクラスタがダウンしている間に全ノード一気にupgradeします。

主な利点は、サイズの大きいデータベースを扱う場合、結果的にrolling upgradeよりはやく終わることでしょう。

主な欠点は、upgradeとrestartが早く終わらなければいけない点でしょう。InnoDBをshutdownするとき、ダーティページの書き込みに数分かかることもあります。

### PROVIDER-ONLY UPGRADE
Galeraのみのupgradeの方法です。省略します。

# Offline/SQLダンプ方式での時間短縮を考える
ここまで3つのドキュメントを見てきましたが、SQLダンプによるOffline Upgradeが、サービス停止を伴ってしまいますが、もっとも安全そうです。特にバージョンを跨いだ、5.5.X -> 10.2.Xとかいう危険なupgradeする場合はなおさらですね。

「安全と停止時間のトレードオフ」とはいえ、なるべく停止時間は短くしたいものです。

実データが30TBぐらいあって、mysqldumpと、dumpの流し込みにトータル2時間かかりますから、2時間サービス止まりますよ！と言ったところで「そんなの無理だよ何言ってんの」と言われる可能性があります。かといって「じゃあやめましょう」ともいきません。

フルバックアップとリストアに時間がかかるなら、事前に実施しておいて、止めた後の差分だけリストアすれば停止時間は短くできるのでは？ということで差分バックアップ/リストアを活用し、停止時間を短縮してみましょう。

## binlog(バイナリログ)を用いたポイントインタイムリカバリ
さて、差分バックアップの実現方法として、データベースの予期せぬクラッシュが起きた場合のリカバリ方法であるポイントインタイムリカバリの仕組みを使います。

通常、運用者は定期的にフルダンプを取っていると思いますが、フルダンプ取得して後のデータ変更をリカバリするために、データベースの変更を定期的に取得する必要があります。

[binary log](https://mariadb.com/kb/en/library/binary-log-formats/)についての詳細な説明はリンク先を見ていただくとして、binlogはROW(行)あるいはMIXEDでDBの変更前後を確実に再現できる形式で出力しておきます。

### binlogの出力

binlog出力はデフォルトでOFFになっているので、以下をmy.cnfの[mysqld]セクションに記載し、サービスを再起動しましょう。

```ini:my.cnf
[mysqld]
log-bin = mysql-bin
expire_logs_days = 1
binlog_format = ROW
```

binlogはDB変更前後が記録されているため、ほっておくと結構なサイズになります。dump取得以前のbinlogは不要になるので、領域圧迫しないよう適当な日数にexpire_logs_daysを設定しておきましょう。

binlogはshow binary logsで確認できます。

```sh
MariaDB [(none)]> show binary logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000001 |     26412 |
| mysql-bin.000002 |       365 |
| mysql-bin.000003 |       365 |
| mysql-bin.000004 |       365 |
| mysql-bin.000005 | 391425243 |
+------------------+-----------+
5 rows in set (0.00 sec)
```

### ログをフラッシュしてのdump
さて、定期的に取得するフルダンプですが、この時--flush_logsオプションをつけておくと、binlogがローテートされます。

これにより、最後にdumpしたとき以降のbinlogだけを用いればいいので楽です。以下、dumpの例です。

```
# mysqldump -uroot -A --compatible ansi --single-transaction --master-data=2 --flush-logs | gzip > /var/tmp/<日付>_<node>_dump.sql.gz
```

* compatibleオプションで、なるべく互換性を保ったSQLをdumpするようにします。メジャーバージョンを跨ぐ場合はつけておいたほうが無難でしょう。
* innodbの場合、運用ノードにロックをかけないようsingle-transactionをつけましょう。
* master-dataの2を指定すると、dumpの内容とbinlogを紐づける情報を出力します。
* flush-logsは前述のとおり、binlogをフラッシュして新規ファイルを作成します。

### dumpのリストア
流し込むだけです。

```
# mysql -uroot < <日付>_<node>_dump.sql
```

### binlogのリストア
mysqlbinlogを用います。

```
# mysqlbinlog /var/tmp/mysql-bin.000006 | mysql -uroot
```

## binlogを用いた差分バックアップ/リストア
本来、定常フルダンプを取っておき、クラッシュ状態からリカバリするための手段ですが、upgrade時にも同様に利用することができます。

DBの更新を止める(=サービスを止める)処理のあとに、フルダンプ、upgrade後のノードでフルリストアを行った場合、サービス停止時間は以下になります。(そのまんまですが)

```
サービス停止時間 = フルダンプ時間 + フルリストア時間
```

一方で、binlogを用いた差分バックアップ/リストアを行った場合、サービス停止時間は以下のようになります。

```
サービス停止時間 = 差分リストア時間
```

## マルチマスタの場合の注意
フルダンプ/リストアより差分ダンプ(binlog)/差分リストアのほうが短くでいいじゃん！すごい！となるところですが、Galera Clusterでマルチマスタを行っている場合、書き込みノードを1つにする、つまりシングルマスタにする必要があります。

binlogは、各ノード自身の書き込みがあったものしか出力されません。通常、あるノードに書き込みがあった場合、クラスタを組んでいる他のノードにも変更をかけてコミットとしますが、その場合binlogには出力されません。

そのため、各ノードで出たある時点以降のbinlogを時系列順に並べてマージする必要がありますが、時刻同期が完璧に行われている必要がありますし、galeraが制御している排他制御やwrite-setの順番保障をその通り再現する必要があるので、リスクが大きいと感じます。

(そもそもgaleraはレプリケーションをbinlogと同じROWベースで行っています。gcacheに含まれている変更内容をbinlogなのかSQLdumpなのか何らかの方式に出力し、別ノードで流し込むことができればいいんですが。。。)

参考：[All You Need to Know About GCache (Galera-Cache)](https://www.percona.com/blog/2016/11/16/all-you-need-to-know-about-gcache-galera-cache/)

Galera Clusterはマルチマスタが可能であるとはいえ、やはり安全なのはシングルンマスタで残りをバックアップとする運用です。この場合ですとbinlogはmasterノードで出力されるため、binlogも一意になります。マルチマスタで運用している場合で、差分バックアップ/リストアを行う場合は一時的にシングルマスタにしましょう。

# おわりに
本記事ではMariaDB Galera ClusterのUpgrade方法を既存のドキュメントから確認し、そのうちもっとも安全かつサービス停止時間が短い、「Offline/SQLdump方式」でのUpgrade方法について説明しました。

完全無停止が求められるシビアなサービスももちろんあると思います。他に良い方法があればぜひ教えてください。