cakephpのfindをmemcacheするこのエントリをはてなブックマークに追加

1 月 12, 2009

[お詫び]間違えがありましたので修正しました。getのとこの条件間違えてました。
新年初投稿で。よろしくお願いします。

今回はmemcacheとfindをからめたお話です。
Webサイトでキャッシュを使うことはパフォーマンスを高めるのにも有効です。
特にWebサイトのほとんどはDBも使うでしょう。

最近そんな私もDBを使用したサイトをcakeで作ってて、うまくキャッシュ使えないかなーと思ってて若干(?)むりやりですが、findメソッドをオーバーライドしてfindの結果をmemcacheに入れるようなプログラムを書いてみた。

cakephpのfind系のメソッド(findAllとか、findCountなど)はすべてfindを通しているので、findの部分をちょっと変更する。

app_model.php
ちと長いですが勘弁してください><findAllとfindCountはよく使うので一緒に書きました

<?php
uses("cache/Memcache");
class AppModel extends Model{

    var $_mem_obj = null;
    var $_memcache_key = null;
    var $_mem_server = null;
    var $_mem_expire = null;
    var $_mem_timeout = null;

    /**
     * memcacheKey用のコントローラ名とアクション名を登録する
     */
    function setMemcacheKey($controller, $action) {
        if(empty($controller)) return false;
        if(empty($action)) return false;

        $this->_memcache_key = $controller . "_" . $action;
        return true;
    }

    /**
     * memcache keyの作成
     *
     * @param string $memkey ユニークなmemcache key
     * @return string
     */
    function createMemKey($memkey) {
        if(empty($memkey)) return "";
        return sha1($this->_memcache_key."_".$memkey);
    }

    function find($conditions = null, $fields = array(), $order = null, $recursive = null, $memcache=false) {

        // get
        if(!empty($memcache)) {
            $this->_mem_obj = $this->getMemcacheObj();
            $memcache_key = $this->createMemKey($memcache);
            if(!empty($memcache_key)) {
                $result = $this->_mem_obj->read($memcache_key);
                if($result!==false) return $result;
            }else{
                $this->log("Memcache Read key Error: key -> " . $memcache);
            }
        }

        $result = parent::find($conditions, $fields, $order, $recursive);
        // regist
        if(!empty($memcache)) {
            if(!empty($memcache_key)) {
                $mem_result = $this->_mem_obj->write($memcache_key, $result, $this->_mem_expire);
                if(!$mem_result) {
                    $this->log("Memcache write Error: key -> " . $memcache);
                }
            }else{
                $this->log("Memcache key Error: key -> " . $memcache);
            }
            //$this->_mem_obj->clear(); // memcache all clear
        }
        return $result;
    }


    /**
     * findAllオーバーライド
     */
    function findAll($conditions = null, $fields = null, $order = null, $limit = null, $page = 1, $recursive = null, $memcache=false) {
        return $this->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'), null, null, $memcache);
    }

    /**
     * findCountオーバーライド
     */
    function findCount($conditions = null, $recursive = 0, $memcache=false) {
        return $this->find('count', compact('conditions', 'recursive'), null, null, $memcache);
    }
    /**
     * memcacheオブジェクトの生成と設定する
     */
    function getMemcacheObj() {
        // memcache設定
        $this->decideMemInfo();
        if(empty($this->_memcache)) {
            $this->_memcache = new MemcacheEngine();
            $setting = array(
                        "servers" => array($this->_mem_server),
                        );
            $this->_memcache->init($setting);
        }
        return $this->_memcache;
    }
    /**
     * 開発環境を見て、つなぐmemcacheサーバを変える。
     */
    function decideMemInfo() {

        switch(php_uname('n')) {
            default:
                $server_name = 'localhost:11211';
                $connect_timeout = 1;
                $cache_expire = 3;
        }

        $this->_mem_server = $server_name;
        $this->_mem_expire = $cache_expire;
        $this->_mem_timeout =$connect_timeout;

        return true;
    }
}
?>

app_controller.php

<?php
    /**
     * memcacheを使う準備
      *
     * @param array $models memcacheを使うモデル名
      * @return boolean
     */
    function setMemkey($models) {
        if(empty($models)) return false;
        foreach($models as $val) {
            $this->$val->setMemcacheKey($this->name, $this->action);
        }
        return true;
    }
?>

使い方。

<?php
class UsersController extends AppController {
    function index() {
         $this->setMemkey(array("User"));
       $result = $this->User->find("all", null, null, null, "index");
    }
?>

findの最後の変数に、文字列を入れるとmemcacheに登録して、ある場合はmemcacheから返します。

keyの形はapp_model.phpのcreateMemKeyで生成していて
sha1(コントローラ名_アクション名_ユニークな値)
としています。かぶらないような値を考えた結果こんな感じになりました・・・。(memcacheのkeyの設計って皆さんどうしてるんですかね?><)
keyの長さが255文字までなので、sha1でhash値にしてます。そうすればユニークな値が長すぎてエラーになるってこともないので。

↑の例だと、controllerでfindを発行していますが、自分は基本controllerでfindを発行するようなことはなく、model内で整形して、controllerに返すようにしています。

そのため、modelからも使えるようにするため、modelからcontroller名やアクション名を登録することができないので、controllerからコントローラ名とアクション名を渡してます。
modelからfindする場合は、そのfunction名とかをmemcacheのkeyにするかとよいと思います。

こんな感じ(実際のコードではなく適当に参考程度に)
user.php

<?php
function getaUserFromID($id) {
    if(empty($id)) return false;
    
    $conditions = array("User.id"=>$id);
    $result = $this->find($conditions, null, null, null, "getaUserFromID_{$id}");

    if(empty($result)) return false;

    return $result;
}
?>

とか書いてて思ったけど、idから取得するならreadでもいいのか。まぁいいか。

以下は随時追加していきます。
○メリット
- 最初の導入が手間だけど、そのあとはお手軽にfindの結果をmemcacheできる。
○デメリット
- ある程度app_modelをいじってからだとめんどいかも。
- expireを各findで設定できない。
- memcacheオブジェクトが各モデル内に生成されるのでちと微妙。
- findBy***では使えない。まぁそこは普通にconditionsで設定するようにすればいいかなーとか思うけど。

使うのはかまいませんが、自己責任でお願いします。
memcacheだとこんな使い方もあるよーってのがあったら教えてくださいませ。><

Categories: cakephp, memcache
Tags: ,

1 件のコメント »

このコメント欄の RSS フィードトラックバック URL

  1. Nice site really!

    コメント by gambling casino — 2009 年 2 月 1 日 @ 5:07 AM

コメントをどうぞ