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


                                          @v vakame


Wednesday, April 10, 13
自己紹介

                          わかめ まさひろ
                                   @v vakame
                                 GAE/J

                           TypeScript

                              AngularJS
Wednesday, April 10, 13
GAEもお金がかかる

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




Wednesday, April 10, 13
Money=√Evil
                                抜粋 http://goo.gl/AA9BA




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




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を活用する
                @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
                          • デフォルト無効…
                                                          wiki
Wednesday, 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
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/lp
Wednesday, 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

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

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