Contenu connexe
Plus de Masahiro Wakame (20)
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
- 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
- 12. めんどくさ…orz
テンプレコード多すぎ…
毎回毎回ガンバルの辛い…
規則もジャンバリ増えるし…
つらいわー…折れるわー…
Wednesday, April 10, 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
- 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
- 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
- 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
- 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
- 44. pull requestのお願い
• バグを見つけたら…
• 新しい良いStrategyを思いついたら…
• 性能の改善…
• RpcVisitorへのメソッドの追加
code review と、
• etc, etc... “問題なかったよ”
報告も嬉しいです!
Wednesday, April 10, 13
- 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
- 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