ローカルのDockerでMySQLのバイナリーログレプリケーションをやってみる
自己紹介
こんにちは!
株式会社ココナラSREチームのかとりょーです。
この記事に書くこと
- ローカル環境にDockerでMySQLのバイナリーログレプリケーションを実行する環境を作成する方法
- バイナリーログレプリケーションの方法
- 切り戻しのための逆方向のバイナリーログレプリケーションの方法
この記事を書こうと思った背景
ココナラのデータベースは基本的にAWSのAurora MySQLを利用しています。
現状サービス停止してバージョンアップ対応していますが、ユーザーへの影響も考慮し、オンラインのまま安全にバージョンアップをする方法を検討しています。
現段階では下記のどちらかの方法でバージョンアップすることを考えています。(今回バージョンアップの手法の説明はしませんが、手法についてfreeeさんとサイバーエージェントさんの記事を参考にさせていただいたのでリンクをつけます)
オンラインでバージョンアップする場合でも、新バージョンのMySQLでクリティカルなエラーが発生した場合に備え、切り戻しができる状態にしておくことは重要だと思います。
どちらの方法でもクリティカルなエラーに対してバイナリーログレプリケーションを利用し切り戻しができるよう対策しています。
これまでAWS上のマネージドなデータベースを主に扱ってきたためバイナリーログレプリケーションは馴染みのない技術でした。なので、ローカルで気軽に検証できる環境が欲しいと思いこの記事を書いています。
環境構築
今回はDockerを使ってローカルにMySQLを作成します。
また、マイナーバージョンアップのためのバイナリーログレプリケーションを想定しているので、MySQL 8.0.40と8.0.42の2つを起動します。
2025年11月現在、ココナラではAurora MySQLの3.09を利用しています。次はLTSの3.10にバージョンアップすることを予定しており、これに対応するMySQLのバージョンが8.0.40と8.0.42です。
この記事では8.0.40 -> 8.0.42のレプリケーションを順方向、8.0.42 -> 8.0.40のレプリケーションを逆方向として扱います。
バージョンアップ前の準備として順方向のレプリケーションをし、バージョンアップ後の切り戻しの備えとして逆方向のレプリケーションを行います。
ディレクトリ構成
repl_db/
├── docker-compose.yml
├── mysql_8040/
│ ├── Dockerfile
│ └── my.cnf
└── mysql_8042/
├── Dockerfile
└── my.cnf
設定ファイル
relay-logなどは本来プライマリ側に不要ですが、逆方向のレプリケーションを前提にしているため、server-id以外は同じ内容にしています。
アプリケーションへの影響も考慮し、GTIDは有効化せずバイナリーログレプリケーションします。
mysql_8040/my.cnf
[mysqld]
server-id=1 # ユニークな必要がある
log-bin=/var/lib/mysql/mysql-bin # バイナリーログの場所
relay-log=/var/lib/mysql/mysql-relay-bin # リレーログの場所
relay_log_purge=1 # 不要なリレーログを削除する設定
binlog_format=ROW
binlog_row_image=FULL
log_replica_updates=ON # この設定がないとレプリカにバイナリーログを書き込まないので、逆方向にレプリケーションできない
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
bind-address=0.0.0.0
mysql_8042/my.cnf
[mysqld]
server-id=2
log-bin=/var/lib/mysql/mysql-bin
relay-log=/var/lib/mysql/mysql-relay-bin
relay_log_purge=1
binlog_format=ROW
binlog_row_image=FULL
log_replica_updates=ON
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
bind-address=0.0.0.0
Dockerfile
mysql_8040/Dockerfile
FROM mysql:8.0.40
ENV TZ=Asia/Tokyo
COPY my.cnf /etc/mysql/conf.d/my.cnf
mysql_8042/Dockerfile
FROM mysql:8.0.42
ENV TZ=Asia/Tokyo
COPY my.cnf /etc/mysql/conf.d/my.cnf
docker-compose.yml
services:
mysql_8040:
build:
context: ./mysql_8040
dockerfile: Dockerfile
container_name: mysql_8040
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: secret
volumes:
- ./mysql_8040/my.cnf:/etc/mysql/conf.d/my.cnf
- mysql_8040_data:/var/lib/mysql
networks:
- mysql
mysql_8042:
build:
context: ./mysql_8042
dockerfile: Dockerfile
container_name: mysql_8042
ports:
- "3307:3306"
environment:
MYSQL_ROOT_PASSWORD: secret
volumes:
- ./mysql_8042/my.cnf:/etc/mysql/conf.d/my.cnf
- mysql_8042_data:/var/lib/mysql
networks:
- mysql
networks:
mysql:
driver: bridge
volumes:
mysql_8040_data:
mysql_8042_data:
dockerの起動
docker compose build
docker compose up -d
これで環境構築は完了です。
検証のシナリオ
下記のようなシナリオを想定しています。
- 現行バージョンから新バージョンに対してバイナリーログレプリケーションでデータを同期する
- 同期が完了したらレプリケーションを止め、新バージョンのMySQLをプライマリに昇格する
- 切り戻しのために新バージョンから旧バージョンのMySQLに対して逆方向にバイナリーログレプリケーションする
バイナリーログレプリケーション
最初は8.0.40をプライマリ、8.0.42をレプリカとします。
プライマリである8.0.40に接続し、レプリケーション用のユーザーを作成します。
mysql -h 127.0.0.1 -P 3306 -uroot -psecret
mysql> CREATE USER 'repl_user'@'%' IDENTIFIED WITH mysql_native_password BY 'secret';
Query OK, 0 rows affected (0.07 sec)
# レプリカとして接続するための権限を付与する
mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'%';
Query OK, 0 rows affected (0.01 sec)
# 権限設定のリロード
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.01 sec)
現在のプライマリのバイナリーログのポジションを確認します。
FileとPositionの値をレプリケーションで利用するのでメモしてください。
# 8.0.40(プライマリ)
mysql> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000004 | 829 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.01 sec)
レプリカである8.0.42に接続しレプリケーションを実行します。
mysql -h 127.0.0.1 -P 3307 -uroot -psecret
# 8.0.40(プライマリ)をソースとして設定
mysql> CHANGE REPLICATION SOURCE TO
SOURCE_HOST='mysql_8040',
SOURCE_PORT=3306,
SOURCE_USER='repl_user',
SOURCE_PASSWORD='secret',
SOURCE_LOG_FILE='mysql-bin.000004', -- プライマリのSHOW MASTER STATUSで確認したFile
SOURCE_LOG_POS=829; -- プライマリのSHOW MASTER STATUSで確認したPosition
Query OK, 0 rows affected, 1 warning (0.06 sec)
# レプリケーションを開始
mysql> START REPLICA;
Query OK, 0 rows affected (0.06 sec)
レプリケーションされていることを確かめるために、プライマリ側にデータを入れていきます。
# 8.0.40(プライマリ)
mysql> CREATE DATABASE IF NOT EXISTS `example_db`;
Query OK, 1 row affected (0.01 sec)
mysql> USE example_db;
mysql> CREATE TABLE IF NOT EXISTS `users` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL,
`age` INT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
Query OK, 0 rows affected (0.06 sec)
mysql> INSERT INTO users (name, age) VALUES ('Alice', 25);
Query OK, 1 row affected (0.04 sec)
mysql> INSERT INTO users (name, age) VALUES ('Bob', 30);
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO users (name, age) VALUES ('Charlie', 35);
Query OK, 1 row affected (0.01 sec)
レプリカに接続し、データがレプリケーションされたことを確認します。
# 8.0.42(レプリカ)
mysql> USE example_db;
Database changed
mysql> SHOW TABLES;
+----------------------+
| Tables_in_example_db |
+----------------------+
| users |
+----------------------+
1 row in set (0.01 sec)
mysql> SELECT * FROM users;
+----+---------+------+---------------------+---------------------+
| id | name | age | created_at | updated_at |
+----+---------+------+---------------------+---------------------+
| 1 | Alice | 25 | 2025-11-14 18:43:26 | 2025-11-14 18:43:26 |
| 2 | Bob | 30 | 2025-11-14 18:43:26 | 2025-11-14 18:43:26 |
| 3 | Charlie | 35 | 2025-11-14 18:43:27 | 2025-11-14 18:43:27 |
+----+---------+------+---------------------+---------------------+
3 rows in set (0.01 sec)
mysql> SHOW REPLICA STATUS\G
*************************** 1. row ***************************
Replica_IO_State: Waiting for source to send event
Source_Host: mysql_8040
Source_User: repl_user
Source_Log_File: mysql-bin.000004
Read_Source_Log_Pos: 2446 <-- プライマリのバイナリーログの読み取り位置
Relay_Log_File: mysql-relay-bin.000002
Relay_Log_Pos: 1943
Replica_IO_Running: Yes <-- IOスレッドが動作中(バイナリーログの受信)
Replica_SQL_Running: Yes <-- SQLスレッドが動作中(クエリの実行)
Last_Errno: 0
Last_Error:
Seconds_Behind_Source: 0 <-- レプリケーションの遅延(秒)
Last_IO_Errno: 0 <-- IOスレッドのエラーメッセージ
Last_IO_Error:
Last_SQL_Errno: 0 <-- SQLスレッドのエラーメッセージ
Last_SQL_Error:
Source_Server_Id: 1
1 row in set (0.00 sec)
SHOW REPLICA STATUSの結果は重要な項目のみ残し省略したものを記載しています。
レプリケーションの遅延やエラーがないので問題なくレプリケーション成功しています。
逆方向のバイナリーログレプリケーションができる条件として、
Seconds_Behind_Sourceが0で遅延がないことが重要です。
レプリケーションの停止
逆方向のバイナリーログレプリケーションをする前に、レプリカのバイナリーログの最終ポジションを確認し、その後レプリケーションを停止します。
後ほど利用するのでFileとPositionの値は記録します。
# 8.0.42(レプリカ)
mysql> STOP REPLICA;
Query OK, 0 rows affected (0.02 sec)
mysql> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000003 | 4621 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
# これまでのレプリケーションの情報を削除
mysql> RESET REPLICA ALL;
Query OK, 0 rows affected (0.04 sec)
mysql> SHOW REPLICA STATUS\G
Empty set (0.00 sec)
これで完全にバイナリーログレプリケーションを停止しました。
切り戻しのための逆方向へのバイナリーログレプリケーション
クリティカルなエラーが発生した場合の切り戻しを想定し、逆方向へのバイナリーログレプリケーションを行います。
これからはプライマリが8.0.42でレプリカを8.0.40とし、
8.0.42が8.0.40から最後に受信したポジションからバイナリーログレプリケーションを再開します。
新プライマリに接続し、レプリケーションユーザーを再作成します。
# 8.0.42(新プライマリ)
mysql> DROP USER IF EXISTS 'repl_user'@'%';
Query OK, 0 rows affected, 1 warning (0.04 sec)
mysql> CREATE USER 'repl_user'@'%' IDENTIFIED WITH mysql_native_password BY 'secret';
Query OK, 0 rows affected (0.00 sec)
mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'%';
Query OK, 0 rows affected (0.00 sec)
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.01 sec)
新レプリカに接続し、レプリケーションを再開します。
# 8.0.40(新レプリカ)
# 8.0.42(新プライマリ)をソースとして設定
mysql> CHANGE REPLICATION SOURCE TO
SOURCE_HOST='mysql_8042',
SOURCE_PORT=3306,
SOURCE_USER='repl_user',
SOURCE_PASSWORD='secret',
SOURCE_LOG_FILE='mysql-bin.000003', -- SHOW MASTER STATUSで確認したFile
SOURCE_LOG_POS=4621; -- SHOW MASTER STATUSで確認したPosition
Query OK, 0 rows affected, 1 warning (0.05 sec)
mysql> START REPLICA;
Query OK, 0 rows affected (0.06 sec)
新プライマリに接続し、データを入れていきます。
# 8.0.42(新プライマリ)
mysql> INSERT INTO users (name, age) VALUES ('Ivy', 29);
Query OK, 1 row affected (0.02 sec)
mysql> INSERT INTO users (name, age) VALUES ('Jack', 33);
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO users (name, age) VALUES ('Kate', 26);
Query OK, 1 row affected (0.01 sec)
新レプリカに接続し、レプリケーションされていることを確認します。
# 8.0.40(新レプリカ)
mysql> SELECT * FROM users;
+----+---------+------+---------------------+---------------------+
| id | name | age | created_at | updated_at |
+----+---------+------+---------------------+---------------------+
| 1 | Alice | 25 | 2025-11-14 18:43:26 | 2025-11-14 18:43:26 |
| 2 | Bob | 30 | 2025-11-14 18:43:26 | 2025-11-14 18:43:26 |
| 3 | Charlie | 35 | 2025-11-14 18:43:27 | 2025-11-14 18:43:27 |
| 4 | Ivy | 29 | 2025-11-14 19:46:36 | 2025-11-14 19:46:36 |
| 5 | Jack | 33 | 2025-11-14 19:46:41 | 2025-11-14 19:46:41 |
| 6 | Kate | 26 | 2025-11-14 19:46:47 | 2025-11-14 19:46:47 |
+----+---------+------+---------------------+---------------------+
6 rows in set (0.00 sec)
mysql> SHOW REPLICA STATUS\G
*************************** 1. row ***************************
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
Seconds_Behind_Source: 0
Last_IO_Error:
Last_SQL_Error:
逆方向へのバイナリーログレプリケーションも成功していました!
Dockerが不要になったらボリュームごと削除しましょう。
docker compose down -v
終わりに
今回は、ローカル環境でDockerを利用してMySQLのバイナリーログレプリケーションを試してみました。
MySQLのレプリケーションがどのように行われているか理解が進んでよかったです。
ココナラでは、一緒に事業のグロースを推進していただける様々な領域のエンジニアを募集しています。
インフラ・SRE領域だけでなく、フロントエンド領域・バックエンド領域などでも積極的にエンジニア採用を行っています。
少しでも興味を持たれた方がいましたら、エンジニア採用ページをご覧ください。
特にテックブログをご覧いただいて、ぜひ話をしてみたい!と思われた方はカジュアル面談応募フォームからご応募をお願いします。
Discussion