Google Firestore: come ottenere documenti da più ID in un unico viaggio di andata e ritorno?


100

Mi chiedo se sia possibile ottenere più documenti per elenco di ID in un viaggio di andata e ritorno (chiamata di rete) al Firestore.


4
Sembra che tu presuma che i viaggi di andata e ritorno stiano causando problemi di prestazioni nella tua app. Non lo presumo. Firebase ha una storia di prestazioni ottimali in questi casi, poiché convoglia le richieste . Anche se non ho verificato come si comporta Firestore in questo scenario, mi piacerebbe vedere la prova di un problema di prestazioni prima di presumere che esista.
Frank van Puffelen

1
Diciamo che ho bisogno di documenti a, b, cdi fare qualcosa. Chiedo per tutti e tre in parallelo in richieste separate. aimpiega 100 ms, b150 ms e c3000 ms. Di conseguenza, devo attendere 3000 ms per eseguire l'operazione. Sarà da maxloro. Sarà più rischioso quando il numero di documenti da recuperare è elevato. Dipende dallo stato della rete, penso che questo possa diventare un problema.
Joon

1
SELECT * FROM docs WHERE id IN (a,b,c)Tuttavia, inviarli tutti insieme non richiederebbe la stessa quantità di tempo? Non vedo la differenza, poiché la connessione viene stabilita una volta e il resto è pipeline su quella. Il tempo (dopo la creazione iniziale della connessione) è il tempo di caricamento di tutti i documenti + 1 round trip, uguale per entrambi gli approcci. Se si comporta in modo diverso per te, puoi condividere un campione (come nella mia domanda collegata)?
Frank van Puffelen

Penso di averti perso. Quando dici che è pipeline, intendi che Firestore raggruppa e invia automaticamente le query al loro server in un round trip al database?
Joon

Cordiali saluti, quello che intendo per andata e ritorno è una chiamata di rete al database dal client. Sto chiedendo se più query vengono raggruppate automaticamente come un round trip da Firestore o più query vengono eseguite come più round trip in parallelo.
Joon

Risposte:


90

se sei all'interno di 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])}`);
* });
*/

Questo è specifico per l'SDK del server

AGGIORNAMENTO: "Cloud Firestore [sdk lato client] ora supporta le query IN!"

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

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


29
Per chiunque desideri chiamare questo metodo con un array di riferimenti a documenti generato dinamicamente, puoi farlo in questo modo: firestore.getAll (... arrayOfReferences) .then ()
Horea

1
Mi dispiace @KamanaKisinga ... Non ho fatto nulla di firebase da quasi un anno e non posso davvero aiutare in questo momento (hey guarda, in realtà ho pubblicato questa risposta un anno fa oggi!)
Nick Franceschina,

2
Gli SDK lato client ora offrono anche questa funzionalità. vedere la risposta di jeodonara per un esempio: stackoverflow.com/a/58780369
Frank van Puffelen

4
attenzione: il filtro in ingresso è attualmente limitato a 10 elementi. Quindi probabilmente scoprirai che è inutile quando stai per entrare in produzione.
Martin Cremer,

7
in realtà devi usare firebase.firestore.FieldPath.documentId()e non'id'
Maddocks

20

Hanno appena annunciato questa funzionalità, https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html .

Ora puoi usare query come, ma tieni presente che la dimensione dell'input non può essere maggiore di 10.

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


C'è una query whereIn piuttosto che where. E non so come progettare query per più documenti da un elenco di ID di documenti che appartengono a una raccolta specifica. Per favore aiuto.
errore di compilazione termina il

17
@Compileerrorend potresti provare questo? db.collection('users').where(firebase.firestore.FieldPath.documentId(), 'in',["123","345","111"]).get()
jeadonara

grazie, soprattutto per ilfirebase.firestore.FieldPath.documentId()
Cherniv

10

No, al momento non è possibile inviare in batch più richieste di lettura utilizzando Cloud Firestore SDK e quindi non è possibile garantire che sia possibile leggere tutti i dati contemporaneamente.

Tuttavia, come ha detto Frank van Puffelen nei commenti sopra, ciò non significa che il recupero di 3 documenti sarà 3 volte più lento del recupero di un documento. È meglio eseguire le proprie misurazioni prima di giungere a una conclusione qui.


1
Il fatto è che voglio conoscere i limiti teorici alle prestazioni di Firestore prima di migrare a Firestore. Non voglio migrare e poi mi rendo conto che non è abbastanza buono per il mio caso d'uso.
Joon

2
Ciao, c'è anche una considerazione delle cose qui. Diciamo che ho memorizzato l'elenco di tutti gli ID dei miei amici e il numero è 500. Posso ottenere l'elenco in 1 costo di lettura ma per visualizzare il loro nome e photoURL, mi costerà 500 letture.
Tapas Mukherjee

1
Se stai cercando di leggere 500 documenti, sono necessarie 500 letture. Se combini le informazioni di cui hai bisogno da tutti i 500 documenti in un unico documento aggiuntivo, è necessaria solo una lettura. Questo è chiamato sorta di duplicazione dei dati è abbastanza normale nella maggior parte dei database NoSQL, incluso Cloud Firestore.
Frank van Puffelen

