読者です 読者をやめる 読者になる 読者になる

CakePHPでMasterとSlaveにリクエストを振り分けるベストプラクティス的なものが知りたい

DBの負荷分散用に「更新はMaster」、「参照は複数のSlaveたちへ」的なことをCakePHP2.0でやろうと思ったのですが、
ハマってしまったのでメモを残します。

やりたいこと

  • 更新はMasterへ、参照は複数のSlaveへ
    • ただし、ざっくりでいい。参照のいくつかがSlaveへ行けばいい。

それを踏まえての実装(失敗)

まず、

  • Slaveに向けておいて、saveのタイミングでMasterへ向ける

というのは切り替わりに問題があった時にSlaveDBを更新してしまい不整合起きてしまって困る。


なので、

  • 普段はMaster、findのタイミングでSlaveに向ける

という方針を立てました。
そうすればもし問題があったとしてもMasterに参照しに行くだけなのでいいだろうと。


ということで考えた案はAppModelに

    public function beforeFind($queryData)
    {
        $this->_useSlaveDb();

        return $queryData;
    }

    public function afterFind($results, $primary = false)
    {
        $this->_useMasterDb();

        return $results;
    }

というメソッドを追加して、

  1. findの前にSlaveDBを見るようにする
  2. findの後にMasterDBを見るように戻す

と切り替えるようにしたもの。


_useSlaveDb()と_useMasterDb()の中では

        $this->useDbConfig = 'master';

みたいな感じでuseDbConfigの値を変更しているだけ。



もし参照系でfind以外のメソッドが使われていたとしてもその時はMasterを見に行くだけだし、
ざっくり負荷分散ならこんなもんでいいかなと思ったらここで問題が。


belongsToなどの関連モデルがある場合にjoinされない

上のAppModelで試してみたところ、joinされなければいけない部分でjoinされずにエラー。



「どこに問題があるのだろう?」と調べてみると、DboSource.phpで自分のuseDbConfigと関連モデルのuseDbConfigを比較している部分がありました。

1021         foreach ($_associations as $type) {
1022             foreach ($model->{$type} as $assoc => $assocData) {
1023                 $linkModel = $model->{$assoc};
1024                 $external = isset($assocData['external']);
1025 
1026                 $linkModel->getDataSource();
1027                 if ($model->useDbConfig === $linkModel->useDbConfig) {
1028                     if ($bypass) {
1029                         $assocData['fields'] = false;
1030                     }
1031                     if (true === $this->generateAssociationQuery($model, $linkModel, $type, $as     soc, $assocData, $queryData, $external, $null)) {
1032                         $linkedModels[$type . '/' . $assoc] = true;
1033                     }
1034                 }
1035             }
1036         }

2つのuseDbConfigが一致したときだけjoinが走る様子。
そりゃそうだ。



思い込みで「$this->useDbConfigを変更したら関連するモデルも変更される」と思ってしまいましたが、
そんなはずはなく、あくまで変わるのは自分のconfigだけ。



なので、今回やりたいことを実装するとなると

  • useDbConfigを書き換えるときに関連モデル一覧をループさせて全てのuseDbConfigも書き換える
  • belongsToなどの関連モデルを指定してあるモデルはuseDbConfigを書き換えない

のどちらかかなぁと。
bindModelやunbindModelも結構使ってるので前者を実装すると切り替え忘れがありそうで怖いのと、
belongsToみたいなjoinが走るものこそSlaveを使いたいからなぁってので後者もイマイチだなぁというのと、
色々悩みどころ。