先のエントリで書いた#appengine でクエリの結果件数を取得する方法(1000件超とか)の続きです。
どーしても動的な条件に対する結果件数を…ていうなら、最悪Low-level APIでsetKeysOnly()しつつページング(常に条件を変えつつoffset0~limit1000で)、で数えるという手もあるかもしれない。これもまた試してみよう。この方法なら1000件につき約110msなんで、7000件でも770msくらいで数えきれるはずだ。
気になって眠れないので引き続きこちらの方法も試してみた。
手法
AppEngineでのページング手法としてBest practices for writing scalable applicationsでも書かれているページング方法で、setKeysOnly()を使う場合、使わない場合のふたつのパターンを試してみた。どちらもasList()
を使い、FetchOption
にはwithOffset(0).limit(1000)
だけを指定している。setKeysOnly()を使った方は当然条件に使用するpropertyの値がfetchできないので、ページングに必要なEntityのみDatastoreService.get(key)
でfetchしている。
結果、setKeysOnly()しない方は平均3642ms/7473件で、setKeysOnly()した方は平均1441ms/7473件となった。JDOを使ってアレコレして1000件以上取得する場合は7218ms/7473件だったから、随分と速くはなった。どーーーーしても1000件以上の件数をカウントしたい場合はlow-level APIで件数の為にページングする、といった手法が一番現実的ですね。それでも遅いけど!FetchOption
をもぅちっとカスタマイズすれば、まだ速くなるかなぁ?
ページング部分のコード
最近はgistが重いっぽいので久々にSyntaxHighlighterで。PersonというEntityのheightという、値が重複する可能性があるpropertyを条件に検索する、という想定。
static int countEntities(DatastoreService service, Double param) { int pageNo = 1; int totalCount = 0; PagingResultVO result = null; while (true) { Query query = new Query(Person.class.getSimpleName()) .addSort("height", SortDirection.ASCENDING) .addSort("__key__", SortDirection.ASCENDING); if (pageNo == 1) { query.addFilter("height", FilterOperator.GREATER_THAN_OR_EQUAL, param); result = getListInPage(service, query); totalCount += result.count; } else { // まずは重複する可能性のあるpropertyをequality filterし、 // 次ページの先頭となるpropertyをgreater thanでfilterしてクエリする。 Key nextKey = result.nextEntity.getKey(); Object nextParam = result.nextEntity.getProperty("height"); query.addFilter("height", FilterOperator.EQUAL, nextParam) .addFilter("__key__", FilterOperator.GREATER_THAN_OR_EQUAL, nextKey); PagingResultVO dupResult = getListInPage(service, query); totalCount += dupResult.count; if (dupResult.hasNext) { // 1page分丸ごと取得できた。 result = dupResult; } else { // 先の条件だと1page分に足りないので続きを取得する。 // GT_OR_EQではなくGTを使う。 query = new Query(Person.class.getSimpleName())// .addSort("height", SortDirection.ASCENDING)// .addSort("__key__", SortDirection.ASCENDING)// .addFilter("height", FilterOperator.GREATER_THAN, nextParam); result = getListInPage(service, query); totalCount += result.count; } } if (!result.hasNext) { break; } else { // 1000件取得している場合は、最後の1件が次pageにも登場するので1件分引いておく。 totalCount -= 1; } pageNo += 1; } return totalCount; } static PagingResultVO getListInPage(DatastoreService service, Query query) { FetchOptions fetchOptions = FetchOptions.Builder.withOffset(0).limit(1000); List<Entity> list = service.prepare(query.setKeysOnly()).asList(fetchOptions); int size = list.size(); if (size > 999) { return new PagingResultVO(service, size, list.get(999)); } else { return new PagingResultVO(service, size, null); } } public static class PagingResultVO { public final int count; public final Entity nextEntity; // 次のページの最初のEntity public final boolean hasNext; // 次のページが存在するか。 public PagingResultVO(DatastoreService service, int size, Entity nextEntity) { this.count = size; if (nextEntity != null) { // keyしか入っていないので、filterに必要なpropertyを取得する必要がある。 try { this.nextEntity = service.get(nextEntity.getKey()); } catch (EntityNotFoundException e) { LOGGER.warning("あるはずのKeyが見つからない!" + nextEntity.getKey()); throw new RuntimeException(e); } } else { this.nextEntity = nextEntity; // null } this.hasNext = nextEntity != null; } }
0 件のコメント:
コメントを投稿