1
@FrankvanPuffelen Ad esempio, in mongoDb, puoi usare ObjectId come questo stackoverflow.com/a/32264630/648851 .
Sitian Liu

2
Come ha detto @FrankvanPuffelen, la duplicazione dei dati è piuttosto comune nel database NoSQL. Qui devi chiederti quanto spesso questi dati devono essere letti e quanto devono essere aggiornati. Se memorizzi 500 informazioni sugli utenti, diciamo il loro nome + foto + ID, puoi ottenerle in una sola lettura. Ma se hai bisogno di aggiornarli, probabilmente dovrai utilizzare una funzione cloud per aggiornare questi riferimenti ogni volta che un utente aggiorna il proprio nome / foto, eseguendo quindi una funzione cloud + facendo alcune operazioni di scrittura. Non esiste un'implementazione "giusta" / "migliore", dipende solo dal tuo caso d'uso.
schankam

10

In pratica useresti firestore.getAll in questo modo

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

o con sintassi promettente

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
questa dovrebbe essere davvero la risposta selezionata perché ti consente di utilizzare più di 10 ID
sshah98

10

Potresti usare una funzione come questa:

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

Può essere chiamato con un unico ID:

getById('collection', 'some_id')

o un array di ID:

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

5

Sicuramente il modo migliore per farlo è implementare la query effettiva di Firestore in una funzione Cloud? Ci sarebbe quindi solo una singola chiamata di andata e ritorno dal client a Firebase, che sembra essere quello che stai chiedendo.

Vuoi davvero mantenere comunque tutta la tua logica di accesso ai dati come questo lato server.

Internamente ci sarà probabilmente lo stesso numero di chiamate alla stessa Firebase, ma sarebbero tutte attraverso le interconnessioni super veloci di Google, piuttosto che la rete esterna, e combinate con il pipelining che ha spiegato Frank van Puffelen, dovresti ottenere prestazioni eccellenti da questo approccio.


3
Memorizzare l'implementazione in una funzione Cloud è la decisione giusta in alcuni casi in cui si ha una logica complessa, ma probabilmente non nel caso in cui si desideri semplicemente unire un elenco con più ID. Quello che si perde è la memorizzazione nella cache lato client e la formattazione di ritorno standardizzata dalle chiamate regolari. Ciò ha causato più problemi di prestazioni di quanti ne abbia risolti in alcuni casi nelle mie app quando ho utilizzato l'approccio.
Geremia

3

Se stai usando flutter, puoi fare quanto segue:

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

Questo restituirà un futuro contenente List<DocumentSnapshot>che puoi iterare come ti senti in forma.


2

Ecco come faresti qualcosa di simile in Kotlin con Android SDK.
Potrebbe non essere necessariamente in un round trip, ma raggruppa efficacemente il risultato ed evita molti callback nidificati.

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
}

Nota che il recupero di documenti specifici è molto meglio che il recupero di tutti i documenti e il filtraggio del risultato. Questo perché Firestore ti addebita il set di risultati della query.


1
Funziona bene, esattamente quello che stavo cercando!
Georgi

0

Questo non sembra essere possibile in Firestore al momento. Non capisco perché la risposta di Alexander venga accettata, la soluzione che propone restituisce solo tutti i documenti nella raccolta "utenti".

A seconda di ciò che devi fare, dovresti cercare di duplicare i dati rilevanti che devi visualizzare e richiedere un documento completo solo quando necessario.


0

Il meglio che puoi fare è non usarlo Promise.allcome client, quindi deve attendere .allle letture prima di procedere.

Ripeti le letture e lascia che si risolvano in modo indipendente. Sul lato client, questo probabilmente si riduce all'interfaccia utente con diverse immagini del caricatore di avanzamento che si risolvono in valori in modo indipendente. Tuttavia, è meglio che congelare l'intero client fino a quando .allle letture non si risolvono.

Pertanto, scarica immediatamente tutti i risultati sincroni nella vista, quindi lascia che i risultati asincroni arrivino mentre si risolvono, individualmente. Questa può sembrare una distinzione insignificante, ma se il tuo client ha una scarsa connettività Internet (come quella che ho attualmente in questo bar), il congelamento dell'intera esperienza del cliente per diversi secondi risulterà probabilmente in un'esperienza `` questa app fa schifo ''.


2
È asincrono, ci sono molti casi d'uso per l'utilizzo Promise.all... non deve necessariamente "congelare" nulla - potrebbe essere necessario attendere tutti i dati prima di poter fare qualcosa di significativo
Ryan Taylor

Ci sono diversi casi d'uso in cui hai bisogno di caricare tutti i tuoi dati, quindi l'attesa (come uno spinner con un messaggio appropriato, non c'è bisogno di "congelare" alcuna interfaccia utente come dici tu) può essere totalmente necessaria per Promise.all .. Dipende solo dal tipo di prodotti che stai realizzando qui. Questo tipo di commenti sono a mio parere molto irrilevanti e non dovrebbero esserci parole "migliori". Dipende davvero dai diversi casi d'uso che si possono affrontare e da ciò che la tua app sta facendo per l'utente.
schankam

0

Spero che questo ti aiuti, funziona per me.

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);
          });
        });
    });
  }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.