Azure Database for MySQL を 5.7 から 8.0 にアップグレード(停止時間短め)

Azure Database for MySQL で [MySQL 8.0 が使えるようになりました](https://azure.microsoft.com/ja-jp/updates/azure-database-for-mysql-support-for-mysql-8-0-is-now-available/ "Azure Database for MySQL での MySQL 8.0 の提供開始")。ただし、[ドキュメント](https://docs.microsoft.com/ja-jp/azure/mysql/concepts-limits#server-version-upgrades "サーバー バージョンのアップグレード")によると、既存のMySQL 5.7をアップグレードアップするには、ダンプを復元する必要があり停止時間が長くなってしまいます。[読み取りレプリカ](https://docs.microsoft.com/ja-jp/azure/mysql/concepts-read-replicas "Azure Database for MySQL の読み取りレプリカ
")と[データイン レプリケーション](https://docs.microsoft.com/ja-jp/azure/mysql/concepts-data-in-replication "データを Azure Database for MySQL にレプリケートする")を利用することで、データ量に関係なく、停止時間を短めにアップグレードする事が可能になります。
# 全体の手順
手順の概要は以下となります。

1. 現状のMySQL(以下A)の読み取りレプリカ(以下B)を作成します。
2. レプリケーションを停止して、Bをスタンドアロン サーバーとします。
3. 新しく、version 8.0 の Azure Database for MySQL(以下C) を作成します。
4. サーバB からダンプを作成し、サーバCにインポートします。
5. サーバBを削除します。
6. MYSQLの機能を利用して、A から C へのデータレプリケーションを作成します。
7. C のレプリケーション処理がAに追いついたら、アプリケーションを停止してすべてのトランザクションを反映させます。
8. A から C のレプリケーションを停止して、アプリケーションの接続先を C に変更します。
9. A を削除します。

# 各手順の詳細
以下は上記の1～9の手順の詳細です。
## 現状のMySQL(以下A)の読み取りレプリカ(以下B)を作成します。
### 確認用テーブルの作成
レプリカを作る前にテスト用に簡単なテーブルを作成します。本番環境などでは必要ありません。

```SQL:Aで実行
CREATE DATABASE mydb;
USE mydb;

-- Create a table and insert rows
DROP TABLE IF EXISTS employee;
CREATE TABLE employee (id serial PRIMARY KEY, name VARCHAR(50), salary INTEGER);
INSERT INTO employee (name, salary) VALUES ('Taro', 200000);
INSERT INTO employee (name, salary) VALUES ('Jiro', 300000);
INSERT INTO employee (name, salary) VALUES ('Hanako', 400000);
```
### レプリカの作成
次にAzure Portal からレプリカを作成します。レプリカの作成方法は[ドキュメント](https://docs.microsoft.com/ja-jp/azure/mysql/howto-read-replicas-portal#create-a-read-replica "読み取りレプリカを作成します")を参照下さい。

### レプリカが正しく作成できている事の確認
作成完了後に、レプリカに接続してデータを検索しておきます。

``` SQL:レプリカ(B)で実行
use mydb;
select * from employee;
```
結果

|id|name|salary|
|--:|:--|--:|
|1|Taro|200000|
|2|Jiro|300000|
|3|Hanako|400000|
## レプリケーションを停止して、Bをスタンドアロン サーバーとします。
### テーブルをロックして静止点の作成
レプリケーションを停止する前に、テーブルをロックして更新されないようにします。

```SQL:マスター(A)で実行
FLUSH TABLES WITH READ LOCK;
```
### マスターとスレーブでデータのずれが無い事の確認
マスターの状態を確認

```SQL:マスター(A)で実行
SHOW MASTER STATUS;
```
結果例

|File|Position|...|
|:--|--:|:--|
|mysql-bin.000001|5371|...|

スレーブの状態を確認

```SQL:スレーブ(B)で実行
SHOW SLAVE STATUS;
```
結果例

|Slave_IO_State|Master_Host|Master_User|Master_Port|Connect_Retry|Master_Log_File|Read_Master_Log_Pos|...|
|:--|:--|:--|:--|:--|:--|:--|:--|
|Waiting for master to send event|mysql-a.mysql.database.azure.com|az00fb60b015ca31@mysql-a|3306|60|mysql-bin.000001|5371|...|
プライマリーの Position とスレーブの Read_Master_Log_Pos が同じ値(上記の例では 5371) なり、全てのログが転送されている事を確認します。`SHOW MASTER STATUS;`の結果は後で使いますので、保存しておいてください。
### レプリケーションの停止
[ドキュメント](https://docs.microsoft.com/ja-jp/azure/mysql/howto-read-replicas-portal#stop-replication-to-a-replica-server "レプリカ サーバーへのレプリケーションを停止します。")の手順でレプリケーションを停止します。この操作により、レプリカがスタンドアロンサーバとなります。
レプリケーションの停止が完了したら、プライマリーのテーブルロックを解除します。

```SQL:マスター(A)で実行
UNLOCK TABLES;
```
Portal の完了を待たずに、スレーブ(B)側で `SHOW SLABE STATUS;` を定期的に実行して結果が返ってこなくなったタイミングでロック解除しても良いかと思います。

## 新しく、version 8.0 の Azure Database for MySQL(以下C) を作成します。
[ドキュメント](https://docs.microsoft.com/ja-jp/azure/mysql/quickstart-create-mysql-server-database-using-azure-portal#create-an-azure-database-for-mysql-server "Azure Database for MySQL サーバーの作成")の手順に従って、MySQL 8.0 のAzure Database for MySQL(以下C)を作成します。

## サーバB からダンプを作成し、サーバCにインポートします。
[ドキュメント](https://docs.microsoft.com/ja-jp/azure/mysql/concepts-migrate-import-export#import-and-export-by-using-mysql-workbench "https://docs.microsoft.com/ja-jp/azure/mysql/concepts-migrate-import-export#import-and-export-by-using-mysql-workbench")の手順に従って、サーバB からダンプを作成し、サーバCにインポートします。ドキュメントは MySQL Workbench を利用する方法ですが、お好きな方法で行って頂いて大丈夫です。
## サーバBを削除します。
[ドキュメント](https://docs.microsoft.com/ja-jp/azure/mysql/quickstart-create-mysql-server-database-using-azure-portal#clean-up-resources "リソースをクリーンアップする")の手順に従って、サーバB を削除します。(リソースグループ全部消さないでくださいね)
## MYSQLの機能を利用して、A から C へのデータレプリケーションを作成します。
### サーバAにレプリケーション用ユーザの作成
```SQL:マスター(A)で実行
CREATE USER 'repuser'@'%' IDENTIFIED BY 'Himitu999';
GRANT REPLICATION SLAVE ON *.* TO 'repuser'@'%';
```
### レプリケーションの開始
以下のコマンドを実行してレプリケーションを開始します。`mysql.az_replication_change_master` コマンドの「マスター サーバーのホスト名」は適時変更してください。「バイナリ ログ ファイルの名前」「バイナリログの位置」はサーバBのレプリケーションを解除する前に、`SHOW MASTER STATUS;`で取得した値に変更してください。



```SQL:スレーブ(C)で実行
SET @cert1 = '-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
-----END CERTIFICATE-----';

CALL mysql.az_replication_change_master('<server-a>.mysql.database.azure.com', 'repuser@<server-a>', 'Himitu999', 3306, 'mysql-bin.000001', 5371, @cert1);
CALL mysql.az_replication_start;
SHOW SLAVE STATUS;
```
万が一エラーが発生した場合は、以下のコマンドでスキップ可能です。

```SQL:スレーブ(C)で実行
CALL mysql.az_replication_skip_counter;
CALL mysql.az_replication_start;
SHOW SLAVE STATUS;
```
## C のレプリケーション処理がAに追いついたら、アプリケーションを停止してすべてのトランザクションを反映させます。
### アプリケーションを停止
### 全てのトランザクションが反映されている事を確認。
マスターの状態を確認

```SQL:マスター(A)で実行
SHOW MASTER STATUS;
```
結果例

|File|Position|...|
|:--|--:|:--|
|mysql-bin.000001|12766|...|

スレーブの状態を確認

```SQL:スレーブ(C)で実行
SHOW SLAVE STATUS;
```
結果例

|Slave_IO_State|Master_Host|Master_User|Master_Port|Connect_Retry|Master_Log_File|Read_Master_Log_Pos|...|
|:--|:--|:--|:--|:--|:--|:--|:--|
|Waiting for master to send event|mysql-a.mysql.database.azure.com|repuser@mysql-a|3306|60|mysql-bin.000001|12766|...|
プライマリーの Position とスレーブの Read_Master_Log_Pos が同じ値(上記の例では 12766) なり、全てのログが転送されている事を確認します。

## A から C のレプリケーションを停止して、アプリケーションの接続先を C に変更します。
### レプリケーションの停止
```SQL:スレーブ(C)で実行
CALL mysql.az_replication_stop;
SHOW SLAVE STATUS;
```
### レプリケーション関係の解除
```SQL:スレーブ(C)で実行
CALL mysql.az_replication_remove_master;
SHOW SLAVE STATUS;
```
### アプリケーションの接続先の変更

## A を削除します。
[ドキュメント](https://docs.microsoft.com/ja-jp/azure/mysql/quickstart-create-mysql-server-database-using-azure-portal#clean-up-resources "リソースをクリーンアップする")の手順に従って、サーバA を削除します。(リソースグループ全部消さないでくださいね)

# おわりに
上記の手順でシンプルなデータベースでは問題なく処理できる事を確認していますが、お気づきの点などあればコメント頂ければ幸いです。