« 2010年9月 | メイン | 2010年11月 »

2010年10月25日

MemcacheとMySQLのデータ不整合をPropelPDOで解決してみる
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは市丸です。

Zynga Japanでは単純なPrimaryKeyをキャッシュする際、symfonyのPeerをオーバーライドし自動的にキャッシュ&クリアしています。

UserPeer.php

class UserPeer extends BaseUserPeer
{
    public static function retrieveByPK($pk, PropelPDO $con = null)
    {
        if (!$data = /* Cacheからとるよ */) {
            $data = parent::retrieveByPK($pk, $con);
            /* "User_$pk"みたいなキーでCacheするよ */
        }
        return $data;
    }
    public static function doInsert($values, PropelPDO $con = null)
    {
        /* Cacheクリアするよ */
        return parent::doInsert($values, $con);
    }

    public static function doUpdate($values, PropelPDO $con = null)
    {
        /* Cacheクリアするよ */
        return parent::doUpdate($values, $con);
    }

    public static function doDelete($values, PropelPDO $con = null)
    {
        /* Cacheクリアするよ */
        return parent::doDelete($values, $con);
    }
}

ただし、ソーシャルゲームのように、2人以上のユーザーが同じテーブルを参照する場合、MemcacheとMySQLのデータ不整合に気をつけないといけません。

bad.png

このように、トランザクションのcommit前に別のユーザーからアクセスされると、CacheとMySQLのデータに不整合が起こります。

good.png

正しくは、このようにcommit後にキャッシュを削除しなければなりません。

そこで、PropelPDOを改変しcommit後にキャッシュ削除しましょう。

MemPropelPDO.php

class MemPropelPDO extends PropelPDO {
    protected $memcacheKeys = array();

    public function addMemcacheKey($key) {
        $this->memcacheKeys[] = $key;
    }

    public function commit(){
        parent::commit();
        foreach($this->memcacheKeys as $key){
            // よしなにCache削除してくだされ。
        }
    }    
}

database.yml

all:
 db1:
   class:        sfPropelDatabase
   param:
     classname:  MemPropelPDO
     (略)

User.php

class User extends BaseUser
{
    public function save(PropelPDO $con = null)
    {
        if (!is_null($con)) {
            $con->addMemcacheKey("User_".$this->getPrimaryKey())
        }
        return parent::save($con);
    }
}

【仕上げ】トランザクションを使う場合はsaveメソッドにMemPropelPDOを渡します

        $userModel = UserPeer::retrieveByPK(1);
        $userModel->setStatus(2); //statusを1->2にしてみる

        $con = Propel::getConnection(); // database.yml で指定したMemPropelPDO
        $con->beginTransaction();
        try {
            $userModel->save($con);
            $con->commit();
        } catch (Exception $e) {
            $con->rollback();
            throw $e;
        }

まあ、忘れずにcommit後にキャッシュクリアすればいいんだけどね!(爆)

2010年10月14日

Illustratorの小技 その3 ブレンド機能をパスにする
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

たけぞうです

前回、ブレンド機能の小技をお伝えしましたが
今回はそのブレンド機能をパスにする小技を紹介します

※使用しているのはIllustratorのCS4です

101401.gif

ブレンドは、中間の色や形を自動的に補正してくれる機能ですが
これはパスではないため
図の5つ並ぶ真ん中の四角だけを動かしたり色を変えたりする事は出来ません
また
「パスのアウトライン化」をしても適応されません
ではどうしたらよいか

101402.gif


オブジェクトの「透明部分を分割・結合」を選択
詳細設定が出てくると思いますが、そのまま先に進めても構いません

すると

101403.gif


ブレンドの中間部分も全てパスになっているのがわかりますでしょうか

パス化したことにより、自由に形を変えたり動かしたりなどが可能になる
ほんの少しだけ便利な小技です

2010年10月 6日

絵文字のためにsymfonyのカスタムハンドラーを作ってみました
このエントリーをはてなブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは、kayです。

携帯サイトのdocomo絵文字が文字と同じ色になっていると気になって気になって仕方がない今日この頃なのですが、YAMLでデフォルトの色を設定して絵文字を出力するときに自動的にその色にしてくれるなんてことをしたいなぁと思っていました。

