Google Firestore-1回の往復で複数のIDでドキュメントを取得する方法?


98

Firestoreへの1回の往復(ネットワーク呼び出し)で、IDのリストによって複数のドキュメントを取得できるかどうか疑問に思っています。


4
ラウンドトリップがアプリでパフォーマンスの問題を引き起こしていると想定しているようです。私はそうは思いません。Firebaseはリクエストをパイプライン処理するため、このような場合に正常に機能する履歴があります。このシナリオでFirestoreがどのように動作するかは確認していませんが、パフォーマンスの問題が存在すると想定する前に、パフォーマンスの問題の証拠を確認してください。
フランクファンPuffelen 2017年

1
何かをするためab、ドキュメントが必要だとしましょうc。3つすべてを別々のリクエストで並行してリクエストします。a100ms、b150ms、c3000msかかります。結果として、タスクを実行するために3000ms待つ必要があります。それmaxは彼らのものになるでしょう。フェッチするドキュメントの数が多い場合は、リスクが高くなります。ネットワークの状態にもよりますが、問題になると思います。
2017年

1
SELECT * FROM docs WHERE id IN (a,b,c)ただし、それらすべてを1つのものとして送信すると、同じ時間がかかるのではないでしょうか。接続は一度確立され、残りはそれを介してパイプライン処理されるため、違いはわかりません。時間(最初の接続確立後)は、すべてのドキュメントのロード時間+ 1往復であり、どちらのアプローチでも同じです。動作が異なる場合は、(リンクされた質問のように)サンプルを共有できますか?
フランクファンPuffelen 2017年

私はあなたを失ったと思います。パイプライン化されていると言うと、Firestoreは自動的にクエリをグループ化し、データベースへの1回のラウンドトリップでサーバーにクエリを送信しますか?
2017年

ちなみに、往復とは、クライアントからデータベースへの1つのネットワーク呼び出しです。複数のクエリがFirestoreによって1つのラウンドトリップとして自動的にグループ化されるかどうか、または複数のクエリが複数のラウンドトリップとして並行して実行されるかどうかを尋ねています。
ジュン

回答:


87

ノード内にいる場合:

https://github.com/googleapis/nodejs-firestore/blob/master/dev/src/index.ts#L701

/**
* Retrieves multiple documents from Firestore.
*
* @param {...DocumentReference} documents - The document references
* to receive.
* @returns {Promise<Array.<DocumentSnapshot>>} A Promise that
* contains an array with the resulting document snapshots.
*
* @example
* let documentRef1 = firestore.doc('col/doc1');
* let documentRef2 = firestore.doc('col/doc2');
*
* firestore.getAll(documentRef1, documentRef2).then(docs => {
*   console.log(`First document: ${JSON.stringify(docs[0])}`);
*   console.log(`Second document: ${JSON.stringify(docs[1])}`);
* });
*/

これは特にサーバーSDK用です

更新: 「Cloud Firestore [クライアント側SDK]がINクエリをサポートするようになりました!」

https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html

myCollection.where(firestore.FieldPath.documentId(), 'in', ["123","456","789"])


28
動的に生成されたドキュメント参照の配列を使用してこのメ​​ソッドを呼び出す場合は、次のように実行できます。firestore.getAll(... arrayOfReferences).then()
Horea

1
@KamanaKisingaで申し訳ありません...ほぼ1年たってもFirebaseに関することは何も行っておらず、現時点では本当に役に立ちません(ほら、1年前の今日、この回答を投稿しました!)
Nick Franceschina

2
クライアント側のSDKもこの機能を提供するようになりました。例についてはjeodonaraの回答を参照してください:stackoverflow.com/a/58780369
フランクファンPuffelen

4
警告:現在、フィルターは10アイテムに制限されています。ですから、プロダクションにヒットしようとしているときに、それが役に立たないことに気付くでしょう。
マーティンクレマー

6
実際にあなたが使用する必要はfirebase.firestore.FieldPath.documentId()ありません'id'
Maddocks

20

彼らはこの機能を発表しました、 https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html

