55
Datastoreへのアクセスを楽して Memcacheアクセスに置き換える ライブラリ作った in GAE/J appengine ja night #24 @vvakame Wednesday, April 10, 13

Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

Embed Size (px)

DESCRIPTION

ajn #24 発表資料です

Citation preview

Page 1: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った in GAE/J

appengine ja night #24

@vvakame

Wednesday, April 10, 13

Page 2: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

自己紹介

わかめ まさひろ@vvakame

GAE/J

TypeScript

AngularJSWednesday, April 10, 13

Page 3: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

GAEもお金がかかる

課金額に困るぐらいの人気アプリ作りたい…orz

Wednesday, April 10, 13

Page 4: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

Money=√Evil

細かい説明はshin1さんの資料参照http://goo.gl/DzkVW

抜粋 http://goo.gl/AA9BA

Wednesday, April 10, 13

Page 5: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

この辺削りたい…

• Entity Get• 1 read

• Run Query• 1 read + 1 read per entity retrieved

• Run Query (keys only)• 1 read + 1 small per key retrieved

Writeは削減できないけどReadなら…Readならきっと…!

Wednesday, April 10, 13

Page 6: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

Memcacheを活用しよう!

Memcache = 無料!

Wednesday, April 10, 13

Page 7: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

Memcacheを活用する@Testpublic void cacheEntity() throws EntityNotFoundException { final DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); final MemcacheService memcache = MemcacheServiceFactory.getMemcacheService();

