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

shucreamTech

web系エンジニアが意識低く書くブログ

FuelPHPでOrmを使いつつ複数slave対応する試行錯誤

PHP Fuel

やりたいこと

master-slave構成(特に複数slave)のMySQLをFuelから扱いたい。

今回は FuelPHP(1.7) + PHP5.6 + MySQL5.7.9 を使用。

ぶつかった壁

  • ORMを使う際に、接続先を指定出来ない?
  • Readを複数のSlaveに振り分けるような機構がない?

1つずつ説明していきます。

準備

まず、 db.php にmasterとslaveの設定を書きます。

<?php
return [
    'master' => [
        'type'             => 'mysqli',
        'connection' => [
            'hostname'       => 'localhost',
            'port'           => '3306',
            'database'       => 'db_master',
            'username'       => 'user_name',
            'password'       => 'p@ssW0rd'
        'table_prefix'     => '',
        'enable_cache'     => true,
        'readonly'       => ['slave1'],
    ],
    'slave1' => [
        'type'             => 'mysqli',
        'connection' => [
            'hostname'       => 'localhost',
            'port'           => '3306',
            'database'       => 'db_slave1',
            'username'       => 'user_name',
            'password'       => 'p@ssW0rd',
        ],
        'table_prefix'     => '',
        'enable_cache'     => true,
    ],
];

今回は読み込み先の差し替え実験をしたいだけなので、MySQL側の細かい話は置いておきます。 とりあえず同じホストに、db名だけ分けてmasterとslaveを作ります。

ORMを使う際に、接続先を指定出来ない?

ORMを使わない場合 このケースは、ちゃんと試したわけではなくググって出てきた情報がメインです。(すみません)

DBの接続先を差し替えたければ、

DB::query($query)->parameters($params)->execute("slave1");

こんな感じで、execute()に接続先の名前を渡せば良いみたいです。

公式ドキュメント 曰く、

データベースがマスター/スレーブ環境ならば、スレーブサーバの配列を定義できます。データベース読み込み処理の際には、この値の中からランダムに選択されて使用されます。

とのことなので、明示的に指定する必要も無いかもしれません。 ということで、(多分)特に問題なく複数Slaveを扱えそうです。

ORMを使う場合 さて問題はこの場合です。

まず db.php を先述の通り設定したら、何も考えずに ModelHoge::find(1); としてみます。

ModelHoge::find(1);
# -> Error invalid data source name

invalid data source name というエラーが出ますね。

公式ドキュメント 曰く、

protected static $_write_connection
protected static $_connection

こんなパラメータがあるので、

Modelクラスの中で

protected static $_write_connection = 'master';
protected static $_connection = 'slave1';

としてみたところ、正常に slave1からread出来ました。

Readを複数のSlaveに振り分けるような機構がない?

さて、Ormを用いてmaster-slaveな構成を扱えるようになったのですが、 slaveが複数台あるような構成はよくあると思います。 以下のように、db.phpslave2の設定を追加します。

<?php
return [
    'master' => [
        'type'             => 'mysqli',
        'connection' => [
            'hostname'       => 'localhost',
            'port'           => '3306',
            'database'       => 'db_master',
            'username'       => 'user_name',
            'password'       => 'p@ssW0rd'
        'table_prefix'     => '',
        'enable_cache'     => true,
        'readonly'       => ['slave1', 'slave2'],
    ],
    'slave1' => [
        'type'             => 'mysqli',
        'connection' => [
            'hostname'       => 'localhost',
            'port'           => '3306',
            'database'       => 'db_slave1',
            'username'       => 'user_name',
            'password'       => 'p@ssW0rd',
        ],
        'table_prefix'     => '',
        'enable_cache'     => true,
    ],
    'slave2' => [
        'type'             => 'mysqli',
        'connection' => [
            'hostname'       => 'localhost',
            'port'           => '3306',
            'database'       => 'db_slave2',
            'username'       => 'user_name',
            'password'       => 'p@ssW0rd',
        ],
        'table_prefix'     => '',
        'enable_cache'     => true,
    ],
];

こんな感じで。

slaveが1台の時はModelクラスの中で

protected static $_write_connection = 'master';
protected static $_connection = 'slave1';

としてたわけですが、slaveが2台になったらどうするんだ...?という話です。

失敗

protected static $_write_connection = 'master';
protected static $_connection = ['slave1', 'slave2']; // 配列チャレンジ

# -> Array to string conversion

配列での指定は無理なようです。

とりあえず成功

protected static $_write_connection = 'master';
// protected static $_connection = 'slave1'; // これは不要
protected static $_connections = ['slave1', 'slave2']; // 独自定義

/**
 * findオーバーライドチャレンジ
 */
public function find($id = null, array $options = [])
{
    $rand_key = array_rand(self::$_connections, 1);
    // 'slave1' or 'slave2'をランダムでセットする
    static::set_connection(self::$_connections[$rand_key]);
    return parent::find($id, $options);
}

というわけで、findをオーバーライドすることで成功しました。

もっと成功 count()min()max()もslaveから読みたい!!となると、それらもオーバーライドしないといけないのですが、 read系は全部内部で static::query()を呼んでいるので、findではなくqueryをオーバーライドしてやると良いかもしれません。

public static function query($options = array())
{
    $rand_key = array_rand(static::$_connections, 1);
    static::set_connection(static::$_connections[$rand_key]);
    return parent::query($options);
}

良さそう。(多分) うーん...でも db.phpで masterに対してreadonlyを指定してるんだから自動でやってくれないかな... その辺も自力でconfigから抜いてset_connectionするようなコードを基底に書いてあげればどうにかなりそうではありますが。

ちなみに、deleteやupdateは勝手に $_write_connection (=master) の方を向いてくれるみたいなので、何も考えなくても大丈夫でした。

おわり。