MYSQL 8.0.16 Multi-primaryモードでメンバー追加(スケールアウト)できない話

#概要
MYSQL8.0で超便利なmysqlshを使用してクラスタを操作する際に掲題の問題にぶつかったので、一部始終を共有します。


#環境
OS:Centos 7.5

pkg:

- mysql-community-common-8.0.16-2.el7.x86_64
- mysql-community-client-8.0.16-2.el7.x86_64
- mysql-router-community-8.0.16-2.el7.x86_64
- mysql-community-server-8.0.16-2.el7.x86_64
- mysql-community-libs-8.0.16-2.el7.x86_64
- mysql-shell-8.0.16-1.el7.x86_64

#省略
clusterの作成まで成功したところから説明しますので、[省略](https://www.s-style.co.jp/blog/2018/11/2722/)します。

```
cluster =  dba.createCluster()
or 
cluster = dba.getCluster()
```

multi-primaryモードでの前提とします。

```
cluster.switchToMultiPrimaryMode()
```

#再現
この状態でaddInstanceすると以下のエラーで失敗します。

```
cluster.addInstance("xxx.xxx.xxx.xxx")

...
Instance configuration is suitable.
Cluster.addInstance: The table does not comply with the requirements by an external plugin. (MySQL Error 3098)

```

ログ(デフォルト：/var/log/mysqld.log)を見ると：

```
2019-06-05T08:07:08.430457Z 2205 [ERROR] [MY-011543] [Repl] Plugin group_replication reported: 'Table instances has a foreign key with 'CASCADE' clause. This is not compatible with Group Replication.'

```

#原因
cluster.switchToMultiPrimaryMode()を実行すると [group_replication_enforce_update_everywhere_checks=ON](https://dev.mysql.com/doc/refman/8.0/en/group-replication-options.html#sysvar_group_replication_enforce_update_everywhere_checks)にしてくれます。
これはmulti-primaryモードを使うにあたり、競合を検出したり、防ぐためのチェック機能ですが、switchToMultiPrimaryModeがそこで引っかかったわけです。
>- If a transaction is executed under the SERIALIZABLE isolation level, then its commit fails when synchronizing itself with the group.
>- If a transaction executes against a table that has foreign keys with cascading constraints, then the transaction fails to commit when synchronizing itself with the group.

####解決案を洗い出すためにもう少し具体的に見ると

- どのテーブルが引っかかったのか

```
SELECT CONCAT(t1.table_name, '.', column_name) AS 'foreign key',     
     CONCAT(t1.referenced_table_name, '.', referenced_column_name) AS 'references',     
     t1.constraint_name AS 'constraint name', UPDATE_RULE, DELETE_RULE 
     FROM information_schema.key_column_usage as t1 
     JOIN information_schema.REFERENTIAL_CONSTRAINTS as t2 
     WHERE t2.CONSTRAINT_NAME = t1.constraint_name 
       AND t1.referenced_table_name IS NOT NULL 


+-------------------------------------------------------+---------------------------+--------------------------------------------------------+-------------+-------------+
| foreign key                                           | references                | constraint name                                        | UPDATE_RULE | DELETE_RULE |
+-------------------------------------------------------+---------------------------+--------------------------------------------------------+-------------+-------------+
| replicasets.cluster_id                                | clusters.cluster_id       | replicasets_ibfk_1                                     | NO ACTION   | RESTRICT    |
| clusters.default_replicaset                           | replicasets.replicaset_id | clusters_ibfk_1                                        | NO ACTION   | RESTRICT    |
| instances.host_id                                     | hosts.host_id             | instances_ibfk_1                                       | NO ACTION   | RESTRICT    |
| instances.replicaset_id                               | replicasets.replicaset_id | instances_ibfk_2                                       | NO ACTION   | SET NULL    |
| routers.host_id                                       | hosts.host_id             | routers_ibfk_1                                         | NO ACTION   | RESTRICT    |
```

mysql_innodb_cluster_metadata.instancesの外部キー（instances.replicaset_id）のdelete制約が`SET NULL`になっていて、
こういう場合はconflict検知できないからやめてと`group_replication_enforce_update_everywhere_checks`にはじかれましたね。
（CASCADE以外もあったんですね。）
一応5.7.23で[バグ](https://bugs.mysql.com/bug.php?id=91972)としてリポートした方もいましたがまだ対応されてないですね。

#解決案
Onlineのままメンバーを追加する前提

##案１
mysql_innodb_cluster_metadata.instancesの外部キーの制約を変更
[バグ](https://bugs.mysql.com/bug.php?id=91972)を書いてくれた方が改善案も書いてあります。

```
ALTER TABLE `mysql_innodb_cluster_metadata`.`instances` DROP FOREIGN KEY `instances_ibfk_2`;

ALTER TABLE `mysql_innodb_cluster_metadata`.`instances` ADD CONSTRAINT `instances_ibfk_2`
 FOREIGN KEY (`replicaset_id`) REFERENCES `replicasets` (`replicaset_id`);

```
※ mysql_innodb_cluster_metadataを弄っているので全メンバーで実施必要かと思ったら、クラスタ内同期されました。
※ 非公式な案なので、どんな問題起きるかわかりません。**十分な理解とテストした上で採用してください**
※ *ただDELETE_RULEを変更しているので、メンバー削除をしなければ問題ないかも知れません。*


##案２(中の人曰くこっちがおすすめ)
一時的にSingleprimaryに変更すればよいです。
裏でgroup_replication_enforce_update_everywhere_checks=OFFにしてくれます。

```
cluster.switchToSinglePrimaryMode()
cluster.addInstance("xxx.xxx.xxx.xxx")
cluster.switchToMultiPrimaryMode()
```
※SinglePrimaryにSwitchした時点で実質１台のみOnlineのままになりますが。。。it's works!

##案３
mysqlshを使わずにやれば出来ますが、せっかく便利なツールがあるので積極的に使いたいですよね。作業効率を上げるためにも。



#まとめ
まだmysql初心者なので、素朴な質問ですがそもそもmulti-primaryって何がうれしいんでしょう。。。
そのユースケースが分かる方がいらっしゃいましたら教えていただければと思います。

私的には。。。
競合起きる可能性があるから一貫性はさがるでしょう？
書き込みパフォーマンスを上げたい？→shardingがいいでしょう
耐障害性を上げたい（切り替え時間短縮）?→Group replicationによってSingleも遅くないと感じる

マニュアルにもいろいろ[制約](https://dev.mysql.com/doc/refman/8.0/en/group-replication-limitations.html)書いてあり、multi-primaryを使いたい方は忘れずに見てくださいな。

#参考
以下のサイトを参考にしました。ライターの方々にありがたいです。

http://gihyo.jp/dev/serial/01/mysql-road-construction-news
https://lefred.be/content/mysql-group-replication-single-primary-or-multi-primary/