Key key; { // 保存時にMemcacheに突っ込んでおこう Entity entity = new Entity("sample"); entity.setProperty("str", "Hello memcache!"); datastore.put(entity); key = entity.getKey();

memcache.put(key, entity); } { // 読出時にMemcacheをまずチェック! // あるかなー…? Entity entity = (Entity) memcache.get(key); if (entity == null) { // なかったわー entity = datastore.get(key); } }}

全ての操作をこんな感じに!

Wednesday, April 10, 13

Page 8: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

こんな操作だよね• Datastoreにデータを読出 and 保存を行う

• Memcacheからデータを読む

• Memcacheからデータが取れなかった場合、Datastoreからデータを取ってくる

• (データの操作とか)

• Datastoreに保存する

• Memcacheに保存する

Wednesday, April 10, 13

Page 9: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

@Testpublic void cacheQuery() throws EntityNotFoundException {

前略 2件 sample kind の Entity を保存しました。 final MemcacheService memcache = MemcacheServiceFactory.getMemcacheService(); { // 初回はキャッシュされていない Query query = new Query("sample"); List<Entity> list = (List<Entity>) memcache.get(query); if (list == null) { list = datastore.prepare(query).asList(FetchOptions.Builder.withDefaults()); memcache.put(query, list); } assertThat("2件検索される", list.size(), is(2)); } { // 2回目はMemcacheから読み出せる Query query = new Query("sample"); List<Entity> list = (List<Entity>) memcache.get(query); if (list == null) { list = datastore.prepare(query).asList(FetchOptions.Builder.withDefaults()); memcache.put(query, list); } assertThat("2件検索される", list.size(), is(2)); }}

Queryキャッシュしたり

Queryもキャッシュ

false

true

Wednesday, April 10, 13

Page 10: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

正しい状態を保つ

Putがあったら消す

全ての箇所で

@Testpublic void cacheQueryWithCleanup() throws EntityNotFoundException {

前略 Query をキャッシュしてあります { Entity entity = new Entity("sample"); entity.setProperty("str", "Good night!"); datastore.put(entity);

// sample kind に Put があったらキャッシュ消す Query query = new Query("sample"); memcache.delete(query); @SuppressWarnings("unchecked") List<Entity> list = (List<Entity>) memcache.get(query); assertThat("キャッシュ消去済", list, nullValue()); }}

Wednesday, April 10, 13

Page 11: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

課金が減るよ!

やったねわかめちゃん!

Memcacheを活用する

Wednesday, April 10, 13

Page 12: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

めんどくさ…orz

テンプレコード多すぎ…

毎回毎回ガンバルの辛い…

規則もジャンバリ増えるし…

つらいわー…折れるわー…

Wednesday, April 10, 13

Page 13: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

そこでオススメ

Wednesday, April 10, 13

Page 14: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

Memvache

• めむばっしゅ と社内では読まれてます

• Datastoreの操作を勝手に書き換えます

• コードを変更する必要はありません

v vakame のv入れただけ感

Wednesday, April 10, 13

Page 15: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

使用例@Testpublic void test() throws EntityNotFoundException { final DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); final MemcacheService memcache = MemcacheServiceFactory.getMemcacheService();

Key key; { // 明示的にMemcacheは使っていないですよ。 Entity entity = new Entity("sample"); entity.setProperty("str", "Hello memcache!");

assertThat("作成前", memcache.getStatistics().getItemCount(), is(0L)); datastore.put(entity); assertThat("作成後", memcache.getStatistics().getItemCount(), not(0L));

key = entity.getKey(); } { // Memvacheさんありがとう Entity entity = datastore.get(key); assertThat(entity, notNullValue()); } assertThat("見かけ上のRPC", counter2.countMap.get("datastore_v3@Get"), is(1)); assertThat("Memvache適用後", counter1.countMap.get("datastore_v3@Get"), is(0));}

It’s magic!

Wednesday, April 10, 13

Page 16: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

どうやって?

Wednesday, April 10, 13

Page 17: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

GAEの構造

僕らのアプリが動くAppServerは別マシン上で動く色々なサービスと

連携して動作します!http://goo.gl/8Qrx8Google IO 2009 セッションより抜粋

Wednesday, April 10, 13

Page 18: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

裏でデータ流れる

Remote Procedure Call はProtocol Buffers でSerializeされてから

やり取りされてます

僕らのアプリマシン

Datastoreマシン

データくれ~

はいよ~

RPC by TCP/IP(たぶん)

Wednesday, April 10, 13

Page 19: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

Delegateさーん!@Testpublic void delegate() { final Delegate<Environment> original = ApiProxy.getDelegate(); ApiProxy.setDelegate(new DelegateStub(original) {

// DelegateStubはDelegateインタフェースのゴチャゴチャを適当に元のDelegateに投げるよう実装した自前クラスです。 @Override public byte[] makeSyncCall(Environment environment, String packageName, String methodName, byte[] request) throws ApiProxyException { logger.info("sync packageName=" + packageName + ", methodName=" + methodName); return super.makeSyncCall(environment, packageName, methodName, request); }

@Override public Future<byte[]> makeAsyncCall(Environment environment, String packageName, String methodName,

byte[] request, ApiConfig apiConfig) { logger.info("async packageName=" + packageName + ", methodName=" + methodName); return super.makeAsyncCall(environment, packageName, methodName, request, apiConfig); } });

final DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); Entity entity = new Entity("sample"); // INFO: async packageName=datastore_v3, methodName=Put datastore.put(entity);

ApiProxy.setDelegate(original);}

各種RPCの通信内容を傍受できるよ!

Wednesday, April 10, 13

Page 20: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

PBでdeserializeする

packageNameとmethodNameの組み合わせ毎にクラスが代わります

@Testpublic void deserialize() { // 1つ前のスライドから変更なしです! @Override public Future<byte[]> makeAsyncCall(Environment environment, String packageName,

String methodName, byte[] request, ApiConfig apiConfig) {

if ("datastore_v3".equals(packageName) && "Put".equals(methodName)) { PutRequest requestPb = new PutRequest(); requestPb.mergeFrom(request); logger.info(requestPb.toString()); } // 1つ前のスライドから変更なしです! } // 1つ前のスライドから変更なしです!}

entity < key < app: "Unit Tests" path < Element { type: "sample" } > > entity_group <

>>

Wednesday, April 10, 13

Page 21: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

色々な種類があるよif ("datastore_v3".equals(service) && "BeginTransaction".equals(method)) { BeginTransactionRequest requestPb = new BeginTransactionRequest(); requestPb.mergeFrom(request); return pre_datastore_v3_BeginTransaction(requestPb);} else if ("datastore_v3".equals(service) && "Put".equals(method)) { PutRequest requestPb = new PutRequest(); requestPb.mergeFrom(request); return pre_datastore_v3_Put(requestPb);} else if ("datastore_v3".equals(service) && "Get".equals(method)) { GetRequest requestPb = new GetRequest(); requestPb.mergeFrom(request); return pre_datastore_v3_Get(requestPb);} else if ("datastore_v3".equals(service) && "Delete".equals(method)) { DeleteRequest requestPb = new DeleteRequest(); requestPb.mergeFrom(request); return pre_datastore_v3_Delete(requestPb);} else if ("datastore_v3".equals(service) && "RunQuery".equals(method)) { Query requestPb = new Query(); requestPb.mergeFrom(request); return pre_datastore_v3_RunQuery(requestPb);} else if ("datastore_v3".equals(service) && "Next".equals(method)) { NextRequest requestPb = new NextRequest(); requestPb.mergeFrom(request); return pre_datastore_v3_Next(requestPb);} else if ("datastore_v3".equals(service) && "Commit".equals(method)) { Transaction requestPb = new Transaction(); requestPb.mergeFrom(request); return pre_datastore_v3_Commit(requestPb);} else if ("datastore_v3".equals(service) && "Rollback".equals(method)) { Transaction requestPb = new Transaction(); requestPb.mergeFrom(request); return pre_datastore_v3_Rollback(requestPb);} else if ("memcache".equals(service) && "Set".equals(method)) { try { MemcacheSetRequest requestPb = MemcacheSetRequest.parseFrom(request); return pre_memcache_Set(requestPb); } catch (com.google.appengine.repackaged.com.google.protobuf.InvalidProtocolBufferException e) { throw new IllegalStateException("raise exception at " + service + ", " + method, e); }} else if ("memcache".equals(service) && "Get".equals(method)) {Wednesday, April 10, 13

Page 22: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

裏でコレやる!• Datastoreにデータを読出 and 保存を行う

• Memcacheからデータを読む

• Memcacheからデータが取れなかった場合、Datastoreからデータを取ってくる

• (データの操作とか)

• Datastoreに保存する

• Memcacheに保存する 再掲

Wednesday, April 10, 13

Page 23: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

Memvacheについて

Wednesday, April 10, 13

Page 24: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

戦略• GetとPutの置き換え

• GetPutCacheStrategy.java• Queryを自動的にKeysOnlyに書き換え

• QueryKeysOnlyStrategy.java• Queryまるごとキャッシュ

• AggressiveQueryCacheStrategy.java• デフォルト無効…

wikiWednesday, April 10, 13

Page 25: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

その他• MemvacheFilter.java

• Filterとして実装されている

• オプションもここで読込

• MemvacheDelegate.java• “memvache” 名前空間にデータを貯めこむ

• Strategyの追加・削除もここで

Wednesday, April 10, 13

Page 26: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

GetPutCacheStrategy• MemcacheのKey = EntityのKey

• Get, Put を覗き見していい感じにする

• Memcacheに蓄えたり

• キャッシュあったらそれ返したり

• Tx下だったら全部素通しする

• じゃないとTx適用されないからね…

Wednesday, April 10, 13

Page 27: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

QueryKeysOnlyStrategy

• Entityも取得するQueryを書き換える

• KeysOnlyに書き換える

• Keyゲットしたら後はBatchGet

• PutGetCacheStrategyさーん!

• EventualなEntityとれる問題も回避!ajn #23 で言及がありましたが

Queryは古い内容取れる時があるそうなWednesday, April 10, 13

Page 28: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

QueryKeysOnlyStrategy• EventualなEntityとれる問題?

• Queryは古いEntityが取れる時がある

• index更新遅れ…なんてチャチな(ry

• でもデータ超大量の時だけらしい?

• KeysOnly+BatchGet = Strong!• 少なくともEntityの内容は正しい

Wednesday, April 10, 13

Page 29: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

AggressiveQueryCacheStrategy• Queryをまるごと自動でキャッシュ!• Kindが更新されたら消さないと…

• !参照できなけりゃよくね?• Kind単位にカウンタを持つ• EntityがPutされたら+1• MemcacheのKeyにカウンタを混ぜる

• 現在デフォ無効(→あとで詳しく)Namespace単位でのclearAllが欲しい…

Wednesday, April 10, 13

Page 30: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

導入方法

Wednesday, April 10, 13

Page 31: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

ダウンロード

https://github.com/vvakame/memvache

http://goo.gl/PYUw8

ソースコード

バイナリ

mvn, gradle ユーザはnet.vvakame:memvache

Wednesday, April 10, 13

Page 32: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

ダウンロード

適当にクラスパスへ

Wednesday, April 10, 13

Page 33: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

まずはテストに!

既存テストに突っ込んでみよう!

public class MemvacheAppEngineTestCase extends AppEngineTestCase { MemvacheDelegate delegate;

@Before @Override public void setUp() throws Exception { super.setUp();

delegate = MemvacheDelegate.install(); }

@After @Override public void tearDown() throws Exception { delegate.uninstall();

super.tearDown(); }}

落ちないはずなのでもし落ちたらIssueへ…

Wednesday, April 10, 13

Page 34: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

web.xmlでの設定

普通にフィルタを設定してください

<filter> <filter-name>memvache</filter-name> <filter-class>net.vvakame.memvache.MemvacheFilter</filter-class></filter><filter-mapping> <filter-name>memvache</filter-name> <url-pattern>/*</url-pattern></filter-mapping>

Wednesday, April 10, 13

Page 35: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

<filter> <filter-name>memvache</filter-name> <filter-class>net.vvakame.memvache.MemvacheFilter</filter-class> <init-param> <param-name>enableGetPutCacheStrategy</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>enableQueryKeysOnlyStrategy</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>enableAggressiveQueryCacheStrategy</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>enableDebugMode</param-name> <param-value>true</param-value> </init-param></filter>

web.xmlでの設定

オプションもあるよ!

Wednesday, April 10, 13

Page 36: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

@Before@Overridepublic void setUp() throws Exception { MemvacheDelegate.addStrategy(OreOre1Strategy.class);

super.setUp();}

public static class OreOre1Strategy implements Strategy { @Override public Pair<byte[], byte[]> preProcess(String packageName, String method,

byte[] request) { // Pair.request でリクエストを書き換える // Pair.response でレスポンスを生成して返す // null を返して何もしない return null; } @Override public byte[] postProcess(String packageName, String method,

byte[] request, byte[] response) { // レスポンスを書き換えて返す // null を返して何もしない return null; }}

自作Strategyを追加

利用のために自作Filterを作成するのが良さげカナ

Wednesday, April 10, 13

Page 37: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

自作Strategyを追加

便利なヘルパクラスも用意してあります

@Before@Overridepublic void setUp() throws Exception { MemvacheDelegate.addStrategy(OreOre2Strategy.class);

super.setUp();}

public static class OreOre2Strategy extends RpcVisitor {

@Override public Pair<byte[], byte[]> pre_datastore_v3_Put(PutRequest requestPb) { // packageName, method ごとに変換済のオブジェクトが渡される。後は Strategy と変わらない。 return null; }

@Override public byte[] post_datastore_v3_Put(PutRequest requestPb, PutResponse responsePb) { // 同上 足りないものは pull request 待ってます✩

return null; }}

Wednesday, April 10, 13

Page 38: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

• AggressiveQueryCacheStrategy• memvache.properties

• expireSecod• Memcacheに保持する期間

• ignoreKind• キャッシュせずに素通しするKind

Strategy固有設定

将来的に名前を変更するかも…

expireSecond=100ignoreKind=ignore1,ignore2

Wednesday, April 10, 13

Page 39: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

問題点

Wednesday, April 10, 13

Page 40: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

問題点• Slim3以外での利用例が無い

• JPAとか生LowLevelAPIとか…

• わかめがSlim3と生LL APIの仕様の区別があまりついてない

• Projection Queryは考慮対象外• プロジェクト途中からの導入事例なし

• 新規プロジェクトでしか導入してない

Wednesday, April 10, 13

Page 41: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

問題点• AggressiveQueryCacheStrategy…

• 現在デフォルトで無効

• datastore_v3#Next が… orz

• 内部的に状態を持ってるので不用意にキャッシュできなさそう…

cursor < cursor: 0x0 app: "Unit Tests">count: 0x7ffffffecompile: true

FetchOptions fetchOptions = FetchOptions.Builder.withLimit(10);

fetchOptions.prefetchSize(1);if (cursor != null) { fetchOptions.startCursor(cursor);}list = prepare.asQueryResultList(fetchOptions);System.out.println("list size=" + list.size());totalLength += list.size();

NextRequest requestPb

Wednesday, April 10, 13

Page 42: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

問題点

Queryで10Entity取得 = 1 read + 10 read

KeysOnly+Memcache= 1 read + 10 small opsAggressiveQuery(ry = 0 

• AggressiveQueryCacheStrategy(続き)• CursorにはID的なものが振られる

• キャッシュしたQueryだとIDが…• 後でNextが発生するとヤバい><

• limit<prefetchSize ならOK…?• 教えて識者の人><

Wednesday, April 10, 13

Page 43: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

pull requestのお願い

Wednesday, April 10, 13

Page 44: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

pull requestのお願い• バグを見つけたら…

• 新しい良いStrategyを思いついたら…

• 性能の改善…

• RpcVisitorへのメソッドの追加

• etc, etc...code review と、“問題なかったよ”

報告も嬉しいです!

Wednesday, April 10, 13

Page 45: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

閑話休題

Wednesday, April 10, 13

Page 46: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

GAE活用事例

Wednesday, April 10, 13

Page 47: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

GAE or Android• TG社のお仕事

• Androidアプリ開発! 3~4割

• GAE/J (Apps抜き)!! 2~3割• GAE/J (Apps有り)!! 2~3割

• GAE/J + EC2!! ! ! 1割• 今後はGAE/J + GCE かなぁ

TG社はジャンバリGAEです!Memvacheも普通に使っていきます。

Wednesday, April 10, 13

Page 48: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

BizReport

• 伊藤忠テクノソリューションズ様のSmartBiz+ を利用

• Android or iOS から簡単レポート作成

• 管理者向けUIなどでGAE/Jを利用

http://bizreport.topgate.co.jp/Wednesday, April 10, 13

Page 49: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

Chienoki

• 社内ナレッジベース的な何か

• GAE+Appsを利用

http://chienoki.topgate.co.jp/lpWednesday, April 10, 13

Page 50: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

Memvache...• 現在2案件で利用中…

• 両方リリースはまだ出来ていない

• 個人利用もちらほら• 俺とか元社員の人とか

• たまにIssueが発見・報告される• ぐぬぬ……

もっとみんな利用していってね!

Wednesday, April 10, 13

Page 51: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

DatastoreV4について

Wednesday, April 10, 13

Page 52: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

DatastoreV4…だと…!?

頼む~~~Next氏~~~~なくなってくだされ~~~

ヤバそう

状態コワイ

Wednesday, April 10, 13

Page 53: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

GAE/Goの正式リリース

いつなんですかねー…?正式リリースされたらTG社がGoガチ勢化との噂も…

↑だいたいおがわさんの犯行

Wednesday, April 10, 13

Page 54: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

GAE/Pのndbずるい

PythonではMemvache的なものがデフォルトであるそうじゃないですか!ずるい!流用可能なネタがあったら教えてくださ(ry

Wednesday, April 10, 13

Page 55: Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った

質問?

なにかあるかな?スライド中のサンプルコードgithub.com/vvakame/ajn24-sample

Googleグループ(アナウンス等)

http://goo.gl/GiQRJ

Wednesday, April 10, 13