ほげにっき

hogedigoの日記

PreparedQuery.asListは非同期処理だった

GAE/Jのドキュメントを見ていて、↓の様な記述を見つけた。

We do not currently expose an explicitly async API for queries. However, when you invoke PreparedQuery.asIterable(), PreparedQuery.asIterator() or PreparedQuery.asList(FetchOptions fetchOptions), both DatastoreService and AsyncDatastoreService immediately return and asynchronously prefetch results. This allows your application to perform work in parallel while query results are fetched.

https://developers.google.com/appengine/docs/java/datastore/async#Async_Queries

抜粋意訳↓
「クエリーには明示的な非同期APIはないけど、PreparedQueryのasIterable、asIterator、asList(FetchOptions fetchOptions)は即時にreturnされて結果が非同期でフェッチされるよ」

asIteratorとasIterableが非同期なのはしっくりくるけど、asListが非同期だとは知らんかった。
結果のListがFutureみたいな役割をしているってことかな。

ちなみに日本語ドキュメントの方は↓

残念ながら、PreparedQuery.asList() については同じように動作しません。以降のリリースでこの問題を解決できるように努力しております。

https://developers.google.com/appengine/docs/java/datastore/async?hl=ja#Async_Queries

と書かれており、残念ながら翻訳が追いついていないようだ。(-_-;


実験してみた。※Slim3使用

        List<Book> list1 =
            Datastore
                .query(Book.class)
                .prefetchSize(500)
                .filter(BookMeta.get().title.greaterThan("title000"))
                .asList();

        List<Book> list2 =
            Datastore
                .query(Book.class)
                .prefetchSize(500)
                .filter(BookMeta.get().title.greaterThan("title001"))
                .asList();

        List<Book> list3 =
            Datastore
                .query(Book.class)
                .prefetchSize(500)
                .filter(BookMeta.get().title.greaterThan("title002"))
                .asList();

        int list1Size = list1.size();
        int list2Size = list2.size();
        int list3Size = list3.size();

API実行の様子(appstats使用)↓

あれ??非同期処理になってない・・・
と思ってSlim3のコードを見たら、ModelQuery.asListはEntityを全件Modelに変換している為強制的に同期メソッドになっていた。残念!


気を取り直してModelQuery.asEntityListを使用して再トライ。

        List<Entity> list1 =
            Datastore
                .query(Book.class)
                .prefetchSize(500)
                .filter(BookMeta.get().title.greaterThan("title000"))
                .asEntityList();

        List<Entity> list2 =
            Datastore
                .query(Book.class)
                .prefetchSize(500)
                .filter(BookMeta.get().title.greaterThan("title001"))
                .asEntityList();

        List<Entity> list3 =
            Datastore
                .query(Book.class)
                .prefetchSize(500)
                .filter(BookMeta.get().title.greaterThan("title002"))
                .asEntityList();

        int list1Size = list1.size();
        int list2Size = list2.size();
        int list3Size = list3.size();

結果↓

非同期になった!\(^o^)/


という訳で、PreparedQuery.asListはドキュメント通り非同期になっていた。
ただSlim3でクエリを使用する場合はgetEntityListを使ってからモデルに変換*1するか、またはasIterator、asIterableを使う・・ということになるのかな。。


補足:prefetchSizeを大きくしておかないと結果Listのアクセス時にフェッチが行われて非同期クエリの利点が少なくなるので注意

*1:メタクラスのentityToModelメソッドを使うのかな