1. ホーム
  2. swift

ファイアストア コレクション内のランダムなドキュメントを取得する方法

2023-11-08 04:39:05

質問

私のアプリケーションでは、firebaseのコレクションからランダムに複数のドキュメントを選択できることが重要です。

私が知っている限り)Firebaseに組み込まれた、まさにこれを行うクエリを実現するネイティブ関数がないため、私の最初の考えは、コレクション内のドキュメント数がある場合、ランダムに開始と終了インデックスを選択するためにクエリカーソルを使用することでした。

しかし、もし私がその親コレクション内のインデックスによってドキュメントを選択することができれば、ランダムドキュメントクエリーを実現することができます。

ここで、私ができるようにしたいことは、次のファイアストア・スキーマを考えてみることです。

root/
  posts/
     docA
     docB
     docC
     docD

そして、クライアント(私はSwift環境です)では、このようなことができるクエリを書きたいと思います。

db.collection("posts")[0, 1, 3] // would return: docA, docB, docD

これと同じようなことをすることはできますか?あるいは、同じような方法でランダムなドキュメントを選択できる別の方法はありますか?

助けてください。

どのように解決するのですか?

ランダムに生成されるインデックスと簡単なクエリを使用すると、Cloud Firestoreのコレクションまたはコレクショングループからランダムにドキュメントを選択することができます。

この回答は4つのセクションに分かれており、各セクションに異なるオプションがあります。

  1. ランダム インデックスを生成する方法
  2. ランダムインデックスのクエリ方法
  3. 複数のランダム・ドキュメントの選択
  4. 継続的なランダム性のためのリシード

ランダムインデックスの生成方法

この回答の基本は、昇順または降順に並べたときに、すべてのドキュメントがランダムに並べられる結果となるインデックス付きフィールドを作成することです。これを作成するにはさまざまな方法がありますので、最も簡単に利用できるものから順に2つ見ていきましょう。

自動IDバージョン

クライアントライブラリで提供されているランダムな自動IDを使用している場合、この同じシステムを使って文書をランダムに選択することができます。この場合、ランダムに並べられたインデックス がドキュメントIDになります。

後のクエリセクションで、生成したランダムな値は、新しい自動ID ( iOS , アンドロイド , ウェブ ) で、クエリするフィールドは __name__ フィールドであり、後で述べる「低い値」は空文字列です。これはランダムインデックスを生成する最も簡単な方法であり、言語やプラットフォームに関係なく動作します。

デフォルトでは、ドキュメント名 ( __name__ ) は昇順にしかインデックスされませんし、既存のドキュメントを削除して再作成する以外に名前を変更することはできません。これらのいずれかが必要な場合でも、このメソッドを使用し、自動 ID を random という実際のフィールドとして格納することができます。

ランダム整数版

ドキュメントを書くときに、まず、境界のある範囲のランダムな整数を生成し、それを random . 予想されるドキュメントの数に応じて、スペースを節約したり、衝突のリスク(このテクニックの有効性を低下させる)を減らすために、別の境界のある範囲を使用することができます。

異なる考慮事項があるため、どの言語が必要かを検討する必要があります。Swiftは簡単ですが、JavaScriptは特筆すべきはゴトがあることです。

  • 32ビット整数。小さな (~10K が衝突しにくい ) データセット
  • 64ビット整数。大きなデータセット(注:JavaScriptはネイティブにサポートしていません。 まだ )

これで、ドキュメントがランダムにソートされたインデックスが作成されます。後のクエリセクションで、生成するランダムな値はこれらの値のうちの別の1つになり、後で述べる「低い値」は-1になります。

ランダムインデックスのクエリ方法

ランダムインデックスができたので、それをクエリしたいことでしょう。以下では、1つのランダムなドキュメントを選択するためのいくつかの簡単なバリエーションと、1つ以上のドキュメントを選択するためのオプションについて見ていきます。

これらのすべてのオプションについて、ドキュメントを書くときに作成したインデックス値と同じ形式の新しいランダムな値を生成したいと思うでしょう、変数 random という変数で示されます。この値を使用して、インデックス上のランダムな場所を見つけることにします。

ラップアラウンド

ランダムな値が得られたので、1つのドキュメントに対してクエリを実行することができます。

let postsRef = db.collection("posts")
queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: random)
                   .order(by: "random")
                   .limit(to: 1)

これで文書が返されたことを確認します。もしそうでなければ、もう一度クエリを実行し、ランダムインデックスに「低い値」を使用します。例えば、ランダム整数を使用した場合、次のようになります。 lowValue0 :

