GAE + GWT プログラミングメモ

Google App Engine とGoogle Web Toolkit のメモ

Slim3のMemcacheを利用してDatastoreアクセスを少なくする その1

GAEでは、DatastoreへのRead、Writeともに課金されているので、なるべく減らしたい。
Memcacheを使えば、レスポンスを速くしつつ、Datastoreへのアクセスを減らすことができる。

DataAccessor

Slim3では、DataBaseの派生クラスのDAOでDatastoreにアクセスしているが、
さらに1つクラスを用意して、Memcacheを間に挟む。
ここでは、DataAccessorという名前にした。

public class DataAccessor<T extends Slim3Model> {
    private DaoBase<T> dao;

    public DataAccessor(DaoBase<T> dao) {
        this.dao = dao;
    }
}

Slim3Modelインターフェース

ここで、Slim3Modelは

public interface Slim3Model {
    Key getKey();
    Long getVersion();
}

というインターフェースで、Slim3でModelを作成すると自動生成されるインターフェースを明示したもの。

使い方としては、

public class BlogSlim3ModelDao extends DaoBase<BlogSlim3Model>{
    private DataAccessor<BlogSlim3Model> dataAccessor = new DataAccessor<BlogSlim3Model>(this);

    public List<BlogSlim3Model> getModelList() {
        List<Key> keyList = query().asKeyList();
        return dataAccessor.read(keyList);
    }
}

のような形になる。
BlogSlim3ModelDaoはSlim3のgen-model-with-daoで自動生成される。

read実装

readは以下のように実装する。

public class DataAccessor<T extends Slim3Model> {
    private KeyMemcache memcache = new KeyMemcache();

    public List<T> read(List<Key> keyList) {
        Map<Key, T> memcachedModelMap = memcache.getAll(keyList);
        Map<Key, T> daoModelMap = getDaoModelMap(keyList, memcachedModelMap);
        memcache.putAll(daoModelMap);

        List<T> modelList = new ArrayList<T>();
        for (Key key : keyList) {
            if (memcachedModelMap.containsKey(key)) {
                modelList.add(memcachedModelMap.get(key));
            } else if (daoModelMap.containsKey(key)) {
                modelList.add(daoModelMap.get(key));
            }
        }
        return modelList;
    }

    private Map<Key, T> getDaoModelMap(List<Key> keyList, Map<Key, T> memcachedModelMap) {
        Collection<Key> subtractedCollection =
            CollectionUtils.subtract(keyList, memcachedModelMap.keySet());
        List<Key> subtractedList = new ArrayList<Key>(subtractedCollection);
        return dao.getAsMap(subtractedList);
    }
}

Daoにアクセスする前に、Memcacheに保存されていないか確認する。
MemcacheにはDatastoreのKeyクラスをMemcacheのKey、Slim3により自動生成されるModelクラスをValueとして保存する。
Datastoreからは、CollectionUtils.subtractでMemcacheから取得できなかったKeyのみを取り出して、getする。
DatastoreからgetしたdaoModelMapはmemcache.putAll(daoModelMap)で、Memcacheに保存して、次回以降Memcacheから取得できるようにする。
MemcacheとDaoから取得したModelはこのままでは、順番がバラバラなので、最後に順番を整理して返す。

KeyMemcacheクラス

Memcacheは型情報が無くなるので、KeyMemcacheというクラスを用意して、型情報を付与している。

public class DataAccessor<T extends Slim3Model> {
    private class KeyMemcache {
        public void putAll(Map<Key, T> values) {
            Map<Object, Object> objectMap = new HashMap<Object, Object>();
            for (Entry<Key, T> entry : values.entrySet()) {
                Object key = entry.getKey();
                Object model = entry.getValue();
                objectMap.put(key, model);
            }
            Memcache.putAll(objectMap);
        }
        public Map<Key, T> getAll(List<Key> keyList) {
            Map<Key, T> map = new HashMap<Key, T>();
            Map<Object, Object> memcachedMap = Memcache.getAll(keyList);
            for (Entry<Object, Object> entry : memcachedMap.entrySet()) {
                Key key = (Key) entry.getKey();
                @SuppressWarnings("unchecked")
                T model = (T) entry.getValue();
                map.put(key, model);
            }
            return map;
        }
    }
}