Google Firestore - jak uzyskać dokument za pomocą wielu identyfikatorów w jednej podróży w obie strony?

104

Zastanawiam się, czy możliwe jest pobranie wielu dokumentów według listy identyfikatorów w jednej podróży w obie strony (połączenie sieciowe) do Firestore.

Joon
źródło
4
Wydaje się, że zakładasz, że obie strony powodują problemy z wydajnością w Twojej aplikacji. Nie zakładałbym tego. Firebase od dawna radzi sobie dobrze w takich przypadkach, ponieważ obsługuje żądania . Chociaż nie sprawdziłem, jak zachowuje się Firestore w tym scenariuszu, chciałbym zobaczyć dowód na problem z wydajnością, zanim założę, że istnieje.
Frank van Puffelen
1
Powiedzmy, że muszę dokumentów a, b, caby coś zrobić. Proszę o wszystkie trzy równolegle w osobnych wnioskach. atrwa 100 ms, btrwa 150 ms i ctrwa 3000 ms . W rezultacie muszę poczekać 3000 ms, aby wykonać zadanie. Będzie maxz nich. Bardziej ryzykowne będzie, gdy liczba dokumentów do pobrania jest duża. W zależności od stanu sieci, myślę, że może to stanowić problem.
Joon
1
Czy wysłanie ich wszystkich jako jednego nie SELECT * FROM docs WHERE id IN (a,b,c)zajęłoby jednak tyle samo czasu? Nie widzę różnicy, ponieważ połączenie jest ustanawiane raz, a reszta jest przez to przepuszczana. Czas (po początkowym nawiązaniu połączenia) to czas wczytywania wszystkich dokumentów + 1 podróż w obie strony, taki sam dla obu podejść. Jeśli u Ciebie zachowuje się inaczej, czy możesz udostępnić próbkę (jak w moim powiązanym pytaniu)?
Frank van Puffelen
Myślę, że cię straciłem. Kiedy mówisz, że jest to potokowe, czy masz na myśli to, że Firestore automatycznie grupuje i wysyła zapytania do swojego serwera w jednej rundzie do bazy danych?
Joon
FYI, co mam na myśli, mówiąc o podróży w obie strony, to jedno połączenie sieciowe z bazą danych od klienta. Pytam, czy wiele zapytań jest automatycznie grupowanych jako jedna podróż w obie strony przez Firestore, czy też czy wiele zapytań jest wykonywanych równolegle jako wiele podróży w obie strony.
Joon

Odpowiedzi:

98