let postsRef = db.collection("posts")
queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: lowValue)
                   .order(by: "random")
                   .limit(to: 1)

ドキュメントが1つである限り、最低でも1つのドキュメントを返すことが保証されます。

双方向性

回り込み方式は実装が簡単で、昇順インデックスだけを有効にしてストレージを最適化することができます。欠点としては、値が不当に遮蔽される可能性があることです。たとえば、10K のうち最初の 3 つのドキュメント (A,B,C) が A:409496、B:436496、C:818992 というランダムなインデックス値を持つ場合、A と C は 1/10K 以下の確率で選択されますが、B は A の近接によって効果的に保護されて、およそ 1/160K の確率でしかないことに注意してください。

単一方向にクエリを実行し、値が見つからない場合は折り返すのではなく、代わりにランダムに >=<= であり、これは不当に遮蔽された値の確率を半分に減らしますが、その代償としてインデックスのストレージは2倍となります。

一方の方向が結果を返さない場合、もう一方の方向に切り替えます。

queryRef = postsRef.whereField("random", isLessThanOrEqualTo: random)
                   .order(by: "random", descending: true)
                   .limit(to: 1)

queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: random)
                   .order(by: "random")
                   .limit(to: 1)

ランダムな複数文書の選択

一度に1つ以上の文書を選択したい場合がよくあります。どのようなトレードオフを望むかによって、上記のテクニックを調整する2つの異なる方法があります。

リンス&アンプを繰り返す

この方法は簡単です。毎回新しいランダムな整数を選択することを含め、このプロセスを繰り返すだけです。

この方法は、同じパターンを繰り返し見ることを心配することなく、ドキュメントのランダムなシーケンスを得ることができます。

トレードオフは、各文書についてサービスとの個別のラウンドトリップが必要なため、次の方法よりも遅くなることです。

続けて

この方法では、単純に制限の数字を目的の文書まで増やします。を返すかもしれないので、少し複雑です。 0..limit のドキュメントを返すかもしれないので、少し複雑です。その後、同じ方法で不足するドキュメントを取得する必要がありますが、 その場合は制限を減らして差分だけにします。要求している数よりも多くのドキュメントが合計で存在することがわかっている場合、2回目の呼び出しで十分なドキュメントを取り戻せない (1回目は取り戻せない) エッジケースを無視することで最適化することができます。

このソリューションのトレードオフは、繰り返されるシーケンスにあります。ドキュメントはランダムに並べられますが、範囲を重複してしまう場合は、前に見たのと同じパターンを見ることになります。この懸念は、次の reseed のセクションで説明する軽減する方法があります。

このアプローチは、すべてのドキュメントを要求することになるので、'Rinse & Repeat'よりも高速です。

継続的なランダム性のためのReseeding

この方法は、ドキュメントセットが静的であればランダムにドキュメントを与えますが、各ドキュメントが返される確率も同様に静的となります。これは、いくつかの値が、それらが得た最初のランダムな値に基づいて、不当に低いまたは高い確率を持つかもしれないので、問題である。多くの使用例では、これは問題ありませんが、いくつかの使用例では、1つのドキュメントを返すより均一な機会を持つために、長期的なランダム性を増やしたいと思うかもしれません。

挿入された文書は、削除された文書と同様に、徐々に確率を変えながら、その間に織り込まれることに注意してください。挿入/削除の割合が文書数に対して少なすぎる場合、これに対処するいくつかの戦略があります。

マルチランダム

再シードを心配するよりも、常にドキュメントごとに複数のランダムインデックスを作成し、それらのインデックスの一つを毎回ランダムに選択することができます。例えば、フィールド random は、1から3までのサブフィールドを持つマップである。

{'random': {'1': 32456, '2':3904515723, '3': 766958445}}

これで、random.1、random.2、random.3に対してランダムにクエリを実行することになり、より大きなランダム性の広がりが生まれます。これは本質的に、再シードすることによる計算量(ドキュメントの書き込み)の増加を節約するために、ストレージの増加をトレードするものです。

書き込み時に再シードする

ドキュメントを更新するときはいつでも、ランダムな値である random フィールドのランダムな値を生成し直します。これは、ランダムインデックスでドキュメントを移動します。

読み込み時の再読み込み

生成された乱数値が一様でない場合(ランダムなのでこれは予想されることです)、同じ文書が適切でない時間だけ選ばれるかもしれません。これは、ランダムに選択された文書が読まれた後、新しいランダムな値で更新することで簡単に対策できます。

書き込みはより高価であり、ホットスポットになる可能性があるので、時間のサブセットで読み取り時にのみ更新することを選択できます (例, if random(0,100) === 0) update; ).