はじめに
Firestoreの大型アップデートは、記憶に新しいのがcount
です。
今まで全データを取得しないと数えられなかったFirestoreのドキュメント数が1000件1クエリ換算で実行できるようになったのは、場合によっては今まで自分で苦労していた部分がサクッとできてしまうかなり大きなアップデートだったと思います。
今回はそれを超える変化だと思います。
条件に、or
、and
が増えるのと、countと同じように集計関数としてsum
、average
が来ました!
or
のクエリーが追加された9.18.0だと、in
を使ったクエリーはor
を使わないと10個を超えなかったんですが、現状はor
がなくても大丈夫になったので大幅に内容を更新しました。
実際にor
を使ってみた
試したクエリーがこれです。
onSnapshot(
query(
collection(getFirestore(), 'messages'),
or(
and(where('userId', '==', uid), where('public', '==', false)),
where('public', '==', true)
)
), (snapshot) => {
console.log(snapshot.docs.map(doc => doc.data()))
})
公開したメッセージと公開してない自分のメッセージを表示するようなクエリーです。
そもそもor
は、SQLの世界ではなるべく避けるものであって、クエリーが遅くなる可能性もあり、できれば使いたくない演算子だと思います。
ただFirestoreはインデックスがないクエリーが不可能になっているので、うまくパフォーマンスが維持できているようです。
Javascript SDKが2つのクエリをローカルでうまくマージしてくれているのではないか?という疑いも少しだけあったので、
ブラウザの開発者ツールを使って通信ログも見てみました。
確認した結果、ちゃんとor
としてクエリが送信され、その結果としてちゃんと値が返ってきていました!
しかも今回onSnapshotを使ったのでリアルタイム監視にも普通に使えるということです。
さらにorderByを追加することも、limitを追加することもできました。これにより今まではローカルでしかできなかったマージをFirestore側でやってくれるようなもので、同じlimitをかけて後でマージするような処理的にもコスト的に無駄なことが避けられそうです!
いやー、興奮するアップデートですね。
さらに今回のアップデートに合わせて、inなどの複数の値を使った項目のクエリーの上限が10から30に増えました!
これも大きな更新かと思います!
and
も使ってみた
or
がこれだけ変わるのであれば、and
でこれがいけるのでは!?と思って試したクエリがこちらです。
onSnapshot(
query(
collection(getFirestore(), 'messages'),
and(
where('aaaa', '>', '0'),
where('bbbb', '<', '100')
)
), (snapshot) => {
console.log(snapshot.docs.map(doc => doc.data()))
})
Firestoreのつらみとして語られる別フィールドの不等号は複数使えないというやつです。
これがないために範囲指定に限界があるのですが、これはエラーになってしまいました。
Invalid query. All where filters with an inequality (<, <=, !=, not-in, >, or >=) must be on the same field. But you have inequality filters on 'aaaa' and 'bbbb'
うーん、この結果からして、or
のクエリに対してさらにand
するみたいな用途でしか使えないのかもしれません。
ここは少し残念な結果です・・・。
複合インデックスが必要なケースは変わっているか?
これも残念変わっていませんでした。
そもそもand
もor
もorderBy
を内包させることができません。そのため、orderBy
は外に書く必要があるのですが、and
でもor
でもorderBy
と二つ以上のフィールドの条件がついたときは複合インデックスの作成が必要でした。
以下のクエリーを発行したときは、
query(
collection(getFirestore(), 'messages'),
and(
where('aaaa', '==', 'a'),
where('bbbb', '==', 'b'),
),
orderBy('createdAt', 'desc'),
limit(10)
)
and
を使わないときと変わらない結果ですね。
or
を使った場合はどうでしょうか?
query(
collection(getFirestore(), 'messages'),
or(
where('aaaa', '==', 'a'),
where('bbbb', '==', 'b'),
),
orderBy('createdAt', 'desc'),
limit(10)
)
or
の場合は、この場合であれば、aaaa
とcreatedAt
のインデックス、bbbb
とcreatedAt
のインデックスの2つの複合インデックスが必要でした。
クエリを2つ書いたときと同じ結果ですね。ここも改善したという感じではないですね。
sumとaverageの使い方
v10.5.0からsumとaverateも使えるようになりました。
使い方は以下のようになります。
const aggregateSnapshot = await getAggregateFromServer(query, {
countOfDocs: count(),
totalHours: sum('hours'),
averageScore: average('score')
});
const countOfDocs: number = aggregateSnapshot.data().countOfDocs;
const totalHours: number = aggregateSnapshot.data().totalHours;
const averageScore: number | null = aggregateSnapshot.data().averageScore;
おー。
こんな感じになるんですね。これをみる限り、queryを決めて、それに対していろんな集計関数をフィールド名で求めることができるようになります。
試してみての注意点ですが、対象のqueryにwhere
などの条件がつく場合は、複合インデックスが必要になるようです。
orderBy
などと同じ感じですね。仕組みを考えると確かにないと無理そうです。
終わりに
Firestoreで苦しんでいる人をたまに見かけてはアドバイスをしてみたり、自分でも改善する方法を模索していましたが、このようなアップデートでどんどん苦しみが減ってきそうでうれしいです!
今後もどんどん改善が来るのを期待したいと思います!
最後にちょっと紹介。
Firebaseを始めたばかりで、設計、実装、運用に困ってる人たちのために本を書いたのでぜひ読んでみてください!
それはそれとして、実際の案件でちゃんとテストも書いていきたいという人は、以下の本もおすすめです!