Google Firestore: ¿cómo obtener documentos mediante múltiples identificadores en un viaje de ida y vuelta?

101

Me pregunto si es posible obtener varios documentos por lista de identificadores en un viaje de ida y vuelta (llamada de red) a Firestore.

Joon
fuente
4
Parece que asume que los viajes de ida y vuelta están causando problemas de rendimiento en su aplicación. No lo asumiría. Firebase tiene un historial de buen desempeño en tales casos, ya que canaliza las solicitudes . Si bien no he comprobado cómo se comporta Firestore en este escenario, me encantaría ver una prueba de un problema de rendimiento antes de asumir que existe.
Frank van Puffelen
1
Digamos que necesito documentos a, b, chacer algo. Solicito los tres en paralelo en solicitudes separadas. atarda 100 ms, b150 ms y c3000 ms. Como resultado, necesito esperar 3000ms para realizar la tarea. Va a ser maxde ellos. Va a ser más arriesgado cuando la cantidad de documentos a buscar es grande. Depende del estado de la red, creo que esto puede convertirse en un problema.
Joon
1
Sin SELECT * FROM docs WHERE id IN (a,b,c)embargo, ¿no tomaría la misma cantidad de tiempo enviarlos todos como uno solo ? No veo la diferencia, ya que la conexión se establece una vez y el resto se canaliza sobre eso. El tiempo (después del establecimiento inicial de la conexión) es el tiempo de carga de todos los documentos + 1 viaje de ida y vuelta, el mismo para ambos enfoques. Si se comporta diferente para usted, ¿puede compartir una muestra (como en mi pregunta vinculada)?
Frank van Puffelen
Creo que te perdí. Cuando dice que está canalizado, ¿quiere decir que Firestore agrupa y envía consultas automáticamente a su servidor en un viaje de ida y vuelta a la base de datos?
Joon
Para su información, lo que quiero decir con un viaje de ida y vuelta es una llamada de red a la base de datos desde el cliente. Estoy preguntando si Firestore agrupa automáticamente varias consultas como un viaje de ida y vuelta, o si se realizan varias consultas como varios viajes de ida y vuelta en paralelo.
Joon

Respuestas:

91

