CentOS7 + MariaDB でGTIDレプリケーション構築

# 検証環境
- CentOS 7.3
- MariaDB 10.1.24
- DBマスタサーバ（IP: 192.168.0.1）
- DBスレーブサーバ（IP: 192.168.0.2）

MySQLでGTID有効のレプリケーション構築を行う手順とは大きく異なりますのでご注意ください。

# レプリケーション構築に関するMySQLとの違い
MariaDB10.0以降であればGTIDの設定をするだけでクラッシュセーフになるようです。
MySQL5.6以降と異なり、MariaDBは GTID（Global Transaction ID）がデフォルトで有効になっています。

したがって、設定ファイルにGTIDモードを有効にするための以下設定を書く必要はありません。
（※ 書いたらエラーで起動しなくなります）

`gtid-mode = ON`
`enforce-gtid-consistency`

また、GTIDの状態は **mysql.gtid_slave_pos** テーブルに書き込まれるため、以下設定も同様に必要ありません。

`relay_log_info_repository = TABLE`
`relay_log_recovery = ON`
　
######MariaDBとMySQLの設定値の違いは公式に記載があります。
[System Variable Differences Between MariaDB 10.1 and MySQL 5.6](https://mariadb.com/kb/en/mariadb/system-variable-differences-between-mariadb-101-and-mysql-56/)
[System Variable Differences Between MariaDB 10.1 and MySQL 5.7](https://mariadb.com/kb/en/mariadb/system-variable-differences-between-mariadb-101-and-mysql-57/)

# 1. 事前準備
**初めてレプリケーション構築を行う場合、このステップは飛ばして結構です。**

もし既存のレプリケーションやログが残っているDBで新たに構築し直す場合、ログをクリアしておく必要があります。

この作業を怠るとダウンしたマスタをスレーブ群に加える際、誤動作の原因となるようなので注意してください。

```sql:スレーブ側のMariaDBで実行
STOP SLAVE;
RESET SLAVE ALL;
```

```sql:マスタ側のMariaDBで実行
DELETE FROM `mysql`.`gtid_slave_pos`;
RESET MASTER;
```

datadir（デフォルト /var/lib/mysql） に既存のバイナリログが出力されている場合は退避しておきます。

```bash:/var/lib/mysql
# バイナリログのファイル名を "slave-bin" と指定している場合

$ mkdir mysqlbak-yyyymmdd
$ mv /var/lib/mysql/slave-bin.* ./mysqlbak-yyyymmdd
```

# 2. マスタ側にレプリケーション用ユーザを作成
レプリケーションのみ権限を付与したユーザを用意しておきます。

```sql:マスタ側のMariaDBで実行
CREATE USER 'repl'@'192.168.0.2' IDENTIFIED BY '[パスワード]';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.0.2';
FLUSH PRIVILEGES;
```


# 3. マスタ側の設定ファイルを編集
```
systemctl stop mariadb
vi /etc/my.cnf.d/server.cnf
```
```vim
[mysqld]
server-id = 1        # スレーブと異なるサーバIDにする
log-bin=master-bin   # 任意のバイナリログファイル名
binlog-format = ROW
log-slave-updates
```
```
systemctl start mariadb
```
datadir（デフォルト /var/lib/mysql） に`log-bin`で設定したファイル名のバイナリログが出力されているか確認します。
また、設定が正常に反映されているかどうかは、以下の方法で確認できます。

```sql
SHOW VARIABLES LIKE 'server_id';

+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| server_id     | 1     |
+---------------+-------+
```


# 4. スレーブの設定ファイルを編集
```
systemctl stop mariadb
vi /etc/my.cnf.d/server.cnf
```
```vim
[mysqld]
server-id = 2      # マスタと異なるサーバIDにする
read_only          # 意図しない改変を防ぐために推奨
log-bin=slave-bin  # 任意のバイナリログファイル名
log-slave-updates
```
```
systemctl start mariadb
```
 datadir（デフォルト /var/lib/mysql） に`log-bin`で設定したファイル名のバイナリログが出力されているか確認します。


# 5. マスタのダンプファイルを取得
```vim:マスタ側で実行
mysqldump --all-databases -u root -p --master-data=2 --single-transaction --routines > dump.sql
```

ダンプファイルから `CHANGE MASTER TO` と書かれた行を検索し、バイナリログファイル名とポジションをメモしておきます。
私の環境の場合、`grep`使うとかなり長い文字列でヒットしてしまったため、`vi`で開いて`/`検索しました。

```sql:こんな行があるはず
CHANGE MASTER TO MASTER_LOG_FILE='xxxxx-bin.000001', MASTER_LOG_POS=xxx;
```

###### 【2017.12.08 追記】
`SHOW MASTER STATUS` クエリを実行することで、ダンプファイルを直接開いて検索する方法と同様にバイナリログファイル名とポジションを取得可能なようです（記事コメント欄参照）。


# 6. マスタの現時点でのGTIDを調べる
さきほどメモしたバイナリログファイル名とポジションをもとに、現時点でのGTIDを調べます。

```sql:マスタ側のMariaDBで実行
SELECT BINLOG_GTID_POS( "xxxxx-bin.000001", xxx );
```
MariaDBのGTIDは**「ドメインID + サーバID + サーバーごとのトランザクション番号」**というフォーマットで構成されています。
**'0-1-1'** のような形式のGTIDがヒットするはずなので、またメモしておきます。

※ ここで空が返ってきてしまう場合、何かしら適当なUPDATE文を投げてMariaDBの再起動を行い、ダンプ取得の手順からやり直せばヒットするようになります。


# 7. スレーブにリストア
マスタのダンプファイルをもとにスレーブ側でリストアします。

```vim:スレーブ側で実行
mysql -f -u root -p < dump.sql
```


# 8. スレーブにGTIDを設定する
手順6でメモしておいたGTIDを設定します。

```sql:スレーブ側のMariaDBで実行
SET GLOBAL gtid_slave_pos = '0-1-1';
```


# 9. CHANGE MASTERの実行とスレーブ開始
スレーブ側で以下を実行します。

```sql:スレーブ側のMariaDBで実行
CHANGE MASTER TO
  MASTER_HOST     = '192.168.0.1',
  MASTER_USER     = 'repl',
  MASTER_PASSWORD = '[replユーザのパスワード]',
  MASTER_USE_GTID = slave_pos;

START SLAVE;
SHOW SLAVE STATUS \G
```

`CHANGE MASTER TO` の設定内容は `/var/lib/mysql/master.info` に反映されます。
`START SLAVE` でスレーブを開始後、`SHOW SLAVE STATUS` でステータスを表示します。
以下のステータスが返ってくれば問題ありません。

Slave_IO_State| Waiting for master to send event
---|---
Slave_IO_Running|Yes
Slave_SQL_Running|Yes
Last_Errno|0
Last_Error|
Last_IO_Errno|0
Last_IO_Error|
Last_SQL_Errno|0
Last_SQL_Error|
Gtid_IO_Pos|[設定したGTID]

GTIDの状態は以下の方法でも確認可能です。

```sql
SELECT * FROM `mysql`.`gtid_slave_pos`

+-----------+--------+-----------+--------+
| domain_id | sub_id | server_id | seq_no |
+-----------+--------+-----------+--------+
|         0 |      1 |         1 |      1 |
+-----------+--------+-----------+--------+
```


# 10. 動作確認して設定完了
マスタ側で適当なUPDATE文を流してみて、更新がスレーブ側にも反映されていれば作業完了です。


# 【Case1】 マスタが落ちた場合
**以降の手順は未検証です。メモ書き程度にご参考ください。**

マスタ（192.168.0.1 以降、旧マスタ）が落ちてしまい、
スレーブ（192.168.0.2）を新マスタに昇格させる必要がある場合、
スレーブ群（192.168.0.3〜）にマスタの変更を通知する必要があります。

```sql:スレーブ群のMariaDBで実行
CHANGE MASTER TO
  MASTER_HOST = '新マスタIP（192.168.0.2）',
  MASTER_PORT=3307;  -- ポート番号の指定が必要な場合
```

これでスレーブ → マスタ昇格の作業自体は完了ですが、
旧マスタからスレーブに転送し損ねたGTIDが無いか、後々チェックしておく必要があります。

```sql:旧マスタのMariaDBで実行
-- 件数が莫大になる可能性もあるのでLIMITは必ず付与する
SHOW BINLOG EVENTS IN 'xxxxx-bin.000001' LIMIT 20 \G

*************************** 1. row ***************************
   Log_name: xxxxx-bin.000001
        Pos: xxx
 Event_type: Gtid
  Server_id: 1
End_log_pos: xxx
       Info: BEGIN GTID 1-1-4

```

最後に出力された `Info: BEGIN GTID` に記載のGTIDが最新ということになると思います。
`mysqlbinlog` を使って確認することもできます。

もし、未転送のGTIDがある場合、新マスタをいったん旧マスタのスレーブ構成にして、そのGTIDでスレーブを開始すると補填できるようです。

正直、ここの流れが具体的によく分かっていません。
→ 新マスタを停止 & 旧マスタ起動
→ `SET GLOBAL gtid_slave_pos = '未転送のGTID';`
→ `CHANGE MASTER TO & START SLAVE`　で一時的に旧マスタのスレーブ化
→ 同期できたら `STOP SLAVE & CHANGE MASTER TO` の設定をクリアする

という流れでしょうか…。
未転送のGTIDで操作したレコードと同レコードに対して、すでに新マスタ側で何かしらの操作が行われている場合はどうなるの？という疑問があります。
（見当違いなことを言っているかもしれない）


# 【Case2】 落ちた旧マスタをスレーブ化する
落ちてしまった 旧マスタ（192.168.0.1）が復旧したら、スレーブ化します。
レプリケーション用のユーザは事前作成しておく必要があるので注意してください。

```sql:旧マスタのMariaDBで実行
CHANGE MASTER TO
  MASTER_HOST = '新マスタIP（192.168.0.2）',
  MASTER_USER = 'repl',
  MASTER_PASSWORD = '[replユーザのパスワード]',
  MASTER_USE_GTID = current_pos;
```

`MASTER_USE_GTID` に `slave_pos` ではなく `current_pos` を設定している点に注意が必要です。

gtid_slave_pos| スレーブSQLスレッド（*1）が最後に実行したトランザクションのGTID
---|---
gtid_current_pos|最後に処理したトランザクションのGTID（*2）

（\*1）**マスタから転送されたトランザクション**のうち、最後に実行されたGTID
（\*2）**スレーブが直接実行したSQL（エラー修復など）も含む**、最後に実行されたGTID
　　　    -> \*2 は自身のサーバのみで実行されたGTID（他のサーバに存在しないGTID）を含んでいる

旧マスタはもともとスレーブではないため、**slave_pos 自体をまだ持っていません。**
そのため、まずは `current_pos` で自身の最新GTIDをセットしているわけです。
`START SLAVE` でいったん同期すれば、`MASTER_USE_GTID = slave_pos` に変更してOKだと思います。

ただし、最新のGTIDが分かっているなら以下の手順でも構わないようです（手順8〜9と同じ）。

```sql:最新のGTIDを指定して旧マスタをスレーブ化
SET GLOBAL gtid_slave_pos = 'x-x-xxx';

CHANGE MASTER TO
  -- （...中略...）
  MASTER_USE_GTID = slave_pos;
```


# 参考記事
- [MySQL入門　レプリケーション編](http://qiita.com/Tocyuki/items/c224cef57493f536a941)
- [MySQL/MariaDB GTID レプリケーション詳細](http://bizstation.hatenablog.com/entry/2016/05/19/091030)
- [MariaDB10でGTIDを使ったレプリケーションの設定](http://blog.craft-works.jp/uru/20140504/mariadb-gtid-replication)
- [MySQL5.6新機能のGTID(Gloval Transaction ID)を試す](http://sawara.me/mysql/1500/)