Google Firestore-한 번의 왕복으로 여러 ID로 문서를 가져 오는 방법은 무엇입니까?


100

Firestore 로의 한 번의 왕복 (네트워크 호출)으로 ID 목록으로 여러 문서를 가져올 수 있는지 궁금합니다.


4
왕복으로 인해 앱에서 성능 문제가 발생한다고 가정하는 것 같습니다. 나는 그것을 가정하지 않을 것입니다. Firebase는 요청을 파이프 라인하기 때문에 이러한 경우 잘 수행 된 기록이 있습니다. 이 시나리오에서 Firestore가 어떻게 작동하는지 확인하지는 않았지만 성능 문제가 존재한다고 가정하기 전에 성능 문제에 대한 증거를보고 싶습니다.
프랭크 반 Puffelen

1
하자 내가 문서를 필요하다고 a, b, c뭔가를 할 수 있습니다. 나는 세 가지 모두를 별도의 요청으로 병렬로 요청합니다. a100ms, b150ms, c3000ms가 걸립니다. 결과적으로 작업을 수행하려면 3000ms를 기다려야합니다. 그것들이 될 max것입니다. 가져올 문서 수가 많으면 더 위험 할 수 있습니다. 네트워크 상태에 따라 문제가 될 수 있다고 생각합니다.
Joon

1
그래도 모두 한꺼번에 보내는 SELECT * FROM docs WHERE id IN (a,b,c)데 같은 시간이 걸리지 않을까요? 연결이 한 번 설정되고 나머지는 그 위에 파이프 라인이 연결되기 때문에 차이점이 보이지 않습니다. 시간 (초기 연결 설정 후)은 모든 문서의로드 시간 + 왕복 1 회이며 두 접근 방식 모두 동일합니다. 그것이 당신에게 다르게 행동한다면, 샘플을 공유 할 수 있습니까 (링크 된 질문에서와 같이)?
Frank van Puffelen

당신을 잃은 것 같아요. 파이프 라인이라고하면 Firestore가 데이터베이스에 대한 한 번의 왕복으로 서버에 쿼리를 자동으로 그룹화하고 전송한다는 의미입니까?
Joon

참고로, 왕복이란 클라이언트에서 데이터베이스에 대한 한 번의 네트워크 호출을 의미합니다. 여러 쿼리가 Firestore에 의해 하나의 왕복으로 자동 그룹화되는지 또는 여러 쿼리가 병렬로 여러 왕복으로 수행되는지 묻습니다.
Joon

답변:


90

Node 내에있는 경우 :

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"])


29
동적으로 생성 된 문서 참조 배열을 사용하여이 메서드를 호출하려는 경우 다음과 같이 할 수 있습니다. firestore.getAll (... arrayOfReferences) .then ()
Horea

1
죄송합니다 @KamanaKisinga ... 거의 1 년 동안 firebase 작업을하지 않았고 지금은 정말 도움이 될 수 없습니다 (이봐 요, 오늘 1 년 전에이 답변을 게시했습니다!)
Nick Franceschina

2
이제 클라이언트 측 SDK도이 기능을 제공합니다. 예제 jeodonara의 답변을 참조하십시오 stackoverflow.com/a/58780369
프랭크 반 Puffelen에게

4
경고 : in 필터는 현재 10 개 항목으로 제한됩니다. 따라서 프로덕션을 시작하려고 할 때 쓸모가 없다는 것을 알게 될 것입니다.
Martin Cremer

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 목록에서 여러 문서에 대한 쿼리를 디자인하는 방법을 모르겠습니다. 도와주세요.
컴파일 오류 종료

17
@Compileerrorend 이것을 시도해 볼 수 있습니까? db.collection('users').where(firebase.firestore.FieldPath.documentId(), 'in',["123","345","111"]).get()
jeadonara

특히 감사합니다firebase.firestore.FieldPath.documentId()
Cherniv

10

아니요, 현재 Cloud Firestore SDK를 사용하여 여러 읽기 요청을 일괄 처리 할 수있는 방법이 없으므로 모든 데이터를 한 번에 읽을 수 있다고 보장 할 방법이 없습니다.

그러나 Frank van Puffelen이 위의 주석에서 말했듯이 이것은 3 개의 문서를 가져 오는 것이 하나의 문서를 가져 오는 것보다 3 배 느리다는 것을 의미하지는 않습니다. 여기서 결론에 도달하기 전에 직접 측정을 수행하는 것이 가장 좋습니다.


1
문제는 Firestore로 마이그레이션하기 전에 Firestore의 성능에 대한 이론적 한계를 알고 싶다는 것입니다. 마이그레이션하고 싶지 않아 내 사용 사례에 충분하지 않다는 것을 깨닫습니다.
Joon

2
안녕하세요, 여기에 cose의 considaration도 있습니다. 내 친구의 ID 목록을 모두 저장했는데 번호가 500이라고 가정 해 보겠습니다. 1 회 읽기 비용으로 목록을 가져올 수 있지만 이름과 사진 URL을 표시하려면 500 회 읽기가 필요합니다.
Tapas Mukherjee

1
500 개의 문서를 읽으려면 500 회의 읽기가 필요합니다. 500 개 문서에서 필요한 정보를 하나의 추가 문서로 결합하면 한 번만 읽을 수 있습니다. 이를 일종의 데이터 복제라고하는 것은 Cloud Firestore를 포함한 대부분의 NoSQL 데이터베이스에서 매우 일반적입니다.
Frank van Puffelen

