Alibaba CloudのPolarDBの徹底分析(9)_マルチマスター (MySQL 8.0のみ利用可能)

# 普段使われているsingle masterアーキテクチャ
## 定義
- 一つの`primary node`(`master node`と呼ばれることも多い。つまりreadとwrite両方できるノード)と複数の`read-only node`(`read レプリカ`と呼ばれることも多い)の組み合わせでの構成
- クラスター全体の処理能力(厳密的に言うと、読み取りの性能)を向上させるためには、 read-only nodeを追加する
    - 読み取りが非常に多く、書き込み/更新が比較的に少ない環境で最適に機能します。
    - ほとんどの Web サイトはこのカテゴリ (ユーザーが Web サイトをブラウズしたり、記事や投稿を読んだり、製品を見たりする) に該当します。
    - 更新は、セッション管理中、購入するとき、またはフォーラムにコメント/メッセージを追加するときにのみ発生します。

## 課題
- マルチテナント、ゲーム、ECなどでたくさんのユーザーの同時アクセスのあるシステムでは負荷増加に応じてread-only nodeを追加すること(`スケールアウト`)でreadのパフォーマンスが向上できる一方、 writeの性能を向上するにはprimary nodeのスペックを上げる(`スケールアップ`)しかありません。
- `スケールアップ`する時にデータベースの再起動が必要であり、数分サービスdownがどうしても発生してしまい、本番環境では安易にできないでしょう。
- primary nodeがクラッシュしてしまう場合は、異常検知、そしてread-only nodeへのfailoverも二三分程度かかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/6ce5cdec-c819-8149-32ac-6c4bf7611cfe.png)
::: note info
- 赤い部分： データベースのデータ
- 青い部分： メタデータ(MySQLサーバのスキーマやテーブルなどのオブジェクトの設定情報、設定パラメタや稼働状況など)
:::

# multi-masterアーキテクチャ
##  目的
- 複数primary node(最大32個まで)が利用できるようになり、データベース全体の書き込み性能が向上できる。
- 書き込み性能のボトルネックになっている高い書き込み性能が求められている利用シーンに適しています。

## 仕組み
- 一つのclusterに複数primary node(最大32個まで)が利用できて、複数のprimary nodeから同時に`読み書き`できる。
- クエリを実行する時に、nodeを指定する必要がなく、いつも通りにdatabase名を指定すればよい
    - polardb proxy側で自動的にクエリを解析してくれて、正しいnodeにアサインしてくれる。
    - つまり、既存のアプリケーションでsingle masterアーキテクチャからmulti-masterアーキテクチャに変更するにはアプリケーションは一切修正する必要がない。
- 複数nodeに存在する単に読み込みのパフォーマンスを向上させたい場合は、 global read-onlyのnodeも追加できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/eccdfd6f-f6a9-abee-b164-82d3cecec716.png)


## 使ってみる
- 新規作成
    - 現在のmulti-masterはmysqlしか利用できない。
    - cluster作成する時にはnode数は2で固定している。作成した後、node数を変更することも可能
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/aff3b60a-a50f-9b3f-651e-0f193c9b8a8b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/7072e5e2-8f63-441d-c4a1-65a22e0bc6da.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/4c3f0540-1f66-073f-9a0d-8ceaa3f777bf.png)
- 新しいprimay(R/W) nodeを追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/ea81c77d-3310-2a31-5792-91cb35d71707.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/10f13dc2-6744-8c2a-7c8b-6bf5c9355175.png)
- 新規追加されたprimary node
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/a1437cd0-9227-de21-9f29-bca98d4ca0b5.png)



## 詳細仕様
- databaseを作成する時にprimary node番号を指定する(database名はクラスター内で重複していはいけない。つまり異なるnode上でも同じ名前のdatabaseが作成できない。)
    ```
    CREATE DATABASE DB1 POLARDB_WRITE_NODE 1;
    CREATE DATABASE DB2 POLARDB_WRITE_NODE 1;
    CREATE DATABASE DB3 POLARDB_WRITE_NODE 2;
    CREATE DATABASE DB4 POLARDB_WRITE_NODE 2;

    mysql> show databases;
    +--------------------+
    | Database           |
    +--------------------+
    | __recycle_bin__    |
    | db1                |
    | db2                |
    | db3                |
    | db4                |
    | information_schema |
    | mysql              |
    | performance_schema |
    | sys                |
    +--------------------+
    ```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/ce28c280-3eed-1e95-8dae-fdbca12eb715.png)

