Upload
masahiro-wakame
View
4.501
Download
2
Embed Size (px)
DESCRIPTION
ajn #24 発表資料です
Citation preview
Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った in GAE/J
appengine ja night #24
@vvakame
Wednesday, April 10, 13
自己紹介
わかめ まさひろ@vvakame
GAE/J
TypeScript
AngularJSWednesday, April 10, 13
GAEもお金がかかる
課金額に困るぐらいの人気アプリ作りたい…orz
Wednesday, April 10, 13
Money=√Evil
細かい説明はshin1さんの資料参照http://goo.gl/DzkVW
抜粋 http://goo.gl/AA9BA
Wednesday, April 10, 13
この辺削りたい…
• 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
Memcacheを活用しよう!
Memcache = 無料!
Wednesday, April 10, 13
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
こんな操作だよね• Datastoreにデータを読出 and 保存を行う
• Memcacheからデータを読む
• Memcacheからデータが取れなかった場合、Datastoreからデータを取ってくる
• (データの操作とか)
• Datastoreに保存する
• Memcacheに保存する
Wednesday, April 10, 13
@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
正しい状態を保つ
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
課金が減るよ!
やったねわかめちゃん!
Memcacheを活用する
Wednesday, April 10, 13
めんどくさ…orz
テンプレコード多すぎ…
毎回毎回ガンバルの辛い…
規則もジャンバリ増えるし…
つらいわー…折れるわー…
Wednesday, April 10, 13
そこでオススメ
Wednesday, April 10, 13
Memvache
• めむばっしゅ と社内では読まれてます
• Datastoreの操作を勝手に書き換えます
• コードを変更する必要はありません
v vakame のv入れただけ感
Wednesday, April 10, 13
使用例@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
どうやって?
Wednesday, April 10, 13
GAEの構造
僕らのアプリが動くAppServerは別マシン上で動く色々なサービスと
連携して動作します!http://goo.gl/8Qrx8Google IO 2009 セッションより抜粋
Wednesday, April 10, 13
裏でデータ流れる
Remote Procedure Call はProtocol Buffers でSerializeされてから
やり取りされてます
僕らのアプリマシン
Datastoreマシン
データくれ~
はいよ~
RPC by TCP/IP(たぶん)
Wednesday, April 10, 13
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
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
色々な種類があるよ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
裏でコレやる!• Datastoreにデータを読出 and 保存を行う
• Memcacheからデータを読む
• Memcacheからデータが取れなかった場合、Datastoreからデータを取ってくる
• (データの操作とか)
• Datastoreに保存する
• Memcacheに保存する 再掲
Wednesday, April 10, 13
Memvacheについて
Wednesday, April 10, 13
戦略• GetとPutの置き換え
• GetPutCacheStrategy.java• Queryを自動的にKeysOnlyに書き換え
• QueryKeysOnlyStrategy.java• Queryまるごとキャッシュ
• AggressiveQueryCacheStrategy.java• デフォルト無効…
wikiWednesday, April 10, 13
その他• MemvacheFilter.java
• Filterとして実装されている
• オプションもここで読込
• MemvacheDelegate.java• “memvache” 名前空間にデータを貯めこむ
• Strategyの追加・削除もここで
Wednesday, April 10, 13
GetPutCacheStrategy• MemcacheのKey = EntityのKey
• Get, Put を覗き見していい感じにする
• Memcacheに蓄えたり
• キャッシュあったらそれ返したり
• Tx下だったら全部素通しする
• じゃないとTx適用されないからね…
Wednesday, April 10, 13
QueryKeysOnlyStrategy
• Entityも取得するQueryを書き換える
• KeysOnlyに書き換える
• Keyゲットしたら後はBatchGet
• PutGetCacheStrategyさーん!
• EventualなEntityとれる問題も回避!ajn #23 で言及がありましたが
Queryは古い内容取れる時があるそうなWednesday, April 10, 13
QueryKeysOnlyStrategy• EventualなEntityとれる問題?
• Queryは古いEntityが取れる時がある
• index更新遅れ…なんてチャチな(ry
• でもデータ超大量の時だけらしい?
• KeysOnly+BatchGet = Strong!• 少なくともEntityの内容は正しい
Wednesday, April 10, 13
AggressiveQueryCacheStrategy• Queryをまるごと自動でキャッシュ!• Kindが更新されたら消さないと…
• !参照できなけりゃよくね?• Kind単位にカウンタを持つ• EntityがPutされたら+1• MemcacheのKeyにカウンタを混ぜる
• 現在デフォ無効(→あとで詳しく)Namespace単位でのclearAllが欲しい…
Wednesday, April 10, 13
導入方法
Wednesday, April 10, 13
ダウンロード
https://github.com/vvakame/memvache
http://goo.gl/PYUw8
ソースコード
バイナリ
mvn, gradle ユーザはnet.vvakame:memvache
Wednesday, April 10, 13
ダウンロード
適当にクラスパスへ
Wednesday, April 10, 13
まずはテストに!
既存テストに突っ込んでみよう!
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
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
<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
@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
自作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
• AggressiveQueryCacheStrategy• memvache.properties
• expireSecod• Memcacheに保持する期間
• ignoreKind• キャッシュせずに素通しするKind
Strategy固有設定
将来的に名前を変更するかも…
expireSecond=100ignoreKind=ignore1,ignore2
Wednesday, April 10, 13
問題点
Wednesday, April 10, 13
問題点• Slim3以外での利用例が無い
• JPAとか生LowLevelAPIとか…
• わかめがSlim3と生LL APIの仕様の区別があまりついてない
• Projection Queryは考慮対象外• プロジェクト途中からの導入事例なし
• 新規プロジェクトでしか導入してない
Wednesday, April 10, 13
問題点• 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
問題点
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
pull requestのお願い
Wednesday, April 10, 13
pull requestのお願い• バグを見つけたら…
• 新しい良いStrategyを思いついたら…
• 性能の改善…
• RpcVisitorへのメソッドの追加
• etc, etc...code review と、“問題なかったよ”
報告も嬉しいです!
Wednesday, April 10, 13
閑話休題
Wednesday, April 10, 13
GAE活用事例
Wednesday, April 10, 13
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
BizReport
• 伊藤忠テクノソリューションズ様のSmartBiz+ を利用
• Android or iOS から簡単レポート作成
• 管理者向けUIなどでGAE/Jを利用
http://bizreport.topgate.co.jp/Wednesday, April 10, 13
Chienoki
• 社内ナレッジベース的な何か
• GAE+Appsを利用
http://chienoki.topgate.co.jp/lpWednesday, April 10, 13
Memvache...• 現在2案件で利用中…
• 両方リリースはまだ出来ていない
• 個人利用もちらほら• 俺とか元社員の人とか
• たまにIssueが発見・報告される• ぐぬぬ……
もっとみんな利用していってね!
Wednesday, April 10, 13
DatastoreV4について
Wednesday, April 10, 13
DatastoreV4…だと…!?
頼む~~~Next氏~~~~なくなってくだされ~~~
ヤバそう
状態コワイ
Wednesday, April 10, 13
GAE/Goの正式リリース
いつなんですかねー…?正式リリースされたらTG社がGoガチ勢化との噂も…
↑だいたいおがわさんの犯行
Wednesday, April 10, 13
GAE/Pのndbずるい
PythonではMemvache的なものがデフォルトであるそうじゃないですか!ずるい!流用可能なネタがあったら教えてくださ(ry
Wednesday, April 10, 13
質問?
なにかあるかな?スライド中のサンプルコードgithub.com/vvakame/ajn24-sample
Googleグループ(アナウンス等)
http://goo.gl/GiQRJ
Wednesday, April 10, 13