Kentoka.com

SymfonyのDB接続でレプリケーションする設定

こんにちは。

個人開発をしているとき、将来のことを考えて遅々として進まないことありますよね。 これを捕らぬ狸の皮算用といいます。

この記事はSymfonyでAPIサーバー構築をしている時に、DB接続のレプリケーション選択がどのように動いているのかを調べたものです。 doctrine-bundle:2.7.2 時点のソースコードをもとにしています。

どうやればレプリケーションをうまく使えるのか

DoctrineBundle Configuration Reference(2.7.2時点)に書いてある通り設定するだけで問題なく動作します。

いつも通りコネクションを設定したうえで、doctrine.dbal.replicas.[replicaName](複数のコネクションを設定する場合は doctrine.dbal.connections.[connectionName].replicas.[replicaName])にレプリケーションを受けているDBの設定を記述してください。 設定さえしておけば、あとはフレームワーク側より先の処理でいい感じに接続先DB選択などをしてくれます。

いかがでしたか。設定するだけでいい感じにしてくれるなんてDoctrineは優秀ですね。

仕組み

「いい感じに接続先DBを選択してくれる」と言われて「はいそうですか」と何も考えずに使うのは難しいですよね。 実際のところどのように動いているのかを少し掘ってみたので、再度確認するときのフックになる程度に書いておきます。

設定

Doctrine\Bundle\DoctrineBundle\DependencyInjectio\DoctrineExtensionに、設定読み取りとサービスコンテナへの登録処理が書かれています。

dbal のコネクションに関する設定の中で replicas1 といった設定を検知したとき、wrapper_classDoctrine\DBAL\Connections\PrimaryReadReplicaConnection2が設定されます。

この PrimaryReadReplicaConnection が名前の通り、プライマリかレプリカへの接続を自動で選択するコネクションクラスです。

接続先DB選択

前述したとおり PrimaryReadReplicaConnection とその周囲が接続先DBを選択しています。 その選択基準は PrimaryReadReplicaConnection のコメントにしっかり書かれていました。 以下のリストはソースコード中のコメントを要約して追記したものです。

  1. 一度もプライマリに接続したことがない時点で Connection::getWrappedConnection()Connection::executeQuery() を通じて実行したクエリはレプリカに送信される
    • NativeQueryを実行したり、DoctrineのDriverを直接触るようなときにこのケースに引っかかります
  2. Connection::executeStatement()Connection::beginTransaction() などを通じたクエリの実行はプライマリに送信される
    • QueryBuilderを経由した場合などはこのケースに当たります
      • Doctrine\ORM\Query\SqlWalker で実行するクエリに合わせて接続先が自動で選択されます
      • ただし SELECT ~ FOR UPDATE はレプリカに流れてしまいそうな雰囲気がありました3
  3. 一度でもプライマリに接続すると、以降のクエリ実行はすべてプライマリで行われる
    • この挙動は、設定の keep_replica によって変更可能
      • keep_replica: false: プライマリへの接続を確立したとき、レプリカ接続としても確立したプライマリへの接続を使うようになる
        • トランザクションを張っていろいろした後に、トランザクション外でSELECTクエリを実行してもプライマリに送信される
        • トランザクションを張らずに更新系クエリを実行した後のSELECTクエリはプライマリに送信される
      • keep_replica: true: プライマリへの接続を確立したとしても、レプリカへの接続へは影響を及ぼさない
        • トランザクションを張っていろいろした後に、トランザクション外でSELECTクエリを実行するとレプリカに送信される
  4. レプリカでクエリを実行する場合、レプリカ設定が複数存在するなら接続時にランダムに1つ選択される。接続を明示的に閉じるまで選択されたレプリカでクエリが実行される

結論

要するに、よく考えられているなぁということです。

脚注

  1. 昔は slaves というキーでした。

  2. 昔は MasterSlaveConnection というクラス名でした。

  3. FOR UPDATEしてレプリカにクエリを流すケースが思いつきません