MySQL 5.7のマルチソースレプリケーションを試す

この記事は [MySQL Casual Advent Calendar 2015](http://qiita.com/advent-calendar/2015/mysql-casual) の11日目です。

# 概要
MySQL 5.7.6からマルチソースレプリケーションが本体の機能として実装されました。
マルチソースレプリケーション、設定方法はボロボロ見つかるんだけど実際にそれを運用した話とかはあまり聞かないので、
実際にあった複数マスターな環境を再現して、マルチソースレプリケーション使えそうかどうか試してみようかな、と思います。


# TL;DR
 * 5.7のマルチソースレプリケーション、動いてそう
 * 5.6 => 5.7, 5.7 => 5.6も問題なく動作した
 * 5.7で暗黙のデフォルト値がたくさん変わっててつらい
 * 5.6から5.7にアップデートするときはデフォルト値に気をつけてね


# 構成
まず、実際にあった構成を再現します。
3台マスターがあって、それらの間でTable Shardingをしてしました
冗長化のために、それぞれに対して1:1になるようにスレーブを生やしています。
全体でマスター3,スレーブ3の計6台のDBがありました。
Shard間で同じDBを利用していましたが、それぞれのDBに違うテーブルが存在する状態になります。
全てAmazon RDS for MySQL(5.6)でした。

```
+-------+   +-------+   +-------+
|master1|   |master2|   |master3|
+---+---+   +---+---+   +---+---+
    |           |           |
    |           |           |
    v           v           v
+-------+   +-------+   +-------+
|slave1 |   |slave2 |   |slave3 |
+-------+   +-------+   +-------+
```


# やりたいこと
* インフラ費がもったいなかったので、Table Shardingを統合する形でDBをスケールインさせたい
* ダウンタイムを最小限にしたい
* 大人の事情でRDSを使い続けたい


# ためしてみること
EC2にMySQL 5.7をインストールして、RDSマルチソースレプリケーションで1DBに統合。
そこからさらにRDSへレプリケーションして、移行時に新たなマスターとして昇格させる。
移行の時はサービスを止めるか、AUTO INCREMENTの値を適当に変えてオンラインで切り変えればダウンタイムを無しで切り替えることが出来ます。

```       
+-------+   +-------+   +-------+
|master1|   |master2|   |master3|
+---+---+   +---+---+   +---+---+
    |           |           |
    |           v           |
    |       +-------+       |
    +------>+slave57+<------+
            +-------+
                |
                v
            +-------+
            |slave56|
            +-------+
```

## 用意
### マスターDB用意
簡単のために、EC2のインスタンスを立ち上げてMySQL 5.6をインストールします。
AMIはAmazon Linuxの2015.09(ami-383c1956)、インスタンスサイズはt2.microを利用しました。
カジュアルにやりたいのでマスターは2台です。

MySQLのインストールには、Chefを利用します。

```:Berksfile
source 'https://api.berkshelf.com'

cookbook 'mysql'
```

普通に[chef-cookbooks/mysql](https://github.com/chef-cookbooks/mysql)を利用しただけですが、使ったrecipe等を[リポジトリ](https://github.com/the40san/mysql56_cookbooksample)にあげておいたので気になった方は見てください。

my.cnfにレプリケーションのための設定を書いていきます。

```master1_my.cnf
[mysqld]
server-id=1
log-bin=mysql-bin
```

```master2_my.cnf
[mysqld]
server-id=1
log-bin=mysql-bin
```

mysqldを再起動して、スレーブからアクセスするためのユーザーを作成します。

```sql
create user slave_user;
grant reload, select, lock tables, replication slave, replication client on *.* to 'slave_user'@'%';
flush privileges;
```

なんかごちゃごちゃ権限ついていますが、後からmysqldumpするために必要な権限をつけています。
次に、Table Shardingっぽくするために2つのマスターに違う名前のテーブルを1つづつ作っていきます。

```sql:master1
> create database test;
> create table test.table1 (id int primary key);
> show create table test.table1;
+--------+--------------------------------------------------------------------------------------------------------------+
| Table  | Create Table                                                                                                 |
+--------+--------------------------------------------------------------------------------------------------------------+
| table1 | CREATE TABLE `table1` (
  `id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+--------+--------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

> insert into test.table1(id) values (1),(2),(3);
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

> select * from test.table1;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
+----+
3 rows in set (0.00 sec)
```

```sql:master2
> create table test.table2 (id int primary key);
Query OK, 0 rows affected (0.01 sec)

> insert into test.table2(id) values (1),(2),(3);
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

> select * from test.table2;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
+----+
3 rows in set (0.00 sec)
```


### Slave57(MySQL 5.7マルチソースレプリケーション)
マスターと同じようにEC2のインスタンスを立ち上げて、recipeで`version '5.7'`を指定して同じようにChefでインストールすると5.7.10がインストールできました。

```ruby:receipe.rb
mysql_service 'foo' do
  port '3306'
  version '5.7'
  initial_root_password ''
  action [:create, :start]
end
```

次に、my.cnfへ必要な設定を追加していきます。

```my.cnf
[mysqld]
server-id = 3
master_info_repository = TABLE
relay_log_info_repository = TABLE
log-bin=mysql-bin
log_slave_updates
binlog_format=STATEMENT
```

`master_info_repository`と`relay_log_info_repository`は、クラッシュセーフなレプリケーションのために設定する必要があるらしいです。
`log-bin`と`log_slave_updates`は、ここからさらに5.6へレプリケーションするからbinlogを書いておきたいのと、レプリケーションをはしごするためにレプリケーションの結果をbinlogに書き込むために設定しています。
`binlog_format`は…5.7.7からデフォルト値がSTATEMENTからROWに[変わっていて](http://dev.mysql.com/doc/refman/5.7/en/replication-options-binary-log.html#sysvar_binlog_format)、**5.6とレプリケーションするときにbinlogのフォーマットが違ってレプリケーションが失敗する**ということが起きたので、今回は5.6に合わせてSTATEMENTに設定しています。

設定ができたらmysqldを再起動して、次はレプリケーションの設定をします。
mysqldumpを使って、既存のデータを持ってきます。
簡単のために、事前に/etc/hostsにそれぞれのマスターのホストのエイリアスを仕込んであります。

```
slave57> mysqldump --databases test -u slave_user -h master1 --master-data --apply-slave-statements > master1
slave57> mysqldump test -u slave_user -h master2 --master-data --apply-slave-statements > master2
```

マルチソースレプリケーションのために、ダンプ結果を少しだけ編集します。

```diff
- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=345;
+ CHANGE MASTER TO MASTER_HOST='master1', MASTER_USER='slave_user', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=345 FOR CHANNEL 'master-1';


- START SLAVE;
+ START SLAVE FOR CHANNEL 'master-1';
```

`MASTER_HOST`と`MASTER_USER`を指定したのと、`FOR CHANNEL 'master-1'`でマルチソース化しました。
`START SLAVE`にも同様です。

もう片方のダンプも同じように編集したら、5.7へダンプをリストアします。

```
slave57> mysql -uroot < master1
slave57> mysql -uroot test < master2
```

`SHOW SLAVE STATUS`で確認します。

```
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: master1
             Master_Server_Id: 1
                  Master_UUID: bf375f7c-9ee3-11e5-999e-0ae49da8dae1
      Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
                 Channel_Name: master-1
*************************** 2. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: master2
             Master_Server_Id: 1
                  Master_UUID: d8a39f71-9ee6-11e5-99b2-0a0338b21cc9
      Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
                 Channel_Name: master-2
2 rows in set (0.00 sec)
```

内容は一部割愛しています。
2つのマスターに対してレプリケーションされているようです！


### Slave56(Slave57からレプリケーション)
最後に、MySQL 5.7から5.6へレプリケーション設定します。

同じように、Slave57にレプリケーション用のユーザーを作成します。

```sql
create user slave_user;
grant reload, select, lock tables, replication slave, replication client on *.* to 'slave_user'@'%';
flush privileges;
```


mysqldumpします。

```
slave56> mysqldump --databases test -u slave_user -h slave57 --master-data --apply-slave-statements > slave57
```

ダンプにSlave57のホスト、ユーザー名を指定します。

```diff
-CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000004', MASTER_LOG_POS=154;
+CHANGE MASTER TO MASTER_HOST='slave57', MASTER_USER='slave_user', MASTER_LOG_FILE='mysql-bin.000004', MASTER_LOG_POS=154;
```

リストアします。

```
slave56> mysql -uroot < slave57
```

`SHOW SLAVE STATUS`で確認します。

```
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: slave57
      Slave_SQL_Running_State: Slave has read all relay log; waiting for the slave I/O thread to update it
1 row in set (0.00 sec)
```

5.7から5.6へのレプリケーションも動きました。yey :D

最後に、master1, master2に変更を掛けてみます。

```
master1> insert into table1(id) values (4);
Query OK, 1 row affected (0.00 sec)

master2> insert into test.table2(id) values (5);
Query OK, 1 row affected (0.00 sec) 
```


```
slave56> select * from test.table1; select * from test.table2;
+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
+----+
4 rows in set (0.00 sec)

+----+
| id |
+----+
|  1 |
|  2 |
|  3 |
|  5 |
+----+
4 rows in set (0.00 sec)
```

slave56まで変更が届いています！


# 所感
検証はあまりできてないですけど、とりあえず動いてそうなので思い描いていた構成自体は取れるようです。
[裏技](https://github.com/do-aki/N1Repl)を使わなくても良くなる日が来たということでしょうか。
ただ、色々弄ってる間に5.7関連でハマったので、もしアップデートされる方がいたら気をつけてください…