• Like
  • Save
Datastoreへのアクセスを楽してMemcacheアクセスに置き換えるライブラリ作った
Upcoming SlideShare
Loading in...5
×
 

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

on

  • 4,364 views

ajn #24 発表資料です

ajn #24 発表資料です

Statistics

Views

Total Views
4,364
Views on SlideShare
4,316
Embed Views
48

Actions

Likes
12
Downloads
5
Comments
0

3 Embeds 48

https://twitter.com 39
http://tweetedtimes.com 8
https://www.chatwork.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

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

    • Datastoreへのアクセスを楽して Memcacheアクセスに置き換える ライブラリ作った in GAE/J appengine ja night #24 @v vakameWednesday, April 10, 13
    • 自己紹介 わかめ まさひろ @v vakame GAE/J TypeScript AngularJSWednesday, April 10, 13
    • GAEもお金がかかる 課金額に困るぐらいの 人気アプリ作りたい…orzWednesday, April 10, 13
    • Money=√Evil 抜粋 http://goo.gl/AA9BA 細かい説明はshin1さんの資料参照 http://goo.gl/DzkVWWednesday, 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を活用する @Test public 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
    • Queryキャッシュしたり @Test public 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) {false 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) {true list = datastore.prepare(query).asList(FetchOptions.Builder.withDefaults()); memcache.put(query, list); } assertThat("2件検索される", list.size(), is(2)); Queryも } キャッシュ }Wednesday, April 10, 13
    • 正しい状態を保つ @Test public 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()); } } Putがあったら消す 全ての箇所で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
    • 使用例 @Test public 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さんありがとう It’s magic! 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)); }Wednesday, April 10, 13
    • どうやって?Wednesday, April 10, 13
    • GAEの構造 僕らのアプリが動くAppServerは Google IO 2009 セッションより抜粋 別マシン上で動く色々なサービスと http://goo.gl/8Qrx8 連携して動作します!Wednesday, April 10, 13
    • 裏でデータ流れる データくれ∼ 僕らのアプリ Datastore RPC by TCP/IP マシン (たぶん) マシン はいよ∼ Remote Procedure Call は Protocol Buffers でSerializeされてから やり取りされてますWednesday, April 10, 13
    • Delegateさーん! @Test public 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); 各種RPCの通信内容を ApiProxy.setDelegate(original); } 傍受できるよ!Wednesday, April 10, 13
    • PBでdeserializeする @Test public 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); entity < logger.info(requestPb.toString()); key < } app: "Unit Tests" // 1つ前のスライドから変更なしです! path < } Element { // 1つ前のスライドから変更なしです! type: "sample" } } > > entity_group < > > packageNameとmethodNameの 組み合わせ毎にクラスが代わります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:memvacheWednesday, 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
    • web.xmlでの設定 <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> オプションもあるよ!Wednesday, April 10, 13
    • 自作Strategyを追加 @Before @Override public 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 を返して何もしない 利用のために自作Filterを return null; } } 作成するのが良さげカナWednesday, April 10, 13
    • 自作Strategyを追加 @Before @Override public 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
    • Strategy固有設定 • AggressiveQueryCacheStrategy • memvache.properties • expireSecod • Memcacheに保持する期間 • ignoreKind • キャッシュせずに素通しするKind expireSecond=100 将来的に名前を ignoreKind=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 • 内部的に状態を持ってるので 不用意にキャッシュできなさそう… FetchOptions fetchOptions = FetchOptions.Builder.withLimit(10); NextRequest requestPb fetchOptions.prefetchSize(1); cursor < if (cursor != null) { cursor: 0x0 fetchOptions.startCursor(cursor); app: "Unit Tests" } > list = prepare.asQueryResultList(fetchOptions); count: 0x7ffffffe System.out.println("list size=" + list.size()); compile: true totalLength += list.size();Wednesday, April 10, 13
    • 問題点 • AggressiveQueryCacheStrategy(続き) • CursorにはID的なものが振られる • キャッシュしたQueryだとIDが… • 後でNextが発生するとヤバい>< • limit<prefetchSize ならOK…? • 教えて識者の人>< Queryで10Entity取得 = 1 read + 10 read KeysOnly+Memcache = 1 read + 10 small ops AggressiveQuery(ry = 0 Wednesday, April 10, 13
    • pull requestのお願いWednesday, April 10, 13
    • pull requestのお願い • バグを見つけたら… • 新しい良いStrategyを思いついたら… • 性能の改善… • RpcVisitorへのメソッドの追加 code review と、 • etc, etc... “問題なかったよ” 報告も嬉しいです!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的なものが デフォルトであるそうじゃないで すか!ずるい!流用可能なネタが あったら教えてくださ(ryWednesday, April 10, 13
    • 質問? なにかあるかな? スライド中のサンプルコード github.com/vvakame/ajn24-sample Googleグループ(アナウンス等) http://goo.gl/GiQRJWednesday, April 10, 13