こんにちは市丸です。
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のデータ不整合に気をつけないといけません。
このように、トランザクションのcommit前に別のユーザーからアクセスされると、CacheとMySQLのデータに不整合が起こります。
正しくは、このように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後にキャッシュクリアすればいいんだけどね!(爆)