これで次のようなクエリを使用できますが、入力サイズは10以下にする必要があることに注意してください。

userCollection.where('uid', 'in', ["1231","222","2131"])


whereではなくwhereInクエリがあります。また、特定のコレクションに属するドキュメントのIDのリストから複数のドキュメントのクエリを設計する方法がわかりません。助けてください。
コンパイルエラーの終了

16
@Compileerrorend試してみませんか?db.collection('users').where(firebase.firestore.FieldPath.documentId(), 'in',["123","345","111"]).get()
jeadonara

特にありがとうfirebase.firestore.FieldPath.documentId()
チェルニフ

10

いいえ、現時点では、Cloud Firestore SDKを使用して複数の読み取りリクエストをバッチ処理する方法がないため、一度にすべてのデータを読み取ることができると保証する方法はありません。

ただし、Frank van Puffelenが上記のコメントで述べたように、これは、3つのドキュメントのフェッチが1つのドキュメントのフェッチの3倍遅くなることを意味するものではありません。ここで結論に達する前に、独自の測定を実行することをお勧めします。


1
問題は、Firestoreに移行する前に、Firestoreのパフォーマンスの理論的な限界を知りたいということです。移行したくないのですが、私のユースケースでは十分ではないことに気づきました。
ジュン

2
こんにちは、ここにもcoseの検討事項があります。友達のIDのリストを保存していて、その数が500だとします。1回の読み取りでリストを取得できますが、名前とphotoURLを表示するには500回の読み取りが必要です。
Tapas Mukherjee

1
500個のドキュメントを読み取ろうとすると、500回の読み取りが必要になります。500のすべてのドキュメントから必要な情報を1つの追加のドキュメントに結合すると、1回の読み取りで済みます。これは一種のデータ複製と呼ばれ、Cloud Firestoreを含むほとんどのNoSQLデータベースではごく普通のことです。
フランクファンPuffelen 2017年

1
@FrankvanPuffelenたとえば、mongoDbでは、このstackoverflow.com/a/32264630/648851のようなObjectIdを使用できます。
Sitian Liu 2017

1
@FrankvanPuffelenが言ったように、データの複製はNoSQLデータベースではかなり一般的です。ここでは、これらのデータを読み取る必要がある頻度と、データを最新にする必要があるかどうかを自問する必要があります。500のユーザー情報を保存している場合、たとえば、名前+写真+ IDを1回の読み取りで取得できます。しかし、それらを最新の状態にする必要がある場合は、ユーザーが名前/写真を更新するたびにクラウド関数を使用してこれらの参照を更新する必要があるため、クラウド関数を実行+書き込み操作を実行します。「正しい」/「より良い」実装はありません。それは単にユースケースに依存します。
シャンカム

9

実際には、次のようにfirestore.getAllを使用します。

async getUsers({userIds}) {
    const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
    const users = await this.firestore.getAll(...refs)
    console.log(users.map(doc => doc.data()))
}

またはpromise構文を使用

getUsers({userIds}) {
    const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
    this.firestore.getAll(...refs).then(users => console.log(users.map(doc => doc.data())))
}

2
それはあなたが10の以上のIDを使用することができますので、これは実際に選択された解答する必要があります
sshah98を

9

次のような関数を使用できます。

function getById (path, ids) {
  return firestore.getAll(
    [].concat(ids).map(id => firestore.doc(`${path}/${id}`))
  )
}

単一のIDで呼び出すことができます。

getById('collection', 'some_id')

またはIDの配列:

getById('collection', ['some_id', 'some_other_id'])

5

確かにこれを行う最善の方法は、Cloud FunctionにFirestoreの実際のクエリを実装することです。その場合、クライアントからFirebaseへのラウンドトリップ呼び出しは1つだけであり、これはあなたが望んでいるようです。

とにかく、このサーバー側のようなすべてのデータアクセスロジックを保持したいのです。

内部的にはFirebase自体への呼び出しと同じ数になる可能性がありますが、それらはすべて外部ネットワークではなく、Googleの超高速相互接続全体にあり、Frank van Puffelenが説明したパイプライン処理と組み合わせると、優れたパフォーマンスが得られますこのアプローチ。