しかし、sfYaml::load()をすると毎回パースしてしまうのでとっても頻繁に呼び出される絵文字の色にそれを使ってしまうと大変なことになってしまいます。

app.ymlなどのsymfony側の設定YAMLはパース結果をキャッシュしてくれるのですが、それを絵文字の色の設定でもすればいいと大先生に言われましたのでカスタムハンドラーを作って対応することにしました。

symfonyのビルドインハンドラー

まず、symfonyにはビルトインハンドラーが多数存在しますが、今回は汎用的に使えるsfDefineEnvironmentConfigHandlerとsfSimpleYamlConfigHandlerを見てみました。

  • sfDefineEnvironmentConfigHandler
    app.ymlのようにdev, prodなどの環境に応じて異なる値が設定できる汎用ハンドラー
    この場合環境によって設定が異なる訳ではないのでリッチすぎる
  • sfSimpleYamlConfigHandler
    パースした結果をキャッシュしてくれるだけのハンドラー
    sfConfig::add()をしてくれないので、読みだしただけではsfConfig::get()が使えない

結果、sfSimpleYamlConfigHandlerプラスアルファでsfConfig::add()をしてくれるだけでそこそこ使えるものになるなぁと思いました。

YAMLを用意

# config/hoge.yml
fuga: 1
foo:
  bar: 2

config_handlers.ymlを編集

config_handlers.yml内に、上記のYAMLファイルを読むのに使用するハンドラーを指定します。
ファイルパスを指定しておかないとデバッグモードが無効な状態でエラーになってしまうので要注意です。

config/hoge.yml:
  class: myHogeConfigHandler
  file: %SF_ROOT_DIR%/lib/config/myHogeConfigHandler.class.php
  param:
    # sfConfig::get()で値を取得する時のキープレフィクス
    prefix: hoge_

ハンドラー作成

あくまでも例ですが、下記のようにシンプルに出来ます。

<?php

class myHogeConfigHandler extends sfYamlConfigHandler
{
    public function execute($configFiles)
    {
        $prefix = strtolower($this->getParameterHolder()->get('prefix', ''));
        $config = self::parseYamls($configFiles);

        $hogeConfig = array();
        foreach ($config as $key => $value) {
            // sfConfig::get()で使うキープレフィックスを付ける
            $hogeConfig[$prefix . $key] = $value;
        }

        // include等した際に同時に中身をsfConfig::add()もするようにする
        return sprintf("<?php\n"
                . "// auto-generated by %s\n"
                . "// date: %s\n"
                . "sfConfig::add(%s);\n",
                __CLASS__,
                date('Y/m/d H:i:s'),
                var_export($hogeConfig, true));
    }
}

sfConfigCache::import()で設定を読む

  • sfConfigCache::checkConfig()してキャッシュファイルをインクルードしてくれます
  • インクルードの帰り値を返してはくれないけどこの場合sfConfig::get()で値を取得出来るようになっているので大丈夫です

YAMLの設定値をとにかく読み込ませておきたい場合はApplicationConfiguration::initialize()等に入れておきます

<?php

abstract class myApplicationConfiguration extends sfApplicationConfiguration
{
    public function initialize()
    {
        $this->getConfigCache()->import('config/hoge.yml', true, true);
    }
}

sfConfig::get()で設定値を取得

  • sfConfig::get('hoge_fuga')は1
  • sfConfig::get('hoge_foo')はarray('bar' => 2)
  • sfConfig::get('hoge_foo_bar')は2
とそれぞれ設定した値を返してくれるようになりました!

感想

マスターデータ系はDBではなくYAML等のファイルで設定するのが好きなのですが、その際の問題点を解決出来たので良かったです。みなさまもぜひぜひ使いやすいカスタムハンドラーを作ってみてはいかがでしょうか?

About 2010年10月

2010年10月にブログ「ウノウラボ by Zynga Japan」に投稿されたすべてのエントリーです。過去のものから新しいものへ順番に並んでいます。

前のアーカイブは2010年9月です。

次のアーカイブは2010年11月です。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。

Zynga Japan