si estás dentro del nodo:

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])}`);
* });
*/

Esto es específicamente para el SDK del servidor.

ACTUALIZACIÓN: "¡Cloud Firestore [sdk del lado del cliente] ahora admite consultas IN!"

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

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

Nick Franceschina
fuente
29
Para cualquiera que busque llamar a este método con una matriz generada dinámicamente de referencias de documentos, puede hacerlo así: firestore.getAll (... arrayOfReferences) .then ()
Horea
1
Lo siento @KamanaKisinga ... No he hecho nada de firebase en casi un año y realmente no puedo ayudar en este momento (oye, ¡de hecho publiqué esta respuesta hace un año hoy!)
Nick Franceschina
2
Los SDK del lado del cliente ahora también ofrecen esta funcionalidad. vea la respuesta de jeodonara para ver un ejemplo: stackoverflow.com/a/58780369
Frank van Puffelen
4
advertencia: el filtro de entrada está limitado a 10 elementos actualmente. Así que probablemente descubrirás que es inútil cuando estés a punto de llegar a la producción.
Martin Cremer
7
en realidad necesitas usar firebase.firestore.FieldPath.documentId()y no'id'
Maddocks
20

Acaban de anunciar esta funcionalidad, https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html .

Ahora puede usar consultas como, pero tenga en cuenta que el tamaño de entrada no puede ser mayor que 10.

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

jeadonara
fuente
Hay una consulta whereIn en lugar de where. Y no sé cómo diseñar consultas para varios documentos de una lista de identificadores de documentos que pertenecen a una colección específica. Por favor ayuda.
Error de compilación final del
17
@Compileerrorend ¿podrías probar esto? db.collection('users').where(firebase.firestore.FieldPath.documentId(), 'in',["123","345","111"]).get()
jeadonara
gracias, especialmente por elfirebase.firestore.FieldPath.documentId()
Cherniv
10

No, en este momento no hay forma de agrupar varias solicitudes de lectura con el SDK de Cloud Firestore y, por lo tanto, no hay forma de garantizar que pueda leer todos los datos a la vez.

Sin embargo, como ha dicho Frank van Puffelen en los comentarios anteriores, esto no significa que obtener 3 documentos será 3 veces más lento que obtener un documento. Es mejor realizar sus propias mediciones antes de llegar a una conclusión aquí.

Sam Stern
fuente
1
El caso es que quiero conocer los límites teóricos del rendimiento de Firestore antes de migrar a Firestore. No quiero migrar y luego darme cuenta de que no es lo suficientemente bueno para mi caso de uso.
Joon
2
Hola, también hay una consideración de cose aquí. Digamos que tengo una lista almacenada de todas las identificaciones de mis amigos y el número es 500. Puedo obtener la lista en el costo de 1 lectura, pero para mostrar su Nombre y photoURL, me costará 500 lecturas.
Tapas Mukherjee
1
Si está intentando leer 500 documentos, se necesitan 500 lecturas. Si combina la información que necesita de los 500 documentos en un solo documento adicional, solo se necesita una lectura. Ese tipo de duplicación de datos es bastante normal en la mayoría de las bases de datos NoSQL, incluida Cloud Firestore.
Frank van Puffelen
1
@FrankvanPuffelen Por ejemplo, en mongoDb, puede usar ObjectId como este stackoverflow.com/a/32264630/648851 .
Sitian Liu
2
Como dijo @FrankvanPuffelen, la duplicación de datos es bastante común en la base de datos NoSQL. Aquí debe preguntarse con qué frecuencia deben leerse estos datos y qué tan actualizados deben estar. Si almacena información de 500 de los usuarios, digamos su nombre + foto + identificación, puede obtenerlos en una lectura. Pero si los necesita actualizados, probablemente tendrá que usar una función en la nube para actualizar estas referencias cada vez que un usuario actualice su nombre / foto, por lo tanto, ejecutar una función en la nube + realizar algunas operaciones de escritura. No existe una implementación "correcta" / "mejor", solo depende de su caso de uso.
schankam
10

En la práctica, usaría firestore.getAll así

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 sintaxis de promesa

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())))
}
Sebastián
fuente
3
esta debería ser realmente la respuesta seleccionada porque le permite usar más de 10 identificadores
sshah98
10

Podrías usar una función como esta:

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

Se puede llamar con un solo ID:

getById('collection', 'some_id')

o una serie de ID:

getById('collection', ['some_id', 'some_other_id'])
JP de la Torre
fuente
5

Sin duda, la mejor manera de hacerlo es implementando la consulta real de Firestore en una función de nube. Entonces solo habría una única llamada de ida y vuelta desde el cliente a Firebase, que parece ser lo que está pidiendo.

Realmente desea mantener toda su lógica de acceso a datos como este lado del servidor de todos modos.

Internamente, probablemente habrá la misma cantidad de llamadas a Firebase, pero todas serían a través de las interconexiones súper rápidas de Google, en lugar de la red externa, y combinadas con la canalización que Frank van Puffelen ha explicado, debería obtener un excelente rendimiento de Este enfoque.

Chris Wilson
fuente
3
Almacenar la implementación en una función de nube es la decisión correcta en algunos casos en los que tiene una lógica compleja, pero probablemente no en el caso en el que solo desea fusionar una lista con múltiples ID. Lo que pierde es el almacenamiento en caché del lado del cliente y el formato de devolución estandarizado de las llamadas regulares. Esto causó más problemas de rendimiento de los que resolvió en algunos casos en mis aplicaciones cuando usé el enfoque.
Jeremías
3

Si está usando flutter, puede hacer lo siguiente:

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

Esto devolverá un futuro List<DocumentSnapshot>que contiene que puede iterar como mejor le parezca.

Monis
fuente
2

Así es como haría algo como esto en Kotlin con el SDK de Android.
Puede que no sea necesariamente en un viaje de ida y vuelta, pero agrupa eficazmente el resultado y evita muchas devoluciones de llamada anidadas.

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
}

Tenga en cuenta que buscar documentos específicos es mucho mejor que buscar todos los documentos y filtrar el resultado. Esto se debe a que Firestore le cobra por el conjunto de resultados de la consulta.

Markymark
fuente
1
Funciona muy bien, ¡exactamente lo que estaba buscando!
Georgi
0

Esto no parece ser posible en Firestore en este momento. No entiendo por qué se acepta la respuesta de Alexander, la solución que propone simplemente devuelve todos los documentos de la colección "usuarios".

Dependiendo de lo que deba hacer, debe buscar duplicar los datos relevantes que necesita mostrar y solo solicitar un documento completo cuando sea necesario.

Horea
fuente
0

Lo mejor que puede hacer es no usarlo Promise.allcomo su cliente, luego debe esperar.all las lecturas antes de continuar.

Repite las lecturas y deja que se resuelvan de forma independiente. En el lado del cliente, esto probablemente se reduce a que la interfaz de usuario tiene varias imágenes del cargador de progreso que se resuelven en valores de forma independiente. Sin embargo, esto es mejor que congelar a todo el cliente hasta.all resuelvan las lecturas.

Por lo tanto, descargue todos los resultados síncronos en la vista inmediatamente, luego deje que los resultados asincrónicos entren a medida que se resuelven, individualmente. Esto puede parecer una distinción insignificante, pero si su cliente tiene una mala conectividad a Internet (como la que tengo actualmente en esta cafetería), congelar toda la experiencia del cliente durante varios segundos probablemente resultará en una experiencia de 'esta aplicación apesta'.

Ronnie Royston
fuente
2
Es asincrónico, hay muchos casos de uso para usar Promise.all... no necesariamente tiene que "congelar" nada; es posible que deba esperar todos los datos antes de poder hacer algo significativo
Ryan Taylor
Hay varios casos de uso en los que necesita cargar todos sus datos, por lo tanto, la espera (como una ruleta con un mensaje apropiado, sin necesidad de "congelar" ninguna interfaz de usuario como usted dice) puede ser totalmente necesaria para Promise.all .. Realmente depende del tipo de productos que esté creando aquí. Este tipo de comentarios son, en mi opinión, muy irrelevantes y no debería haber ninguna palabra "mejor" en ellos. Realmente depende de cada caso de uso diferente al que se pueda enfrentar y de lo que su aplicación esté haciendo por el usuario.
schankam
0

Espero que esto te ayude, me funciona.

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
fuente