MySQL 8.0 GroupReplication Multi-primary modeでのバージョンアップ

#　概要
本記事はMySQL 8.0 GroupReplication Multi-primary modeでのバージョンアップ手法について紹介します。
公式ドキュメントなどでも、さまざまな手法を紹介されていますが、以下のゴールを目指して採用した最適化した流れを紹介しようと思います。

## ゴール
- サービス停止なし
- リスク最小限
- コスト最小限

## 構成
### MySQL構成
- Group Replication
- Multi-primary mode
- 3node構成

### LB構成
writer:
- L4LBを利用(MySQL Routerは利用していない)
- Active/Standby構成で１ノードに書き込みを寄せている
- 1nodeは常にサービスアウトの状態
- L3DSR方式

reader:
- L4LBを利用(MySQL Routerは利用していない)
- Active/Active構成で2ノードを参照する
- 1nodeは常にサービスアウトの状態
- L3DSR方式



### ( 余談 ) なぜMulti-primary modeを採用したか？

弊社のLBはL3DSR方式で実現し、LBのヘルスチェックを操作することによって、サービスアウト(コネクション ドレイン)が可能です。
そのため、ヘルスチェックが失敗しても直ちに接続切れません。
メリット:
- 既存セッションに影響なく、新ノードへ接続を誘導できる
- どのノードでも書き込み可能なため、FOのスピードを限りなく速くすることができる

デメリット:
- サービスアウト直後は２ノード間で書き込む状態になるため、Conflict発生する可能性がある
- そのためアプリ側には再実行ロジックを実装及び、定期的接続し直すような設定が必要

# バージョンアップの流れ

### 1. 作業前の状態：

- バージョンアップ途中で、writeの向き先を意図したnodeに向けるため、LBの優先順位一番高いnode1に向く
- readはservice inの全てのnodeに向く
```mermaid
flowchart TB
    subgraph client
    read(read)
    write(write);
    end
    subgraph DB Cluster
    direction LR
    node1[(node1 - old)];
    node2[(node2 - old)];
    node3[(node3 - old)];
    end
    read --> node1 & node2;
    write --> node1;
    style node1 fill:#f96,stroke:#f66,stroke-width:2px
    style node2 fill:#f96,stroke:#f66,stroke-width:2px
    style node3 fill:#fbb,stroke:#f88,stroke-width:2px,stroke-dasharray: 5 5
```

### 2. node3をバージョンアップ：
- node3をバージョンアップ
- node3でレプリケーションの動作確認を実施
:::note warn
Group Replicationでバージョンが混在する際、自動で`read_only = ON`として立ち上がります。本手順ではMPモードを維持するため、都度`OFF`に戻しています。
:::

```mermaid
flowchart TB
    subgraph client
    read(read)
    write(write);
    end
    subgraph DB Cluster
    direction LR
    node1[(node1 - old)];
    node2[(node2 - old)];
    node3[(node3 - new)];
    end
    read --> node1 & node2;
    write --> node1;
    style node1 fill:#f96,stroke:#f66,stroke-width:2px
    style node2 fill:#f96,stroke:#f66,stroke-width:2px
    style node3 fill:#9f6,stroke:#6f6,stroke-width:2px,stroke-dasharray: 5 5
```

### 3. node2をService Out：
- node3をservice in
- node2をservice out
- readの動作確認を実施し、問題あった場合はロールバックする

```mermaid
flowchart TB
    subgraph client
    read(read)
    write(write);
    end
    subgraph DB Cluster
    direction LR
    node1[(node1 - old)];
    node2[(node2 - old)];
    node3[(node3 - new)];
    end
    read --> node1 & node3;
    write --> node1;
    style node1 fill:#f96,stroke:#f66,stroke-width:2px
    style node2 fill:#f96,stroke:#f66,stroke-width:2px,stroke-dasharray: 5 5
    style node3 fill:#9f6,stroke:#6f6,stroke-width:2px
```

### 3. node2をバージョンアップ：
- node2をバージョンアップ

```mermaid
flowchart TB
    subgraph client
    read(read)
    write(write);
    end
    subgraph DB Cluster
    direction LR
    node1[(node1 - old)];
    node2[(node2 - new)];
    node3[(node3 - new)];
    end
    read --> node1 & node3;
    write --> node1;
    style node1 fill:#f96,stroke:#f66,stroke-width:2px
    style node2 fill:#9f6,stroke:#6f6,stroke-width:2px,stroke-dasharray: 5 5
    style node3 fill:#9f6,stroke:#6f6,stroke-width:2px
```

### 4. node1をService Out：
- node2をservice in
- node1をservice out
- writeの動作確認し、問題あった場合はロールバック

```mermaid
flowchart TB
    subgraph client
    read(read)
    write(write);
    end
    subgraph DB Cluster
    direction LR
    node1[(node1 - old)];
    node2[(node2 - new)];
    node3[(node3 - new)];
    end
    read --> node2 & node3;
    write --> node2;
    style node1 fill:#f96,stroke:#f66,stroke-width:2px,stroke-dasharray: 5 5
    style node2 fill:#9f6,stroke:#6f6,stroke-width:2px
    style node3 fill:#9f6,stroke:#6f6,stroke-width:2px
```

### 5. node1のバージョンアップ
- node3をバージョンアップ
```mermaid
flowchart TB
    subgraph client
    read(read)
    write(write);
    end
    subgraph DB Cluster
    direction LR
    node1[(node1 - new)];
    node2[(node2 - new)];
    node3[(node3 - new)];
    end
    read --> node2 & node3;
    write --> node2;
    style node1 fill:#9f6,stroke:#6f6,stroke-width:2px,stroke-dasharray: 5 5
    style node2 fill:#9f6,stroke:#6f6,stroke-width:2px
    style node3 fill:#9f6,stroke:#6f6,stroke-width:2px
```


# まとめ
## 工夫したポイント

MySQL新バージョンでは前方互換性がない可能性があるため、以下のポイントでリスクを抑えています。
- バージョンアップ途中はなるべくold -> newのレプリケーションを維持して、replication停止など防ぎます。
- 影響小さい順にreplication -> read -> writeの順に徐々に動作確認してリスクを最小限に抑えます。
- writeの向き先を切り替えるタイミングでは、過半数が新バージョンの状態になっているため、旧バージョンへのreplicationが失敗してもクラスタとしては問題ありません。
- 向き先を切り替えるたびに、動作確認及び、既存コネクションが消えるのを待つため、24H猶予を持たせています。

## おまけ
他のバージョンアップ手法との比較結果

| No. | 方法 | Pros | Cons |
|:-:|:-|:-|:-|
| 1 | インプレイスでバージョンアップ | - 手順がシンプル<br>- 追加コストが不要<br>- 環境変更が最小限<br>- ロールバックが容易<br>- エンドポイント切り替え不要 | - 可用性が一時的に下がる |
| 2 | 新ノード追加 & 旧ノードと入れ替え | - 可用性維持<br>- ロールバックが容易<br>- テストが容易 | - コストがかかる<br>- 設定の移行と同期が必要 |
| 3 | 新クラスタ作成 & 旧クラスタと入れ替え | - 可用性維持<br>- 新旧のバージョンを比較テストできる<br>- 新しい環境を最適化できる | - コストが最も高い<br>- 移行作業が複雑<br>- ロールバックするには逆同期の考慮も必要 |

## 終わりに
環境によって、考慮する要素や優先順位の考え方などさまざまですが、参考になれると嬉しいです。