- クエリ実行する時には該当のdatabaseはどのnodeに存在しているのかを意識する必要がなく、いつも通りのクエリを実行し、PolarDB Proxyが自動的に正しいnodeに転送してくれる。
    ```
    select * from DB1;
    ```
- database削除する時にもnodeを指定する必要がない。
    ```
    DROP DATABASE DB1;
    ```
- primary nodeも簡単に変更できる。
    ```
    ALTER DATABASE DB2 POLARDB_WRITE_NODE 2;
    ```
:::note info
- データ自体はshared storageに保持されていて、node上ではあくまでもメタデータしかもっていない。
- データベース所在nodeの切り替えに必要な時間はデータ量とは関係ありません。 以下のようにテストしてみたところ、100万件データのあるtableのnodeを切り替えるには1秒以内にできました。
```
DBHOST= ***
DBPASS= ***
DBUSER= ***

sysbench --db-driver=mysql \
      --mysql-host=${DBHOST}  \
      --mysql-user=${DBUSER} \
      --mysql-password=${DBPASS} \
      --mysql-db=db1 \
      --table_size=1000000 \
      oltp_read_write \
      prepare

time echo 'ALTER DATABASE db1 POLARDB_WRITE_NODE 2' | mysql -h ${DBHOST} -P 3306 -u ${DBUSER} -p
Enter password:
echo 'ALTER DATABASE db1 POLARDB_WRITE_NODE 2'  0.00s user 0.00s system 27% cpu 0.003 total
mysql -h ${DBHOST} -P 3306 -u ${DBUSER} -p  0.02s user 0.01s system 0% cpu 3.916 total
```
- db1はnode1からnode2に変わりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/9b2a605f-ff7f-3fc2-d7f8-f600571188a1.png)

:::

### global read-only node
- 普通のPolarDB cluster(multi-masterではないもの)ではread-only nodeは読み取り性能を向上させるために使われるものである一方、こちらのglobal read-only nodeは読み取り性能の向上ではなく、クロスノード上の複数データベースを同時にアクセスし、データを集計するためです。
::: note info
primary nodeには`global`という文字がついていなくて、read-only nodeに`global`という文字がついている理由：
- primay node：一部のデータベースのメタデータしか見れないため、全部のデータベースにアクセスできるということではありません。
- read-only node：読み取り専用のノードであり、全部のデータベースのメタデータを持っていて、全部のデータベースからデータを読み取ることができる。
:::
- global read-only nodeがない場合は、　異なるnode上のデータを結合(join)すると、エラーになります。
    - `db1`と`db2`はそれぞれ`node1`と`node2`にある
    - `db1`には`users`テーブルがある
    - `db2`には`user_entry`テーブルがある
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/2f8e30be-b7a3-32da-a80d-944ed6c08174.png)

```
select * from db1.users;
+----+---------------+---------+
| id | email         | name    |
+----+---------------+---------+
|  1 | abc@gmail.com | taro    |
|  2 | def@gmail.com | hanako  |
|  3 | ghi@gmail.com | takashi |
|  4 | jkl@gmail.com | yumi    |
|  5 | mno@gmail.com | hiroshi |
+----+---------------+---------+

select * from db2.user_entry;
+----+---------+---------------------+
| id | user_id | created_at          |
+----+---------+---------------------+
|  1 |       3 | 2016-06-07 13:11:53 |
|  2 |       3 | 2016-06-09 12:04:02 |
|  3 |       1 | 2016-06-13 17:41:30 |
|  4 |       2 | 2016-06-19 07:05:10 |
|  5 |       3 | 2016-06-20 09:25:01 |
+----+---------+---------------------+

mysql> select y.id, y.created_at, x.name, x.email from db1.users as x left join db2.user_entry as y on x.id = y.user_id;
ERROR 30119 (HY000): Failed to get global table lock for 'db1.users'
```
- 新しいglobal read-only nodeを追加すれば、ことなるnode上のdataをjoinできるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/940e7f18-e914-fa1f-08c2-4e3f24381251.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/d8f9233d-d1ee-83bc-e36c-862f73a96beb.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/b132e55b-f3a1-a7c6-2628-0b334c6befa6.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/cb78e490-d4ed-37ef-5e6e-427d475652d1.png)

