これでいいのかしら。。
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();
}
[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;
}
[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の恩恵を受けているシステムでは使用しないほうが良いかと思います。
テストをするにあたり、slim3に組み込まれているktrwjrを使ってみましたがすごく便利!これは素晴らしいです。
オープンソースとしてありますので自己責任でご自由にお使いください。
使っていただけた方は些細な事でもフィードバックいただけると嬉しいです。protocol buffer はあまり詳しくないので探り探りの実装になってます。
フィードバックは私のtwitterまでお寄せください。 (@miztaka)
App Engine Java用データストアの透過キャッシュを作りました はコメントを受け付けていません
appengineでJSON-RPCサーバーのようなものを作りたいときなど ServletRequest#getInputStream()で取得できるInputStreamを使いたい場合があります。ところがslim3のcontrollerでこれをやろうとするとInputStreamのIllegalStateExceptionが発生します。
Jettyや大概のサーブレットサーバーは getInputStreamとgetParameter(s)を同時には使えないようです。
JSON-RPCならslim3のcontrollerは使う必要ないじゃんといえばそれまでなのですができれば慣れているもので全てやってしまいたいというのも事実。そこで以下のようなworkaroundで回避します。
- StreamFilterを作って、そこでServletRequestをWrapperクラスに置き換える (一番最初にこのFilterが動くようにする)
- RequestWrapperでは getInputStreamをoverrideし、Streamを再利用可能にする
なお、このworkaroundは http://d.hatena.ne.jp/machi_pon/20090120/1232420325 にて紹介されているやり方とほぼ同じです。(ナイスポストありがとうございました!)
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
}
}
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 );
}
}
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 );
}
}
slim3のcontrollerでServletInputStreamを使いたいとき はコメントを受け付けていません