🌽
DoctrineでMySQLのMaster/Slaveを構築する
はじめに
PHP + Symfony + Doctrine
環境でMySQLのMaster/Slave構成を動かしてみます。
PHP + Symfony + Doctrine
環境は以下の記事で解説した環境を利用します。
また、Master/Slaveの設定に関しても以下の設定を用いています。
環境
- macOS: 13.5
- MySQL: 8.0
- doctrine/orm: ^2.15
コード
本記事のコードはGitHubにありますので、適宜確認ください。
doctrineの設定
を参考に以下のように設定します。
backend/src/config/packages/doctrine.yaml
doctrine:
dbal:
default_connection: default
connections:
default:
driver: 'pdo_mysql'
charset: utf8mb4
default_table_options:
charset: utf8mb4
collate: utf8mb4_unicode_ci
url: '%env(resolve:DATABASE_URL)%'
# Master/Slave configuration
replicas:
slave1:
url: '%env(resolve:SLAVE_DATABASE_URL)%'
charset: utf8mb4
環境変数は以下のように設定してください。
backend/src/env.local
DATABASE_URL="mysql://zenn_master:zenn_master@mysql_master:3306/zenn_db?serverVersion=8"
SLAVE_DATABASE_URL="mysql://zenn_slave:zenn_slave@mysql_slave:3306/zenn_db?serverVersion=8"
動作確認
新しくDBを作り直しているのでマイグレーションから実行します。
# phpのdockerに入ってマイグレーションを実施
$ docker compose exec -it php /bin/bash
> symfony console doctrine:migrations:migrate
マイグレーションが完了したらAPI Platformでbookを挿入します。
master/slaveのDBの状態を確認します。
# master側に変更があったか確認
$ docker exec -it mysql_master mysql -uzenn_master -pzenn_master
mysql> use zenn_db
mysql> select * from book;
Empty set (0.01 sec)
# ここでPOSTなどで挿入処理を実行
mysql> select * from book;
+----+-------+--------+
| id | title | author |
+----+-------+--------+
| 1 | test | author |
+----+-------+--------+
1 row in set (0.01 sec)
# slave側でも同様に変更があったか確認します。
$ docker exec -it mysql_slave mysql -uzenn_slave -pzenn_slave
# (同じ結果のため省略)
これでMaster/Slaveの構築ができました。
Master/Slaveの挙動について
見つけられてないだけかもですが、あまり動作に関するドキュメントは無く、以下のコードに書かれているコメントしか見つけられませんでした。
- Replica if primary was never picked before and ONLY if 'getWrappedConnection'
or 'executeQuery' is used.- Primary picked when 'executeStatement', 'insert', 'delete', 'update', 'createSavepoint',
'releaseSavepoint', 'beginTransaction', 'rollback', 'commit' or 'prepare' is called.- If Primary was picked once during the lifetime of the connection it will always get picked afterwards.
- One replica connection is randomly picked ONCE during a request.
動作としては、以下のようになるみたいです。
- レプリカ(Slave)が選ばれるのは、プライマリ(Master)が直前に選択されておらず、かつ
getWrappedConnection
かexecuteQuery
を実行した場合。 - プライマリは更新系の処理が走ると選択される
- 一度プライマリが選択されると、以降の接続では常にプライマリが選択される。
- レプリカへの接続はランダムに選ばれる
replicaにMasterを追加する
現状のままだと、SELECT系のクエリのほとんどはSlaveのDBに行ってしまいます。それだと、負荷分散があまりできていないため、以下の3行を追記することでMasterをreplicaにできます。
backend/src/config/packages/doctrine.yaml
doctrine:
dbal:
default_connection: default
connections:
default:
driver: 'pdo_mysql'
charset: utf8mb4
default_table_options:
charset: utf8mb4
collate: utf8mb4_unicode_ci
url: '%env(resolve:DATABASE_URL)%'
# Master/Slave configuration
replicas:
slave1:
url: '%env(resolve:SLAVE_DATABASE_URL)%'
charset: utf8mb4
+ slave2:
+ url: '%env(resolve:DATABASE_URL)%'
+ charset: utf8mb4
SELECTがSlaveを参照しているかの確認
一応、SELECTなどのクエリがちゃんとSlaveを参照しているか確認します。
リポジトリのディレクトリ直下で以下のコマンドを打ってmysql_slave
のコンテナに入ります。
$ docker compose exec -it mysql_slave /bin/bash
API Platformでbookの一覧を取得します。
Slaveのログを確認すると、アクセスがあることを確認できました。
# コンテナ内にて以下のログを出力する
$ cat var/log/mysql-general.log
(前略)
2023-08-22T13:57:34.607613Z 114 Connect zenn_slave@172.18.0.3 on zenn_db using TCP/IP
2023-08-22T13:57:34.608335Z 114 Query SELECT count(b0_.id) AS sclr_0 FROM book b0_
2023-08-22T13:57:34.612539Z 114 Query SELECT b0_.id AS id_0, b0_.title AS title_1, b0_.author AS author_2 FROM book b0_ ORDER BY b0_.id ASC LIMIT 30
2023-08-22T13:57:34.615568Z 114 Quit
おわりに
Master/Slave構成をdoctrineで実現してみました。
誰かの参考になれば幸いです!
Discussion