```
mysql> select y.id, y.created_at, x.name, x.email from db1.users as x left join db2.user_entry as y on x.id = y.user_id;
+------+---------------------+---------+---------------+
| id   | created_at          | name    | email         |
+------+---------------------+---------+---------------+
|    1 | 2016-06-07 13:11:53 | takashi | ghi@gmail.com |
|    2 | 2016-06-09 12:04:02 | takashi | ghi@gmail.com |
|    3 | 2016-06-13 17:41:30 | taro    | abc@gmail.com |
|    4 | 2016-06-19 07:05:10 | hanako  | def@gmail.com |
|    5 | 2016-06-20 09:25:01 | takashi | ghi@gmail.com |
| NULL | NULL                | yumi    | jkl@gmail.com |
| NULL | NULL                | hiroshi | mno@gmail.com |
+------+---------------------+---------+---------------+
```

## 制限事項
- 現時点はmulti-masterのアーキテクチャはMySQL8.0にしかありません。multi-masterを使うには必然的にMySQL8.0を利用するしかありません。
- multi-masterのクラスターを利用したい場合は、新規作成の時点でmulti-masterのタイプを選択していなければならない
- 複数primary nodeを跨いたselectクエリはサポートしていません。 selectクエリに複数のprimary node上のデータベースを含む場合、実行エラーになります。  以下の２つの解決方法があります。
    1. 結合したいデータベースを全部一つのnodeに変更/移動する。結合したいデータベースはそもそも同じnodeに作るべきです。
    1. global read-only nodeを追加
        - selectクエリに複数のprimary node上のデータベースを含む場合、PolarDB Proxyは自動的にクエリをglobal read-only nodeに割り当てくれる。
        - 統一的なcluster endpointにアクセスすれば、クエリが自動的に正しいnodeに割り当てくれる。

## 利用シーン
1. SaaSのマルチテナントで、テナントごとにデータベースを持つシナリオ：
    - すべてのデータベースは同じスキーマを持ち、異なるデータベースは異なるテナントのデータを持つ
    - データベースはRWノードにできるだけ均等に分散させる。
    - ユーザーはクラスタエンドポイントを使ってクラスタにアクセスし、PolarDB Proxyはクエリ文を自動的に正しいノードに転送してくれる。

2. ワールド分割のゲームのシナリオ：
ゲームの成長期には、データベースの負荷が大きくなり、増加傾向を示す。 ゲームの成長期にはデータベースが継続的に増加し、その結果、RWノードの負荷が増加します。 ゲーム衰退期には、データベースの負荷が徐々に低下し、データベースの統合が継続的に行われるため、RWノードの負荷も低下します。
single masterアーキテクチャではゲーム成長に伴って、必要な書き込み性能をだんだん満たせなくなります。
multi-masterアーキテクチャでは、ゲームの成長期に、一部のデータベースを新しいノードに素早く切り替えて負荷分散を実現し、ゲームの衰退期には、データベースを少数のノードに素早く集約して運用コストを素早く削減することが可能です。

1. [マイクロサービス](https://xtech.nikkei.com/atcl/nxt/column/18/01956/022500004/)：
マイクロサービスは、独立する機能単位のサービスをAPIでつなぎ、疎結合で運用するアーキテクチャーです。
必然的にデータベースもマイクロサービスごとに分割し、それぞれ持っているわけです。
複数データベースを複数のprimary nodeに分散させることで、全体的な読み書き性能が向上できる見込みです。

::: note info
databaseは一個しか使われてない、あるいはそもそもデータベースは複数に分割できないシナリオでは当然multi-masterは適切ではありません。
:::

# 書き込み性能の向上の効果
クラスタ全体の書き込み能力はprimay nodeの台数にほぼ比例する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28664/f28269f2-df5a-8363-14ac-cb19d1d9716e.png)