今日ひがさんと「クエリの結果件数の取得方法」についてTwitterで少しやりとりした。
- Low-level APIの
PreparedQuery
だと1000件の上限があるが、JDOのQuery
だと上限が無い - 確かに上限は無いけど、JDOで
Query#execute().size()
はめっちゃ遅くて現実的ではない - JDOでも
Query#setResult("count(this)")
は速い。でも、こいつは実はLow-level APIに丸投げなのでやっぱ1000件の上限に引っかかる。 - JDOでも
Query#setResult("key")
するとLow-level APIでいうsetKeysOnly()
と同じ、キーのみクエリになる。でも1000件の上限に引っかからない!
大体こんなカンジで、最後の件はひがさんが発見してGoogle App Engine for Java GroupのMLにPostもしていた。で、具体的にどれくらいのパフォーマンスなのかが気になったので試してみる。
以下の5つのパターンで試してみた。
- パターン1:普通に
((List
して件数を取得する。)Query#execute()).size()
1000件超の件数も取得できるし、Entityの全フィールドを取得できる。 - パターン2:
Query#setResult("count(this)")
して、(Integer)Query#execute()
で件数を取得する。
これは当然のことながら件数が返るだけ。しかも1000件以上の時は取得できない(1000という件数が返される)。 - パターン3:
Query#setResult("key")
して、((List
で件数を取得する。)Query#execute()).size()
1000件超の件数も取得できるし、各Entityの主キーフィールドだけは取得できる。 - パターン4:Low-level APIを使う。
DatastoreService#preparedQuery()#countEntities()
で件数を取得する。
件数のみ取得できる。 - パターン5:Low-level APIで
Query#setKeysOnly()
してから、DatastoreService#preparedQuery()#countEntities()
で件数を取得する。
件数のみ取得できる。
当然ながら、どのパターンも「filterあり」で実行する。ソート対象は「filter対象のProperty、主キー」を使用する。
1000件超の件数を取得する場合
クエリの結果件数は7473件、全Entityの数は10万件くらい(cronでランダムなEntityを自動生成していたので、全部で何件か?が把握できていないw)。主キーはKey
を使っており、gae.encoded-pkなString
ではない。22回ずつ試して最大最小を取り除いて平均してみた。試行が少ないけど、ぶれも少なかく安定した数値が出ていたのでまぁいいか、と。
- パターン1:
Query#execute()
の実行時間の平均は62msで、取得したListのsize()
メソッドの実行時間の平均が9140ms。 - パターン5:
Query#execute()
の実行時間の平均は19msで、取得したListのsize()
メソッドの実行時間の平均が7218ms。
素のクエリと比較すると、setResult("key")
する方が20%以上速いよぅだ。
1000件以内の件数を取得する場合
クエリの結果件数は720件。極力1000件ギリギリにしたかったんだけど、いいクエリ条件を作れなかった、ゴメンナサイ。クエリ対象のKindは上記の1000件超と同じもの。
- パターン1:
((List)Query#execute()).size()
の実行時間の平均は858.2ms。 - パターン2:
(Integer)Query#execute()
の実行時間の平均は99.2ms。 - パターン3:
DatastoreService#preparedQuery()#countEntities()
の実行時間の平均は97.8ms。 - パターン4:
DatastoreService#preparedQuery()#countEntities()
の実行時間の平均は104.4ms。setKeysOnly()
しない時よりも7msほど遅いけど、たぶん誤差ですな。 - パターン5:
((List)Query#execute()).size()
の実行時間の平均は704.2ms。
やっぱ件数の取得専用の機能を使う方が断トツでいい結果ですよ、と。
ちなみに、この1000件以内の場合に件数を取得したQueryと同じQueryでKeyを100件Fetchする場合(パターン2は無理だけど)、JDOを使ったパターン1とパターン3はJava的な速度で済むから平均1msで走査できる。パターン4とパターン5は、Keyを100件Fetchするのにそれぞれ平均148ms、112msかかる。setKeysOnly()
が効いています。パターン4でKey以外を100件走査するのに147msで、Keyの走査と同じ速度ですね。
まとめ
1000件以内とほぼ断定できているなら素直にJDOでQuery#setResult("count(this)")
するか、Low-level APIでcountEntities()
しよぅ、て事ですね。
1000件超の件数を知りたい場合は、今のところJDOでQuery#setResult("key")
で件数を取得するのが一番マシな方法、…とはいえ7000ms/7000件だし、まぁ実用的ではない。1000件以上数えるような要件はとっとと捨ててLow-level APIを使いましょう、て事かな。実際1000件以上の件数を数える要件にそこまで価値がある事は少ないと思うし。1000件以上をカウントしたい場合でも、別途カウンタを用意して済むならそれがいいし(Shardingとかの手法の考慮が必要ですけどもね。あとTaskQueueもとっとと導入してくれないとツライ)。動的な条件による結果の件数、となるとそこまで価値は無いような気がするしなぁ。どーしても動的な条件に対する結果件数を…ていうなら、最悪Low-level APIでsetKeysOnly()しつつページング(常に条件を変えつつoffset0~limit1000で)、で数えるという手もあるかもしれない。これもまた試してみよう。この方法なら1000件につき約110msなんで、7000件でも770msくらいで数えきれるはずだ。
追記
最悪Low-level APIでsetKeysOnly()しつつ
ページング(常に条件を変えつつoffset0~limit1000で)、で数える
無理です、setKeysOnly()
したらページングできません。…と追記するつもりだったけど、最後の1件だけkey以外もFetchするって事ならsetKeysOnly()
でもいけるか?…とか思ったりもする。
0 件のコメント:
コメントを投稿