jeśli jesteś w 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])}`);
* });
*/

Dotyczy to szczególnie zestawu SDK serwera

AKTUALIZACJA: „Cloud Firestore [sdk po stronie klienta] obsługuje teraz zapytania!”

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

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

Nick Franceschina
źródło
29
Dla każdego, kto chce wywołać tę metodę z dynamicznie generowaną tablicą odwołań do dokumentów, możesz to zrobić w następujący sposób: firestore.getAll (... arrayOfReferences) .then ()
Horea
1
Przepraszam @KamanaKisinga ... Nie robiłem żadnych rzeczy z firebase od prawie roku i naprawdę nie mogę pomóc w tej chwili (hej, tak naprawdę opublikowałem tę odpowiedź rok temu dzisiaj!)
Nick Franceschina
2
Pakiety SDK po stronie klienta oferują teraz również tę funkcję. zobacz przykładową odpowiedź jeodonara: stackoverflow.com/a/58780369
Frank van Puffelen
6
ostrzeżenie: filtr wejściowy jest obecnie ograniczony do 10 pozycji. Więc prawdopodobnie okaże się, że jest to bezużyteczne, gdy masz zamiar rozpocząć produkcję.
Martin Cremer
9
właściwie musisz używać, firebase.firestore.FieldPath.documentId()a nie'id'
Maddocks
22

Właśnie ogłosili tę funkcję, https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html .

Teraz możesz używać zapytań takich jak, ale pamiętaj, że rozmiar wejściowy nie może być większy niż 10.

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

jeadonara
źródło
Istnieje zapytanie whereIn, a nie gdzie. I nie wiem, jak zaprojektować zapytanie dla wielu dokumentów z listy identyfikatorów dokumentów, które należą do określonej kolekcji. Proszę pomóż.
Błąd kompilacji kończy się
17
@Compileerrorend, czy możesz tego spróbować? db.collection('users').where(firebase.firestore.FieldPath.documentId(), 'in',["123","345","111"]).get()
jeadonara
dziękuję, szczególnie zafirebase.firestore.FieldPath.documentId()
Iwana Czernyka
10

Nie, w tej chwili nie ma możliwości grupowania wielu żądań odczytu za pomocą pakietu SDK Cloud Firestore, a zatem nie ma możliwości zagwarantowania, że ​​można odczytać wszystkie dane naraz.

Jednak, jak powiedział Frank van Puffelen w komentarzach powyżej, nie oznacza to, że pobranie 3 dokumentów będzie 3x wolniejsze niż pobranie jednego dokumentu. Najlepiej jest wykonać własne pomiary przed wyciągnięciem wniosków.

Sam Stern
źródło
1
Chodzi o to, że chcę poznać teoretyczne ograniczenia wydajności Firestore przed migracją do Firestore. Nie chcę migrować, a potem zdaję sobie sprawę, że nie jest to wystarczająco dobre dla mojego przypadku użycia.
Joon
2
Cześć, jest tu również rozważenie kwestii. Powiedzmy, że zapisałem listę wszystkich identyfikatorów moich znajomych, a liczba to 500. Mogę uzyskać listę za 1 koszt odczytu, ale aby wyświetlić ich imię i adres URL, będzie to kosztować 500 odczytów.
Tapas Mukherjee
1
Jeśli próbujesz odczytać 500 dokumentów, potrzeba 500 odczytów. Jeśli połączysz potrzebne informacje ze wszystkich 500 dokumentów w jeden dodatkowy dokument, wystarczy jedno przeczytanie. Nazywa się to duplikacją danych, która jest całkiem normalna w większości baz danych NoSQL, w tym w Cloud Firestore.
Frank van Puffelen
1
@FrankvanPuffelen Na przykład w mongoDb możesz użyć ObjectId, takiego jak stackoverflow.com/a/32264630/648851 .
Sitian Liu
2
Jak powiedział @FrankvanPuffelen, duplikacja danych jest dość powszechna w bazie danych NoSQL. Tutaj musisz zadać sobie pytanie, jak często te dane są wymagane do odczytu i jak muszą być aktualne. Jeśli przechowujesz informacje o 500 użytkownikach, powiedzmy ich imię i nazwisko + zdjęcie + identyfikator, możesz je pobrać za jednym razem. Ale jeśli potrzebujesz ich aktualnych danych, prawdopodobnie będziesz musiał użyć funkcji chmury, aby zaktualizować te odniesienia za każdym razem, gdy użytkownik zaktualizuje swoje imię i nazwisko / zdjęcie, a tym samym uruchomi funkcję chmury + wykonując pewne operacje zapisu. Nie ma "właściwej" / "lepszej" implementacji, zależy to tylko od twojego przypadku użycia.
schankam
10

W praktyce użyłbyś firestore.getAll w ten sposób

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

lub ze składnią obietnicy

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())))
}
Sebastian
źródło
3
to naprawdę powinna być wybrana odpowiedź, ponieważ pozwala użyć więcej niż 10 identyfikatorów
sshah98
10

Możesz użyć takiej funkcji:

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

Można go wywołać za pomocą jednego identyfikatora:

getById('collection', 'some_id')

lub tablica identyfikatorów:

getById('collection', ['some_id', 'some_other_id'])
JP de la Torre
źródło
5

Z pewnością najlepszym sposobem na to jest zaimplementowanie rzeczywistego zapytania Firestore w funkcji chmury? Byłoby wtedy tylko jedno połączenie w obie strony od klienta do Firebase, o które wydaje się, że właśnie o to prosisz.

Naprawdę chcesz i tak zachować całą logikę dostępu do danych, taką jak ta po stronie serwera.

Wewnętrznie prawdopodobnie będzie taka sama liczba połączeń z samym Firebase, ale wszystkie będą realizowane przez superszybkie połączenia międzysieciowe Google, a nie przez sieć zewnętrzną, aw połączeniu z potokami, które wyjaśnił Frank van Puffelen, powinieneś uzyskać doskonałą wydajność od to podejście.

Chris Wilson
źródło
3
Przechowywanie implementacji w funkcji chmury jest właściwą decyzją w niektórych przypadkach, gdy masz złożoną logikę, ale prawdopodobnie nie w przypadku, gdy chcesz po prostu scalić listę z wieloma identyfikatorami. To, co tracisz, to buforowanie po stronie klienta i standardowe formatowanie zwrotów z regularnych połączeń. Spowodowało to więcej problemów z wydajnością niż rozwiązało w niektórych przypadkach w moich aplikacjach, gdy zastosowałem to podejście.
Jeremiah,
5

Jeśli używasz trzepotania, możesz wykonać następujące czynności:

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

To zwróci Przyszłość, List<DocumentSnapshot>którą możesz iterować według własnego uznania.

Monis
źródło
2

Oto jak zrobiłbyś coś takiego w Kotlin z Android SDK.
Może niekoniecznie odbywać się w jednej podróży w obie strony, ale skutecznie grupuje wynik i pozwala uniknąć wielu zagnieżdżonych wywołań zwrotnych.

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
}

Pamiętaj, że pobieranie określonych dokumentów jest znacznie lepsze niż pobieranie wszystkich dokumentów i filtrowanie wyników. Dzieje się tak, ponieważ Firestore pobiera opłaty za zestaw wyników zapytania.

Markymark
źródło
1
Działa ładnie, dokładnie to, czego szukałem!
Georgi
1

Mam nadzieję, że to ci pomoże, działa na mnie.

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);
          });
        });
    });
  }
Muhammad Mabrouk
źródło
0

W tej chwili nie wydaje się to możliwe w Firestore. Nie rozumiem, dlaczego odpowiedź Aleksandra została przyjęta, rozwiązanie, które proponuje, zwraca po prostu wszystkie dokumenty z kolekcji „użytkownicy”.

W zależności od tego, co musisz zrobić, powinieneś rozważyć powielenie odpowiednich danych, które chcesz wyświetlić, i zażądać pełnego dokumentu tylko wtedy, gdy jest to konieczne.

Horea
źródło
-1

Najlepsze, co możesz zrobić, to nie używać go Promise.alljako klienta, a następnie musi czekać.all odczyt przed kontynuowaniem.

Powtarzaj odczyty i pozwól im rozwiązać się niezależnie. Po stronie klienta prawdopodobnie sprowadza się to do tego, że interfejs użytkownika ma kilka obrazów programu ładującego postęp, które są przetwarzane na wartości niezależnie. Jest to jednak lepsze niż zamrażanie całego klienta do.all czasu rozwiązania odczytów.

Dlatego natychmiast zrzuć wszystkie synchroniczne wyniki do widoku, a następnie pozwól, aby asynchroniczne wyniki pojawiały się indywidualnie, gdy zostaną rozwiązane. Może się to wydawać drobnym rozróżnieniem, ale jeśli twój klient ma słabą łączność z Internetem (tak jak ja mam obecnie w tej kawiarni), zamrożenie całego doświadczenia klienta na kilka sekund prawdopodobnie spowoduje, że `` ta aplikacja jest do bani ''.

Ronnie Royston
źródło
3
Jest asynchroniczny, istnieje wiele przypadków użycia Promise.all… niekoniecznie musi cokolwiek „zamrażać” - być może będziesz musiał poczekać na wszystkie dane, zanim będziesz w stanie zrobić coś znaczącego
Ryan Taylor
Istnieje kilka przypadków użycia, w których trzeba załadować wszystkie dane, dlatego też Promise.all może całkowicie potrzebować czasu oczekiwania (jak spinner z odpowiednią wiadomością, nie ma potrzeby „zamrażania” żadnego interfejsu użytkownika, jak mówisz). . To naprawdę zależy od tego, jakie produkty tutaj tworzysz. Tego rodzaju komentarze są moim zdaniem bardzo nieistotne i nie powinno być w nich żadnych „najlepszych” słów. To naprawdę zależy od różnych przypadków użycia, z którymi można się spotkać, i od tego, co robi Twoja aplikacja dla użytkownika.
schankam