“slim3” カテゴリのアーカイブ


これでいいのかしら。。


    public static Key allocateId(AsyncDatastoreService ds, String kind)
            throws NullPointerException {
        if (ds == null) {
            throw new NullPointerException("The ds parameter must not be null.");
        }
        if (kind == null) {
            throw new NullPointerException(
                "The kind parameter must not be null.");
        }
        String ns = NamespaceManager.get();
        if (ns == null) {
        	ns = "";
        }
        String cacheKey = "!"+ns+":"+kind;
        Iterator<Key> keys = keysCache.get(cacheKey);
        if (keys != null && keys.hasNext()) {
            return keys.next();
        }
        keys =
            FutureUtil
                .getQuietly(allocateIdsAsync(ds, kind, KEY_CACHE_SIZE))
                .iterator();
        keysCache.put(cacheKey, keys);
        return keys.next();
    }

Comments [appengine][slim3] DatastoreUtil#allocateIdがNamespaceに対応するように修正 はコメントを受け付けていません。


listプロパティにInMemoryInCriterionを適用するとエラーになるので修正。
これでいいのかしら。。


   public boolean accept(Object model) {
        Object v = convertValueForDatastore(attributeMeta.getValue(model));
        for (Object o : value) {
        	if (v instanceof Collection<?>) {
        		for (Object v2: (Collection<?>)v) {
                    if (compareValue(v2, o) == 0) {
                        return true;
                    }
        		}
        	} else {
        		if (compareValue(v, o) == 0) {
        			return true;
        		}
        	}
        }
        return false;
    }

Comments [appengine][slim3]InMemoryInCriterionがlistプロパティでも動作するように修正 はコメントを受け付けていません。



ソースコードは こちら に公開しています。

概要

データストアのRead/Writeの無料課金枠が結構シビアなのでもはやキャッシュなしではやっていけないと思ったのがこれを作るきっかけでした。(速度的にはデータストアの呼び出しがそんなに遅いとは感じないのであくまで課金対策が主眼です。)

既存のコードにできるだけ手を入れないで実現したかったのでApiProxyを使ってdatastoreのAPI呼び出しをhookし、protocol bufferのrequest,responseをそのままキャシュしてはどうかと思いつきました。

キャッシュ対象とするのはQuery(RunQueryメソッド)でGetはもともと安いので対象外としました。RunQueryメソッドをhookしてキャッシュにデータが存在すればdatastore APIは呼び出さずにキャッシュしたレスポンスを返す、キャッシュにデータが存在しなければそのままAPI呼び出しをしてresponseをキャッシュする、というしくみになっています。
同一カインドのデータがPutまたはDeleteされた場合はキャッシュを無効にするようにカインド毎のResetDateを持って管理しています。

WriteよりReadのほうが圧倒的に多いシステムではキャッシュヒット率が高くなるのでこのしくみは有効かと思います。
逆に同一カインドのエンティティが頻繁に更新されるようなシステムではあまり効果がないかもしれません。

必要なライブラリ

  • commons-logging-1.1
  • commons-lang-2.4
  • gdata-core-1.0

使い方

必要なjarファイルを追加して、web.xmlのfilterChain先頭に以下のフィルター設定を追加するだけです。


    <filter>
        <filter-name>CacheContextFilter</filter-name>
        <filter-class>jp.honestyworks.pbcache.ContextFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CacheContextFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>

その他の特徴

  • App EngineのMemcache Quota Limit を超えるサイズのデータも問題なくキャッシュされます。(適切なサイズにchunkされて保存します。)
  • memcacheだけでなくThreadLocalなコンテキストにもキャッシュを保持します。(Request毎にクリアされます。)
  • 上記ローカルキャッシュは使わない設定にすることもできます。
  • Production環境でしか有効になりません。

制限

  • datastore APIのasyncCallはsyncCallにデグレードします。asyncCallの恩恵を受けているシステムでは使用しないほうが良いかと思います。

本題とは関係ないけど、ktrwjr が便利

テストをするにあたり、slim3に組み込まれているktrwjrを使ってみましたがすごく便利!これは素晴らしいです。

ダウンロード

オープンソースとしてありますので自己責任でご自由にお使いください。

フィードバック

使っていただけた方は些細な事でもフィードバックいただけると嬉しいです。protocol buffer はあまり詳しくないので探り探りの実装になってます。
フィードバックは私のtwitterまでお寄せください。 (@miztaka)

Comments App Engine Java用データストアの透過キャッシュを作りました はコメントを受け付けていません。


概要

appengineでJSON-RPCサーバーのようなものを作りたいときなど ServletRequest#getInputStream()で取得できるInputStreamを使いたい場合があります。ところがslim3のcontrollerでこれをやろうとするとInputStreamのIllegalStateExceptionが発生します。
Jettyや大概のサーブレットサーバーは getInputStreamとgetParameter(s)を同時には使えないようです。
JSON-RPCならslim3のcontrollerは使う必要ないじゃんといえばそれまでなのですができれば慣れているもので全てやってしまいたいというのも事実。そこで以下のようなworkaroundで回避します。

  1. StreamFilterを作って、そこでServletRequestをWrapperクラスに置き換える (一番最初にこのFilterが動くようにする)
  2. RequestWrapperでは getInputStreamをoverrideし、Streamを再利用可能にする

なお、このworkaroundは ServletInputStreamを2回使う – うさ☆うさ日記 にて紹介されているやり方とほぼ同じです。(ナイスポストありがとうございました!)

StreamFilter


public class StreamFilter implements Filter {

    public void destroy() {
        // TODO Auto-generated method stub

    }

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest req = (HttpServletRequest)request;
        request = new BufferedServletRequestWrapper( req );
        chain.doFilter(request, response);
    }

    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub

    }

}

BufferedServletRequestWrapper


public class BufferedServletRequestWrapper extends HttpServletRequestWrapper {
    
    private byte[] buffer;

    public BufferedServletRequestWrapper(HttpServletRequest request) throws IOException {
        super( request );

        InputStream is = request.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte buff[] = new byte[ 1024 ];
        int read;
        while( ( read = is.read( buff ) ) > 0 ) {
            baos.write( buff, 0, read );
        }

        this.buffer = baos.toByteArray();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new BufferedServletInputStream( this.buffer );
    }

}

BufferedServletInputStream


public class BufferedServletInputStream extends ServletInputStream {

    private ByteArrayInputStream inputStream;

    public BufferedServletInputStream(byte[] buffer) {
        this.inputStream = new ByteArrayInputStream( buffer );
    }

    @Override
    public int available() throws IOException {
        return inputStream.available();
    }

    @Override
    public int read() throws IOException {
        return inputStream.read();
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        return inputStream.read( b, off, len );
    }

}

参考

Comments slim3のcontrollerでServletInputStreamを使いたいとき はコメントを受け付けていません。