Datastoreへのアクセスを楽して         Memcacheアクセスに置き換える                      ライブラリ作った in GAE/J                          appengine j...
自己紹介                          わかめ まさひろ                                   @v vakame                                 GAE/J  ...
GAEもお金がかかる                                課金額に困るぐらいの                               人気アプリ作りたい…orzWednesday, April 10, 13
Money=√Evil                                抜粋 http://goo.gl/AA9BA                               細かい説明はshin1さんの資料参照        ...
この辺削りたい…                     • Entity Get                      • 1 read                     • Run Query                   ...
Memcacheを活用しよう!                          Memcache = 無料!Wednesday, April 10, 13
Memcacheを活用する                @Test                public void cacheEntity() throws EntityNotFoundException {              ...
こんな操作だよね                     • Datastoreにデータを読出 and 保存を行う                     • Memcacheからデータを読む                     • Mem...
Queryキャッシュしたり                @Test                public void cacheQuery() throws EntityNotFoundException {               ...
正しい状態を保つ                      @Test                      public void cacheQueryWithCleanup() throws EntityNotFoundExceptio...
Memcacheを活用する                            課金が減るよ!                           やったね                          わかめちゃん!Wednesday,...
めんどくさ…orz                           テンプレコード多すぎ…                           毎回毎回ガンバルの辛い…                           規則もジャンバリ増...
そこでオススメWednesday, April 10, 13
Memvache                     • めむばっしゅ と社内では読まれてます                     • Datastoreの操作を勝手に書き換えます                     • コードを変...
使用例            @Test            public void test() throws EntityNotFoundException {            	   final DatastoreService ...
どうやって?Wednesday, April 10, 13
GAEの構造                                  僕らのアプリが動くAppServerは       Google IO 2009 セッションより抜粋   別マシン上で動く色々なサービスと       http:/...
裏でデータ流れる                                データくれ∼             僕らのアプリ                                     Datastore           ...
Delegateさーん!   @Test   public void delegate() {   	   final Delegate<Environment> original = ApiProxy.getDelegate();   	  ...
PBでdeserializeする                    @Test                    public void deserialize() {                    	   // 1つ前のスライ...
色々な種類があるよ                           if ("datastore_v3".equals(service) && "BeginTransaction".equals(method)) {            ...
裏でコレやる!                     • Datastoreにデータを読出 and 保存を行う                     • Memcacheからデータを読む                     • Memc...
MemvacheについてWednesday, April 10, 13
戦略                     • GetとPutの置き換え                          • GetPutCacheStrategy.java                     • Queryを自動的に...
その他                     • MemvacheFilter.java                          • Filterとして実装されている                          • オプション...
GetPutCacheStrategy                     • MemcacheのKey = EntityのKey                     • Get, Put を覗き見していい感じにする          ...
QueryKeysOnlyStrategy                     • Entityも取得するQueryを書き換える                          • KeysOnlyに書き換える              ...
QueryKeysOnlyStrategy                     • EventualなEntityとれる問題?                          • Queryは古いEntityが取れる時がある       ...
AggressiveQueryCacheStrategy                     • Queryをまるごと自動でキャッシュ!                     • Kindが更新されたら消さないと…            ...
導入方法Wednesday, April 10, 13
ダウンロード         ソースコード           https://github.com/vvakame/memvache         バイナリ           http://goo.gl/PYUw8            ...
ダウンロード                              適当にクラスパスへWednesday, April 10, 13
まずはテストに!                          public class MemvacheAppEngineTestCase extends AppEngineTestCase {                      ...
web.xmlでの設定              <filter>              	 <filter-name>memvache</filter-name>              	 <filter-class>net.vvak...
web.xmlでの設定          <filter>          	 <filter-name>memvache</filter-name>          	 <filter-class>net.vvakame.memvache...
自作Strategyを追加                      @Before                      @Override                      public void setUp() throws ...
自作Strategyを追加                     @Before                     @Override                     public void setUp() throws Exc...
Strategy固有設定                • AggressiveQueryCacheStrategy                     • memvache.properties                      ...
問題点Wednesday, April 10, 13
問題点                     • Slim3以外での利用例が無い                          • JPAとか生LowLevelAPIとか…                          • わかめがS...
問題点                     • AggressiveQueryCacheStrategy…                      • 現在デフォルトで無効                          • datas...
問題点                     • AggressiveQueryCacheStrategy(続き)                          • CursorにはID的なものが振られる                 ...
pull requestのお願いWednesday, April 10, 13
pull requestのお願い                     • バグを見つけたら…                     • 新しい良いStrategyを思いついたら…                     • 性能の改善… ...
閑話休題Wednesday, April 10, 13
GAE活用事例Wednesday, April 10, 13
GAE or Android                     • TG社のお仕事                          • Androidアプリ開発! 3∼4割                          • GAE/...
BizReport                     • 伊藤忠テクノソリューションズ様の                          SmartBiz+ を利用                     • Android or i...
Chienoki                     • 社内ナレッジベース的な何か                     • GAE+Appsを利用                              http://chienok...
Memvache...                     • 現在2案件で利用中…                          • 両方リリースはまだ出来ていない                     • 個人利用もちらほら   ...
DatastoreV4についてWednesday, April 10, 13
DatastoreV4…だと…!?                          ヤバそう                          頼む∼∼∼                          Next氏∼∼∼∼         ...
GAE/Goの正式リリース                          いつなんですかねー…?                          正式リリースされたら                          TG社がGoガチ勢化...
GAE/Pのndbずるい                               PythonではMemvache的なものが                               デフォルトであるそうじゃないで            ...
質問?                  なにかあるかな?                          スライド中のサンプルコード                          github.com/vvakame/ajn24-sam...
Upcoming SlideShare
Loading in...5
×

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

4,755

Published on

ajn #24 発表資料です

Published in: Technology
0 Comments
15 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
4,755
On Slideshare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
7
Comments
0
Likes
15
Embeds 0
No embeds

No notes for slide

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

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

    Clipping is a handy way to collect important slides you want to go back to later.

×