2011年11月30日 23:00

Memcacheに1MB以上のオブジェクトを格納する

AZusaar! ではDatastoreからとってきた検索結果を100件ずつMemcacheに入れているのですが、最近ちょっとDatastore Reads Oppsが増えてきました。

チューニング前

これが一番多かった時ですが、$0.11/日課金されてます。普通だとFrontEnd InstanceやDatastore Writesで課金がかかると思うんですがうちの場合これらは余裕ですw

これくらいなら微々たるものなのですがなんとかチューニングできないかと課金対策してみました。

とりあえず検索結果を100件ずつではなく全件Memcacheに入れるようにしたところ、下記のようなエラーが発生。

Uncaught exception from servlet
com.google.appengine.api.memcache.MemcacheServiceException: Memcache put: Error setting single item (dev20111122.354967843445918452_KokucheeseSearchService__ALL_201111)
	at com.google.appengine.api.memcache.AsyncMemcacheServiceImpl$7.transform(AsyncMemcacheServiceImpl.java:427)
	at com.google.appengine.api.memcache.AsyncMemcacheServiceImpl$7.transform(AsyncMemcacheServiceImpl.java:419)
	at com.google.appengine.api.memcache.MemcacheServiceApiHelper$RpcResponseHandler.convertResponse(MemcacheServiceApiHelper.java:59)
	at com.google.appengine.api.memcache.MemcacheServiceApiHelper$1.wrap(MemcacheServiceApiHelper.java:98)
	at com.google.appengine.api.memcache.MemcacheServiceApiHelper$1.wrap(MemcacheServiceApiHelper.java:92)
	at com.google.appengine.api.utils.FutureWrapper.wrapAndCache(FutureWrapper.java:57)
	at com.google.appengine.api.utils.FutureWrapper.get(FutureWrapper.java:98)
	at com.google.appengine.api.utils.FutureWrapper.get(FutureWrapper.java:90)
	at com.google.appengine.api.memcache.MemcacheServiceImpl.quietGet(MemcacheServiceImpl.java:27)
	at com.google.appengine.api.memcache.MemcacheServiceImpl.put(MemcacheServiceImpl.java:79)
	at org.slim3.memcache.MemcacheDelegate.put(MemcacheDelegate.java:551)
	at org.slim3.memcache.Memcache.put(Memcache.java:361)

エラーメッセージからは分かりづらいですが、Memcacheに入れようとしたデータが1MB超えているためのエラーのようです。(実際計算したら1.3MBくらいでした)

シリアライズした状態のバイナリは実は全然圧縮されていないため、gzip圧縮かけてMemcacheに入れるためユーティリティを作りました。

(appengine 1.6.0, slim3 1.0.15で確認済)

使い方はテストコードを見ればだいたい分かると思いますが、Slim3のMemcacheとだいたい同じです。
注意点としてはrootPackage配下に置いておかないとhotReloading時にエラーになること。

気になるProduction Server上でのパフォーマンスはこんな感じです。

put時 (1.3MB -> 286KB)
serialize:53ms
compress:121ms

get時 (286KB -> 1.3MB)
uncompressed:55ms
deserialize:19ms

※データの大部分がStringなので結構圧縮率が高いです

圧縮や再シリアライズによる多少のオーバーヘッドはありますがこれくらいなら許容範囲かと。
体感的にもそんなに変わってないです。
他のライブラリに依存していないのでspin upによるコストもありません。

普通だと圧縮してまでMemcacheに入れるということはあまりないと思いますが、もしどうしても1つのデータとしてMemcacheに入れたい場合はこういう方法もあるといういい例ですね。

このチューニングを施した後は見事Datastore Readsを$0に抑えることができました。わーい!
チューニング後