はじめに
MySQLをはじめとしたRDBにはリードレプリカという機能があり、内容を複製した読み込み専用のデータベースを作成することができます。
今回はMySQLでリードレプリカを作ってみようと思います。
リードレプリカを作成する
リードレプリカは以下の手順で作成します。
- レプリカ用のデータベースを作成する
- ライターのデータベースをレプリカのデータベースにダンプ&リストア
- レプリケーション開始のコマンド
今回はdocker-composeでレプリケーションを作成してみます。
docker-compose.yaml
こちらがdocker-compose.yamlです。
Writer/Readerの両DBのmysqld起動時のコマンド引数で--server-idを付与しています。これは各DBサーバに識別子をつけることでどのDBで何が起きているのかを記録するために利用します。ReaderとWriterで被っていなければ必ずしも1と2でなくても問題ありません。
また、GTIDモードをONにするオプションも付与しています。GTIDとは各トランザクションに付与される識別子です。Readerは適用済みのGTIDを記録しておき、まだ持っていないトランザクションだけをWriterから受け取り、反映します。
--enforce-gtid-consistency=ONは、1つのGTIDで安全に記録できない更新を禁止するオプションで、GTIDモードを使う際には必須のようです。
services:
db:
container_name: mysql
image: mysql:8.0.36
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: hogehoge
MYSQL_DATABASE: mydatabase
command:
- --server-id=1
- --log-bin=binlog
- --gtid-mode=ON
- --enforce-gtid-consistency=ON
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 1s
timeout: 5s
retries: 5
db-replica:
container_name: mysql-replica
image: mysql:8.0.36
volumes:
- db_replica_data:/var/lib/mysql
- ./Docker/mysql/replica-init.sh:/docker-entrypoint-initdb.d/replica-init.sh:ro
environment:
MYSQL_ROOT_PASSWORD: hogehoge
REPLICATION_SOURCE_HOST: mysql
REPLICATION_SOURCE_USER: root
REPLICATION_SOURCE_PASSWORD: hogehoge
REPLICATION_DATABASES: mydatabase
command:
- --server-id=2
- --log-bin=binlog
- --gtid-mode=ON
- --enforce-gtid-consistency=ON
- --relay-log=relay-bin
ports:
- "3307:3306"
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 1s
timeout: 5s
retries: 30
volumes:
db_data:
db_replica_data:
replica-init.sh
init.shです。
大まかな流れは冒頭の通りです。
ダンプ時につけている--single-transactionはコマンド実行時点の状態をWriterをロックせずにダンプするために付与しています。--set-gtid-purged=ONを付与してその時点のGTIDの記録もしています。
レプリケーション開始後のSET PERSIST read_only = ON;とSET PERSIST super_read_only = ON;は、リーダーに対する更新を禁止するコマンドです。無印のread_onlyではrootユーザによる更新を許してしまうため、super_read_onlyで禁止をしてます。
#!/bin/bash
set -euo pipefail
SOURCE_HOST="${REPLICATION_SOURCE_HOST:-mysql}"
SOURCE_USER="${REPLICATION_SOURCE_USER:-root}"
SOURCE_PASSWORD="${REPLICATION_SOURCE_PASSWORD:-root}"
DATABASES="${REPLICATION_DATABASES:-mydatabase}"
LOCAL_MYSQL=(mysql --protocol=socket -uroot -p"${MYSQL_ROOT_PASSWORD}")
# ローカルのGTID履歴をクリア
echo "[replica-init] reset local GTID state"
"${LOCAL_MYSQL[@]}" -e "RESET MASTER;"
# レプリカにダンプ&リストア
echo "[replica-init] dump from ${SOURCE_HOST}: ${DATABASES}"
mysqldump -h"${SOURCE_HOST}" -u"${SOURCE_USER}" -p"${SOURCE_PASSWORD}" \
--databases ${DATABASES} \
--single-transaction --routines --triggers --events \
--set-gtid-purged=ON \
| "${LOCAL_MYSQL[@]}"
# レプリケーション開始
echo "[replica-init] start replication"
"${LOCAL_MYSQL[@]}" <<SQL
CHANGE REPLICATION SOURCE TO
SOURCE_HOST='${SOURCE_HOST}',
SOURCE_PORT=3306,
SOURCE_USER='${SOURCE_USER}',
SOURCE_PASSWORD='${SOURCE_PASSWORD}',
SOURCE_AUTO_POSITION=1,
GET_SOURCE_PUBLIC_KEY=1;
START REPLICA;
SET PERSIST read_only = ON;
SET PERSIST super_read_only = ON;
SQL
動作確認
というわけで動作確認してみましょう。
test2という名前のテーブルを用意し、まずはWriterとReaderに何も入ってないことを確認。
mysql> select * from test2;
Empty set (0.00 sec)
mysql> select * from test2;
Empty set (0.00 sec)
続いてWriterにデータをINSERTします。
mysql> INSERT INTO test2 (persistence_object_identifier, name) VALUES ('testUUID', 'testName');
Query OK, 1 row affected (0.01 sec)
mysql> select * from test2;
+-------------------------------+----------+
| persistence_object_identifier | name |
+-------------------------------+----------+
| testUUID | testName |
+-------------------------------+----------+
1 row in set (0.01 sec)
mysql>
Readerにも結果が反映されました!
mysql> select * from test2;
+-------------------------------+----------+
| persistence_object_identifier | name |
+-------------------------------+----------+
| testUUID | testName |
+-------------------------------+----------+
1 row in set (0.00 sec)
おわりに
今回はMySQLのリードレプリカ機能を試してみました。
普段はAuroraなどのマネージドサービスで利用することが多くどうやって作るのかわかっていませんでしたが、実際に作ってみてGTIDなどの仕組みを知ると面白いですね。
ここまでご覧いただきありがとうございました!