Zend_Db_Tableを利用して複数DBサーバを参照する

DBサーバの負荷分散のために、select文を発行する先のDBを複数用意して運用することがあると思います。マスター(更新&参照)1台に、スレーブ(参照)1台とか。

今回、マスター1台、スレーブ1台の構成で、selectクエリを振り分けるプログラムを作りました。Zend_Db_TableのfetchRow、fetchAll等が呼ばれる度に、クエリを実行するDBサーバを決定します。

まずは、DB接続先の設定をiniファイルに定義します。
接続先情報は、ダミーです。

database.adapter         = Pdo_Mysql
database.params.host     = master.test.com
database.params.username = dbuser
database.params.password = hogehoge
database.params.dbname   = master

# 参照DBhost
database.reference.host  = slave1

次に、Zend_Db_Table_Abstractと各テーブルクラスの間に入れるクラス。
Zend_Db_Table_Abstractクラスの拡張クラスです。

abstract class My_Db_Table_Abstract extends Zend_Db_Table_Abstract
{
    public function fetchAll($where=null, $order=null, $count=null, $offset=null)
    {
        // 2回に1回はmasterに接続
        if ((rand() % 2) == 0) {
            return parent::fetchAll($where, $order, $count, $offset);
        }
        
        // 2回に1回はslaveに接続
        $adapter = $this->getAdapter();
        $this->_setAdapter($this->getReferenceAdapter());
        $rows = parent::fetchAll($where, $order, $count, $offset);
        $this->_setAdapter($adapter);
        
        return $rows;
    }

    public function fetchRow($where=null, $order=null)
    {
        // 2回に1回はmasterに接続
        if ((rand() % 2) == 0) {
            return parent::fetchRow($where, $order);
        }
        
        // 2回に1回はslaveに接続
        $adapter = $this->getAdapter();
        $this->_setAdapter($this->getReferenceAdapter());
        $row = parent::fetchRow($where, $order);
        $this->_setAdapter($adapter);
        
        return $row;
    }
    
    public function getReferenceAdapter()
    {
        if (Zend_Registry::isRegistered('Ref_Db_Adapter')) {
            return Zend_Registry::get('Ref_Db_Adapter');
        }
        
        $config = new Zend_Config_Ini('↑で作ったconfigファイル名');
        $params = $config->database->params->toArray();
        $params['host'] = $config->database->reference->host;
        $db = Zend_Db::factory($config->database->adapter, $params);
        $db->setFetchMode(Zend_Db::FETCH_ASSOC);
        Zend_Registry::set('Ref_Db_Adapter', $db);
        
        return $db;
    }
}

テーブルクラスの定義は以下のように

class Users extends My_Db_Table_Abstract
{
    protected $_name    = 'users';
    protected $_primary = 'user_id';
}

これで、fetchRow、fetchAllを使う際は、masterとslaveにクエリが分散されます

MySQLのViewを使った感想

100万件以上のデータを持つDBで、Viewテーブルを使える機会があったので使ってみました。結果としては残念なことに。Viewテーブルを利用するだけでは性能改善につながりません。

Viewテーブルについて調べているときに、「MySQLのViewテーブルは裏側でcreateしたときのSQL発行するだけだから、単独では性能改善につながらない」という内容の記事を見たんですが、まさにその通りの結果になりました。ViewテーブルをcreateしたときのSQLでの検索性能と、Viewテーブルを使って同様のデータを取得しようとしたときの性能はほぼ一緒。改善された様子はありませんでした。

DBにOracleを使っていたときは、性能改善の手段として、まず変数のbind化やストアドプロシージャと供にViewテーブルを使って性能改善をしていた記憶があるので、Viewテーブルに対して検索をかけたときは単一テーブルに対して検索かけた程度の性能が期待できるのかと勘違いしていました。OracleではSQL発行の窓口としてのViewテーブルではなく、実際にデータを持っていたんでしょうか。(もう5年近く前の経験なので、今は全く変わっているかもしれません。Oracle)

Plugin内のExceptionで、処理はとまらない

ActionHelperやPluginを有効利用しようと模索していますが、
今回Pluginでチェック処理を行った際に、気づいたことがあります。

Plugin内でExceptionを発生させても、処理は止まらないということ。
Zend_Controller_Action内の処理で発生した場合は止まるのに。(※1)

※1
Zend_Controller_Action内とは、ActionHelper、Controller内で
利用しているmodelの処理を指しています。

調べてみると、PluginでのExceptionはPluginBroker
(Pluginの処理を統合してるとこ)の中で全てcatchされているために、
PluginBrokerを呼び出しているFrontControllerでは認識できません。
そのために、FrontControllerはPlugin内でExceptionが発生しても、
その後の処理をどんどん実行していくというわけです。

チェック次第で処理を中断するという内容の処理を記述するなら、
Zend_Controller_Action内(※1)でってことですね。

HelperとPluginのこういう違いがわかっていくと、使い分けの
目安がなんとなくできるようになっていきますね。

Snow Leopardに問題が発生

