【この記事はbaserCMS Advent Calendar 2014 の記事です。】

こんにちわ、1年以上ブログ書いてなかったけど、久々に記事書きました。
最近はbaserCMSでガッツリ開発しています。

先日、baserCMS(Ver.3.0.6.1)を使って中規模システムを構築したのでその時のあーだこーだをまとめみようとおもいます。今回はその中でもデータベースをレプリケーション設定した時のbaserCMSの設定についてです。

システム構成としては、

  • フロントエンド: Nginx
  • バックエンド: Apache(baserCMS) x 2
  • データベース: MySQL x 2(マルチマスタ)

という感じなんですが、とりあえず、今回はわかりやすいように下記のような構成で、負荷分散のための更新系と参照系でデータベースサーバを分けてみたいとおもいます。

VirtualBoxなんかの仮想環境を使うと、複数サーバ構築のテストも楽勝ですよ!

今回のシステム構成

  • Webサーバ: Apache(baserCMS)
  • データベースサーバ#1: MySQLスレーブ(更新系)
  • データベースサーバ#2: MySQLマスタ (参照系)

データベースのMySQLはマスタ → スレーブでレプリケーションさせているので、マスタ側を更新すると、スレーブ側も更新される状態です。特に特殊な処理はしていません。

baserCMSは標準では上記の構成だとどちらかのサーバにしか接続できないのですが、CakePHPの記事を参考に少しカスタマイズして対応してみます。

参考記事 http://oh-sky.hatenablog.com/entry/2013/12/14/111725

今回の構築例

Apache(baserCMS)

Host: 192.168.0.233

MySQL Master

Host: 192.168.0.234
ID: foo
PW: bar
DB: basercms

MySQL Slave

Host: 192.168.0.235
ID: foo
PW: bar
DB: basercms

データベース設定

データベース接続の設定を変更してマスタ、スレーブの設定を行います。

app/Config/database.php

変更前
<?php
//
// Database Configuration File created by baserCMS Installation
//
class DATABASE_CONFIG {
public $baser = array(
'datasource' => 'Database/BcMysql',
'persistent' => false,
'host' => '192.168.0.234',
'port' => '3306',
'login' => 'foo',
'password' => 'bar',
'database' => 'basercms',
'schema' => '',
'prefix' => 'mysite_',
'encoding' => 'utf8'
);
public $plugin = array(
'datasource' => 'Database/BcMysql',
'persistent' => false,
'host' => '192.168.0.234',
'port' => '3306',
'login' => 'foo',
'password' => 'bar',
'database' => 'basercms',
'schema' => '',
'prefix' => 'mysite_pg_',
'encoding' => 'utf8'
);
public $test = array(
'datasource' => 'Database/BcMysql',
'persistent' => false,
'host' => '192.168.0.234',
'port' => '3306',
'login' => 'foo',
'password' => 'bar',
'database' => 'basercms',
'schema' => '',
'prefix' => 'mysite_test_',
'encoding' => 'utf8'
);
}
変更後
<?php
//
// Database Configuration File created by baserCMS Installation
//
class DATABASE_CONFIG {
// Maser
public $baser = array(
'datasource' => 'Database/BcMysql',
'persistent' => false,
'host' => '192.168.0.234',
'port' => '3306',
'login' => 'foo',
'password' => 'bar',
'database' => 'basercms',
'schema' => '',
'prefix' => 'mysite_',
'encoding' => 'utf8'
);
public $plugin = array(
'datasource' => 'Database/BcMysql',
'persistent' => false,
'host' => '192.168.0.234',
'port' => '3306',
'login' => 'foo',
'password' => 'bar',
'database' => 'basercms',
'schema' => '',
'prefix' => 'mysite_pg_',
'encoding' => 'utf8'
);
public $test = array(
'datasource' => 'Database/BcMysql',
'persistent' => false,
'host' => '192.168.0.235',
'port' => '3306',
'login' => 'foo',
'password' => 'bar',
'database' => 'basercms',
'schema' => '',
'prefix' => 'mysite_test_',
'encoding' => 'utf8'
);
// Slave
public $baser2 = array(
'datasource' => 'Database/BcMysql',
'persistent' => false,
'host' => '192.168.0.235',
'port' => '3306',
'login' => 'foo',
'password' => 'bar',
'database' => 'basercms',
'schema' => '',
'prefix' => 'mysite_',
'encoding' => 'utf8'
);
public $plugin2 = array(
'datasource' => 'Database/BcMysql',
'persistent' => false,
'host' => '192.168.0.235',
'port' => '3306',
'login' => 'foo',
'password' => 'bar',
'database' => 'basercms',
'schema' => '',
'prefix' => 'mysite_pg_',
'encoding' => 'utf8'
);
public $test2 = array(
'datasource' => 'Database/BcMysql',
'persistent' => false,
'host' => '192.168.0.235',
'port' => '3306',
'login' => 'foo',
'password' => 'bar',
'database' => 'basercms',
'schema' => '',
'prefix' => 'mysite_test_',
'encoding' => 'utf8'
);
}

