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; }
というメソッドを追加して、
- findの前にSlaveDBを見るようにする
- 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を使いたいからなぁってので後者もイマイチだなぁというのと、
色々悩みどころ。