makeAsyncCallによる非同期クエリの実験

[Google App Engine] makeAsyncCallによる非同期クエリの実験結果がこちらのブログで紹介されています。
大変興味深かったので、自分でも環境を作り、実際に測定してみました。

テストデータと仕様
 (shin1ogawaさんのブログから引用)

1-1000までのIDを持つキーを作成し、そのキーのIDの数値が2で割り切れるならmod2という属性の値がtrue、割り切れないならfalseというカンジでmod3,mod5とか用意した。で、クエリは「2または3または5で割り切れる」というものを抽出するという簡単なもの。条件的にはmod2 EQUAL true OR mod3 EQUAL true OR mod5 EQUAL trueとなり、分けるとmod2 EQUAL true、mod3 EQUAL true、mod5 EQUAL trueの3本を走らせて結果をマージするというもの。うん、でも本当はそんな細かいとこはどうでも良くて、重要なのはクエリを分割実行してマージする、しかもクエリは非同期で一気にみっつ実行するという点です。

http://shin1o.blogspot.com/2010/01/or-appengine.html

今回測定したのは以下の3種です。

 (A) Datastore.query版 (同期)
 (B) makeSyncCall版 (同期)
 (C) makeAsyncCall版 (非同期)

結果
タイプA BC
368377189
384387222
434386499
445385231
465391178
389548195
388383218
393393194
391391188
372401203
平均402.9404.2231.7

(↑それぞれ12回ずつGAE環境で実行し、最少と最大の値は除外しています。)

テスト用コードはshin1ogawaさんの黒魔術コードを、ほぼそのままお借りしました。
なお、新しく作ったmakeSyncCall版は以下の通りです。

void sync2or3or5a(PrintWriter w) throws InterruptedException, ExecutionException {
	Query q2 = new Query(KIND).addFilter("mod2", FilterOperator.EQUAL, true);
	Query q3 = new Query(KIND).addFilter("mod3", FilterOperator.EQUAL, true);
	Query q5 = new Query(KIND).addFilter("mod5", FilterOperator.EQUAL, true);
	FetchOptions fetchOptions = FetchOptions.Builder.withOffset(0).limit(1000).prefetchSize(1000);

	long start = System.currentTimeMillis();

	DatastorePb.Query qPB2 = PbUtil.toQueryRequestPb(q2, fetchOptions);
	DatastorePb.Query qPB3 = PbUtil.toQueryRequestPb(q3, fetchOptions);
	DatastorePb.Query qPB5 = PbUtil.toQueryRequestPb(q5, fetchOptions);

	Delegate<Environment> delegate = ApiProxy.getDelegate();
	Environment env = ApiProxy.getCurrentEnvironment();
	List<byte[]> results = new ArrayList<byte[]>();

	results.add(delegate.makeSyncCall(env, "datastore_v3", "RunQuery", qPB2.toByteArray()));
	results.add(delegate.makeSyncCall(env, "datastore_v3", "RunQuery", qPB3.toByteArray()));
	results.add(delegate.makeSyncCall(env, "datastore_v3", "RunQuery", qPB5.toByteArray()));

	List<List<Entity>> lists = new ArrayList<List<Entity>>();
	for (int i = 0; i < results.size(); i++) {
		byte[] bytes = results.get(i);
		DatastorePb.QueryResult rPb = new DatastorePb.QueryResult();
		rPb.mergeFrom(bytes);
		Iterator<EntityProto> it = rPb.resultIterator();
		List<Entity> entities = new ArrayList<Entity>();
		while (it.hasNext()) {
			entities.add(EntityTranslator.createFromPb(it.next()));
		}
		lists.add(entities);
	}
	w.println("count=" + merge(lists).size() + ", " + (System.currentTimeMillis() - start) + "[ms]");
}