Snow Leopardに深刻なバグ、ユーザーの個人データが失われるおそれ : Appleウォッチ – Computerworld.jp

問題の内容は、ユーザーがゲスト・アカウントでログインしたあとにログアウトし、通常使っているユーザーのアカウントでログインすると、Snow Leopardのホーム・ディレクトリ(Macの主なユーザーの名前が付けられている)が上書きされ、中身がすべて消えてしまうというもの。

普段ゲストアカウントを使うことはないけど、誤クリックは怖い。。
バックアップは大事ですね。timemachine使ってみようかな。
ハードディスク買うところからはじめないと。

Zend_Loader_Autoloaderの使い方

れぶろぐ – [Zend] Zend_Loader_Autoloader クラスの正しい使い方

require_once 'Zend/Loader/Autoloader.php';
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->unregisterNamespace(array('Zend_', 'ZendX_'))
           ->setFallbackAutoloader(true);

ZendFrammeworkを使って新しいプログラムを作ることになったので、噂できいていたZend_Loader::registerAutoload();がなくなって、Zend_Loader_Autoloaderを使うようにするってことに対応することにしました。

参考になる記事を見つけたので、メモ代わりに書いておきます。

Smartyで配列をcount

{ $list|@count } ({、}はデリミタ)

と書くことで、配列の要素数を表示できるということを知った。
@countって書けるんですね。
これって、@つけたらphpの関数呼び出せてしまうんでしょうか。

※ご指摘をいただいて、通常のPHPと同じくエラー回避の構文で、
 なくても問題ないということがわかりました。ありがとうございます。

それなら、こういうのも便利だというものがないか考えましたが、
ぱっとは出てきませんでした。array_shiftとかしてもしょうがないし。

また思いついたら、書こうと思います。

それとは別に昨日、Windows XPにVMWare Serverいれて、apache、DB、php、gcc、emacsをインストールところまで教えてもらいながらやったので、参考サイトとかをまとめて紹介しようと思っています。

【php】trimの使い方

trim、ltrim、rtrimは空白文字(タブ、改行などを含む)を取り除いてくれる関数という認識でいましたし、それ以外の使い方をしたことはありませんでした。

Zendのソースを眺めていると第2引数がある箇所を発見。
phpのマニュアルを読んでみると、取り除く文字を指定できるという説明がありました。ZendのソースでもURIの先頭の”/”を取り除くことに利用されいたので、これは結構使える箇所があるんじゃないかと思って、ここにメモっときます。

【php】arrayの演算子

普段、arrayをくっつけるときは、array_mergeを使っています。
ZendのRouterやRoute_Moduleあたりのソースを見ているときに、
array 同士を “+” している記述があったので調べてみました。
その結果、array $a , array $bがあり、連想配列である場合、
array_mergeと”+”には差があることがわかりました。

同一のキーが存在した場合、”+”演算子は、既存のリストの値を優先し、
array_mergeは新しいリストの値を優先します。
つまり、$a + $b と array_merge($b, $a)の返り値は同じになります。

例をあげると、こういうことです。

$a = array('aa' => '11', 'bb' => '22');
$b = array('aa' => '33', 'cc' => '44');

$a + $b = array('aa' => '11', 'bb' => '22', 'cc' => '44')
array_merge($a, $b) = array('aa' => '33', 'bb' => '22', 'cc' => '44');

ドンキホーテ中目黒本店

最近オープンしたドンキホーテ中目黒本店へ。
自転車に使う潤滑剤を買いに行ったついでに、他の商品も見てまわったら結構安いのもある。特に目についた自分が使いそうなものはこれら。

・外付けHDD(IO-DATA、baffalo)

ヤマダ電機渋谷LABIに対抗していて2000から3000円程度安い。
amazonの方がさらに1000円前後安いけど、ぶっ壊れたときとかには便利かも。

・CD-R

他の量販店とほぼ一緒。amazonでもかわらない。
ときどきヤマダ電機はセールやるからそこが一番お得だと思う。

・インクジェットプリンタのインク

夜中になくなったりしたときにこれが売ってるのは便利。

外付HDDが安かったのには、おどろいた。
こういう商品に力を入れているなんてねー。

Zend_Db_Tableでの勘違い

Zend Framework: Documentation

Zend_Db_Table で UPDATE や DELETE の連鎖操作をエミュレートする場合は、 配列 $_dependentTables を親テーブルで宣言し、 従属しているテーブルをそこで指定します。

各従属テーブルのクラス内で、配列 $_referenceMap を宣言します。これは、参照の “ルール” を定義する連想配列となります。 参照ルールとは、リレーションの親テーブルが何になるのか、 従属テーブルのどのカラムと親テーブルのどのカラムが対応するのかを示すものです。

引用した1つめの文章を読み飛ばしてしまっていたため、find~を使うためには、$_referenceMapと$_dependentTablesは、対を成す形式で記述するものだと勘違いしていました。onUpdateとかonDeleteをZendを使って実現しなければ、$_dependentTablesは不要ってことですね。

これだけのことなんですが、かなり「はっ!!」としました。
作っているプログラムの行が少なくなることは見やすくなっていいことです。