1
@FrankvanPuffelen 예를 들어 mongoDb에서는 다음과 같은 ObjectId를 사용할 수 있습니다 . stackoverflow.com/a/32264630/648851 .
Sitian Liu

2
@FrankvanPuffelen이 말했듯이 데이터 복제는 NoSQL 데이터베이스에서 매우 일반적입니다. 여기에서 이러한 데이터를 얼마나 자주 읽어야하는지, 얼마나 최신 상태 여야하는지 스스로에게 물어봐야합니다. 500 개의 사용자 정보를 저장하면 이름 + 사진 + 아이디를 말하면 한 번에 읽을 수 있습니다. 그러나 최신 정보가 필요한 경우 사용자가 이름 / 사진을 업데이트 할 때마다 이러한 참조를 업데이트하기 위해 클라우드 기능을 사용하여 클라우드 기능을 실행하고 일부 쓰기 작업을 수행해야합니다. "올바른"/ "더 나은"구현은 없으며 사용 사례에 따라 다릅니다.
schankam 19

10

실제로는 다음과 같이 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()))
}

또는 약속 구문으로

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())))
}

3
10 개 이상의 ID를 사용할 수 있기 때문에 정말 선택된 답변이어야합니다
sshah98

10

다음과 같은 함수를 사용할 수 있습니다.

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로 단 한 번의 왕복 호출이 발생하며 이는 귀하가 요청하는 것 같습니다.

어쨌든이 서버 측과 같은 모든 데이터 액세스 논리를 유지하고 싶습니다.

내부적으로 Firebase 자체에 대해 동일한 수의 호출이있을 수 있지만 모두 외부 네트워크가 아닌 Google의 초고속 상호 연결을 통해 이루어지며 Frank van Puffelen이 설명한 파이프 라이닝과 결합되어 뛰어난 성능을 얻을 수 있습니다. 이 접근법.


3
Cloud 함수에 구현을 저장하는 것은 복잡한 논리가있는 경우에 올바른 결정이지만 여러 ID가있는 목록을 병합하려는 경우에는 그렇지 않을 수 있습니다. 당신이 잃는 것은 클라이언트 측 캐싱과 정규 호출에서 표준화 된 반환 형식입니다. 이로 인해 접근 방식을 사용할 때 내 앱에서 해결 된 것보다 더 많은 성능 문제가 발생했습니다.
예레미야

3

flutter를 사용하는 경우 다음을 수행 할 수 있습니다.

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

이것은 List<DocumentSnapshot>당신이 적합하다고 느끼는대로 반복 할 수 있는 Future를 반환 할 것입니다.


2

Android SDK를 사용하여 Kotlin에서 이와 같은 작업을 수행하는 방법은 다음과 같습니다.
반드시 한 번 왕복 할 필요는 없지만 결과를 효과적으로 그룹화하고 많은 중첩 콜백을 방지합니다.

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
내가 찾던 그대로 잘 작동합니다!
Georgi

0

현재 Firestore에서는 불가능한 것 같습니다. Alexander의 대답이 왜 받아 들여 지는지 이해가 안갑니다. 그가 제안한 솔루션은 "users"컬렉션의 모든 문서를 반환합니다.

수행해야하는 작업에 따라 표시해야하는 관련 데이터를 복제하고 필요할 때만 전체 문서를 요청해야합니다.


0

당신이 할 수있는 최선 은 당신의 클라이언트로 사용 하지 않고Promise.all 기다려야합니다.all 계속하기 전에 읽기를 것입니다.

읽기를 반복하고 독립적으로 해결하도록합니다. 클라이언트 측에서는 여러 프로그 레스 로더 이미지가 독립적으로 값으로 확인되는 UI로 귀결 될 수 있습니다. 그러나 이것은 전체 클라이언트를 동결하는 것보다 낫습니다..all 읽기가 해결 습니다.

따라서 모든 동기 결과를 뷰에 즉시 덤프 한 다음 개별적으로 해결되는 비동기 결과가 나오도록합니다. 이것은 사소한 차이처럼 보일 수 있지만 클라이언트의 인터넷 연결이 좋지 않은 경우 (현재이 커피 숍에있는 것처럼) 전체 클라이언트 환경을 몇 초 동안 정지하면 '이 앱이 짜증나는'환경이 발생할 수 있습니다.


2
비동기식이며 사용에 대한 많은 사용 사례가 있습니다. Promise.all반드시 "고정"할 필요는 없습니다. 의미있는 작업을 수행하기 전에 모든 데이터를 기다려야 할 수도 있습니다.
Ryan Taylor

모든 데이터를로드해야하는 몇 가지 사용 사례가 있으므로 Promise.all은 대기 (적절한 메시지가있는 스피너처럼 UI를 "고정"할 필요가 없음)가 완전히 필요할 수 있습니다. . 단지 여기에서 어떤 종류의 제품을 만들고 있는지에 따라 다릅니다. 이러한 종류의 댓글은 내 의견과는 무관하며 "최고의"단어가 있어서는 안됩니다. 실제로 직면 할 수있는 모든 사용 사례와 앱이 사용자를 위해 수행하는 작업에 따라 다릅니다.
schankam 19

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.