3
実装をCloud Functionに保存することは、複雑なロジックがある場合は正しい決定ですが、複数のIDを持つリストをマージするだけの場合はおそらくそうではありません。失うのは、クライアント側のキャッシュと、通常の呼び出しからの標準化された戻りフォーマットです。これは、私がこのアプローチを使用したときに、一部の場合にアプリで解決するよりも多くのパフォーマンスの問題を引き起こしました。
エレミヤ

3

フラッターを使用している場合は、次のことができます。

Firestore.instance.collection('your collection name').where(FieldPath.documentId, whereIn:[list containing multiple document IDs]).getDocuments();

これは、List<DocumentSnapshot>必要に応じて反復できるFutureを返します。


2

これは、KotlinでAndroid SDKを使用してこのようなことを行う方法です。
必ずしも1回の往復であるとは限りませんが、結果が効果的にグループ化され、ネストされた多数のコールバックが回避されます。

val userIds = listOf("123", "456")
val userTasks = userIds.map { firestore.document("users/${it!!}").get() }

Tasks.whenAllSuccess<DocumentSnapshot>(userTasks).addOnSuccessListener { documentList ->
    //Do what you need to with the document list
}

特定のドキュメントを取得する方が、すべてのドキュメントを取得して結果をフィルタリングするよりもはるかに優れていることに注意してください。これは、Firestoreがクエリ結果セットに対して課金するためです。


1
私が探していたとおりにうまく機能します!
ジョージ

0

現在のところ、これはFirestoreでは可能ではないようです。アレクサンダーの答えが受け入れられる理由がわかりません。彼が提案するソリューションは、「ユーザー」コレクション内のすべてのドキュメントを返すだけです。

必要に応じて、表示する必要のある関連データの複製を検討し、必要な場合にのみ完全なドキュメントを要求する必要があります。


0

あなたができる最善の方法は、クライアントとして使用しないことPromise.allであり、次に.all進む前に読み取りを待つ必要があります。

読み取りを反復し、個別に解決させます。クライアント側では、これはおそらく、いくつかのプログレスローダーイメージが個別に値に解決されるUIに要約されます。ただし、これは、クライアント全体を.all、読み取りが解決さています。

したがって、すべての同期結果をすぐにビューにダンプし、非同期結果が解決されるたびに個別に取得できるようにします。これはささいな違いのように思えるかもしれませんが、クライアントのインターネット接続が悪い場合(私が現在このコーヒーショップにいるように)、クライアントエクスペリエンス全体を数秒間フリーズすると、「このアプリはひどい」エクスペリエンスになる可能性があります。


2
それは非同期であり、使用するための多くのユースケースがありPromise.allます...必ずしも何かを「凍結」する必要はありません–意味のある何かを行うことができるようになる前にすべてのデータを待つ必要があるかもしれません
Ryan Taylor

すべてのデータをロードする必要があるユースケースはいくつかあります。したがって、Promise.allは待機(適切なメッセージを表示するスピナーのように、UIを「フリーズ」する必要がないなど)が完全に必要になる場合があります。 。実際にここで構築する製品の種類によって異なります。これらの種類のコメントは私自身の意見には非常に無関係であり、その中に「最良の」言葉があってはなりません。これは、実際に直面する可能性のあるすべてのユースケースと、アプリがユーザーに対して何をしているかに依存します。
シャンカム

0

これがお役に立てば幸いです。

getCartGoodsData(id) {

    const goodsIDs: string[] = [];

    return new Promise((resolve) => {
      this.fs.firestore.collection(`users/${id}/cart`).get()
        .then(querySnapshot => {
          querySnapshot.forEach(doc => {
            goodsIDs.push(doc.id);
          });

          const getDocs = goodsIDs.map((id: string) => {
            return this.fs.firestore.collection('goods').doc(id).get()
              .then((docData) => {
                return docData.data();
              });
          });

          Promise.all(getDocs).then((goods: Goods[]) => {
            resolve(goods);
          });
        });
    });
  }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.