🌽

DoctrineでMySQLのMaster/Slaveを構築する

2023/08/22に公開

はじめに

PHP + Symfony + Doctrine環境でMySQLのMaster/Slave構成を動かしてみます。
PHP + Symfony + Doctrine環境は以下の記事で解説した環境を利用します。

https://zenn.dev/gsy0911/articles/ab193f6eba39dc

また、Master/Slaveの設定に関しても以下の設定を用いています。

https://zenn.dev/gsy0911/articles/2287b8aa75d706

環境

  • macOS: 13.5
  • MySQL: 8.0
  • doctrine/orm: ^2.15

コード

本記事のコードはGitHubにありますので、適宜確認ください。

doctrineの設定

https://www.doctrine-project.org/projects/doctrine-bundle/en/latest/configuration.html

を参考に以下のように設定します。

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の挙動について

見つけられてないだけかもですが、あまり動作に関するドキュメントは無く、以下のコードに書かれているコメントしか見つけられませんでした。

https://github.com/doctrine/dbal/blob/1d6b35150713ec32021443456b825366297a9cb1/src/Connections/PrimaryReadReplicaConnection.php#L24-L36

  1. Replica if primary was never picked before and ONLY if 'getWrappedConnection'
    or 'executeQuery' is used.
  2. Primary picked when 'executeStatement', 'insert', 'delete', 'update', 'createSavepoint',
    'releaseSavepoint', 'beginTransaction', 'rollback', 'commit' or 'prepare' is called.
  3. If Primary was picked once during the lifetime of the connection it will always get picked afterwards.
  4. One replica connection is randomly picked ONCE during a request.

動作としては、以下のようになるみたいです。

  1. レプリカ(Slave)が選ばれるのは、プライマリ(Master)が直前に選択されておらず、かつgetWrappedConnectionexecuteQueryを実行した場合。
  2. プライマリは更新系の処理が走ると選択される
  3. 一度プライマリが選択されると、以降の接続では常にプライマリが選択される。
  4. レプリカへの接続はランダムに選ばれる

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で実現してみました。
誰かの参考になれば幸いです!

GitHubで編集を提案

Discussion