スレーブ参照

データベースへの参照時の処理をスレーブへ切り替えます。

具体的にはapp/Model/AppModel.php へ beforeFind と afterFind を追加して、
データ参照時はスレーブへ接続先を切り替えます。

app/Model/AppModel.php

class AppModel extends BcAppModel {
public $useReplica = true;
public function beforeFind($queryData) {
if ($this->useReplica) {
$dbConfig = '';
switch ($this->useDbConfig) {
case 'baser':    $dbConfig = 'baser2';    break;
case 'plugin':   $dbConfig = 'plugin2';   break;
case 'test':     $dbConfig = 'test2';     break;
}
if ($dbConfig) {
$this->useDbConfig = $dbConfig;
foreach ($this->belongsTo as $btModelName => $btModelData) {
$this->{$btModelName}->useDbConfig = $dbConfig;
}
}
}
return $queryData;
}
public function afterFind($results, $primary = false) {
if ($this->useReplica) {
$dbConfig = '';
switch ($this->useDbConfig) {
case 'baser2':   $dbConfig = 'baser';     break;
case 'plugin2':  $dbConfig = 'plugin';    break;
case 'test2':    $dbConfig = 'test';      break;
}
if ($dbConfig) {
$this->useDbConfig = $dbConfig;
foreach ($this->belongsTo as $btModelName => $btModelData) {
$this->{$btModelName}->useDbConfig = $dbConfig;
}
}
}
return $results;
}

その他の対応

モデルの useReplica メソッドで参照先を変更するかどうか、が変更できるようにしているので、不具合がある場合は一時的にfalseにしてマスタ側を見るようにできます。

baserCMS 3.0.6.1で試した時は、ブログ記事の新規登録時に更新→参照のタイミングがあわないのか、うまく動作しなかったので、コアプラグインに少し手を入れて一時的にマスタ側へ接続して対応しました。

もっと良い解決方法があればよかったのですが、、、。

lib/Baser/Plugin/Blog/Controller/BlogPostsController.php

288行あたり

            if ($event !== false) {
$this->request->data = $event->result === true ? $event->data['data'] : $event->result;
}
// データを保存
// ----------------------------------------------------------------------------
// CUSTOMIZE kaburk 2014/11/30 ADD
// ----------------------------------------------------------------------------
// >>>>>
$useReplica = $this->BlogPost->useReplica;
$this->BlogPost->useReplica = false;
// <<<<<             if ($this->BlogPost->saveAll($this->request->data)) {
clearViewCache();
$id = $this->BlogPost->getLastInsertId();
$this->setMessage('記事「' . $this->request->data['BlogPost']['name'] . '」を追加しました。', false, true);
// 下のBlogPost::read()で、BlogTagデータ無しのキャッシュを作ってしまわないように
// recursiveを設定
$this->BlogPost->recursive = 1;
/*                 * * afterAdd ** */
$this->dispatchEvent('afterAdd', array(
'data' => $this->BlogPost->read(null, $id)
));
// ----------------------------------------------------------------------------
// CUSTOMIZE kaburk 2014/11/30 ADD
// ----------------------------------------------------------------------------
// >>>>>
$this->BlogPost->useReplica = $useReplica;
// <<<<<                 // 編集画面にリダイレクト
$this->redirect(array('action' => 'edit', $blogContentId, $id));
} else {
$this->setMessage('エラーが発生しました。内容を確認してください。', true);
}
// ----------------------------------------------------------------------------
// CUSTOMIZE kaburk 2014/11/30 ADD
// ----------------------------------------------------------------------------
// >>>>>
$this->BlogPost->useReplica = $useReplica;
// <<<<<         }         // 表示設定
$user = $this->BcAuth->user();

おわりに

こんな感じで対応出来ました。

MySQL参照系を2台以上使いたい場合は、LVSなんかのロードバランサーで
参照系を切り替えてやればいいのではないですかねー(試してないですが)。

コーポレートサイトにちょうどいいbaserCMSですが、
そこそこ規模の大きいサイトにも十分使えるbaserCMSだということがわかりました!