Google Firestore - bagaimana cara mendapatkan dokumen dengan banyak id dalam satu perjalanan?

105

Saya ingin tahu apakah mungkin untuk mendapatkan banyak dokumen dengan daftar id dalam satu perjalanan (panggilan jaringan) ke Firestore.

Joon
sumber
4
Anda tampaknya berasumsi bahwa perjalanan bolak-balik menyebabkan masalah kinerja dalam aplikasi Anda. Saya tidak akan berasumsi seperti itu. Firebase memiliki riwayat berperforma baik dalam kasus seperti itu, karena menyalurkan permintaan . Meskipun saya belum memeriksa bagaimana Firestore berperilaku dalam skenario ini, saya ingin melihat bukti dari masalah kinerja sebelum mengasumsikan bahwa itu ada.
Frank van Puffelen
1
Katakanlah saya membutuhkan dokumen a, b, cuntuk melakukan sesuatu. Saya meminta ketiganya secara paralel dalam permintaan terpisah. amembutuhkan 100ms, bmembutuhkan 150ms, dan cmembutuhkan 3000ms. Akibatnya, saya harus menunggu 3000ms untuk melakukan tugas tersebut. Itu akan menjadi salah maxsatu dari mereka. Ini akan lebih berisiko bila jumlah dokumen yang akan diambil banyak. Bergantung pada status jaringan, saya rasa ini bisa menjadi masalah.
Joon
1
Bukankah mengirim mereka semua sebagai satu SELECT * FROM docs WHERE id IN (a,b,c)membutuhkan waktu yang sama? Saya tidak melihat perbedaannya, karena koneksi dibuat satu kali dan sisanya terhubung dengan pipa. Waktu (setelah pembuatan awal koneksi) adalah waktu buka semua dokumen + 1 perjalanan pulang pergi, sama untuk kedua pendekatan. Jika perilakunya berbeda untuk Anda, dapatkah Anda membagikan sampel (seperti dalam pertanyaan tertaut saya)?
Frank van Puffelen
Saya pikir saya kehilangan Anda. Ketika Anda mengatakan itu pipelined, maksud Anda Firestore secara otomatis mengelompokkan dan mengirim kueri ke server mereka dalam satu perjalanan ke database?
Joon
FYI, yang saya maksud dengan perjalanan pulang pergi adalah satu panggilan jaringan ke database dari klien. Saya bertanya apakah beberapa kueri secara otomatis dikelompokkan sebagai satu perjalanan pulang pergi oleh Firestore, atau Apakah beberapa kueri dilakukan sebagai beberapa perjalanan bolak-balik secara paralel.
Joon

Jawaban:

101

jika Anda berada dalam 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])}`);
* });
*/

Ini khusus untuk SDK server

PEMBARUAN: "Cloud Firestore [client-side sdk] Sekarang Mendukung Kueri DI!"

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

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

Nick Franceschina
sumber
29
Bagi siapa pun yang ingin memanggil metode ini dengan array referensi dokumen yang dihasilkan secara dinamis, Anda dapat melakukannya seperti ini: firestore.getAll (... arrayOfReferences) .then ()
Horea
1
Maaf, @KamanaKisinga ... Saya belum melakukan apa pun dalam firebase hampir setahun dan saat ini tidak dapat membantu (hei lihat, saya sebenarnya memposting jawaban ini setahun yang lalu hari ini!)
Nick Franceschina
2
SDK sisi klien sekarang juga menawarkan fungsi ini. lihat jawaban jeodonara untuk contoh: stackoverflow.com/a/58780369
Frank van Puffelen
6
peringatan: filter masuk dibatasi hingga 10 item saat ini. Jadi Anda mungkin akan mengetahui bahwa itu tidak berguna ketika Anda akan mencapai produksi.
Martin Cremer
9
sebenarnya Anda perlu menggunakan firebase.firestore.FieldPath.documentId()dan tidak'id'
Maddocks
23

Mereka baru saja mengumumkan fungsi ini, https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html .

Sekarang Anda dapat menggunakan kueri seperti, tetapi perlu diingat bahwa ukuran input tidak boleh lebih dari 10.

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

jeadonara
sumber
Ada kueri whereIn daripada di mana. Dan saya tidak tahu cara mendesain kueri untuk beberapa dokumen dari daftar id dokumen milik koleksi tertentu. Tolong bantu.
Kompilasi kesalahan akhir
17
@Compileerrorend bisakah Anda mencoba ini? db.collection('users').where(firebase.firestore.FieldPath.documentId(), 'in',["123","345","111"]).get()
jeadonara
terima kasih, terutama untukfirebase.firestore.FieldPath.documentId()
Ivan Chernykh
11

Dalam praktiknya, Anda akan menggunakan firestore.getAll seperti ini

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

atau dengan sintaks janji

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
sumber
4
ini harus benar-benar menjadi jawaban yang dipilih karena memungkinkan Anda menggunakan lebih dari 10 id
sshah98
10

Tidak, saat ini tidak ada cara untuk mengumpulkan beberapa permintaan baca menggunakan Cloud Firestore SDK dan oleh karena itu tidak ada cara untuk menjamin bahwa Anda dapat membaca semua data sekaligus.

Namun seperti yang dikatakan Frank van Puffelen dalam komentar di atas, ini tidak berarti bahwa mengambil 3 dokumen akan menjadi 3x lebih lambat dari mengambil satu dokumen. Sebaiknya lakukan pengukuran Anda sendiri sebelum mencapai kesimpulan di sini.

Sam Stern
sumber
1
Masalahnya adalah saya ingin mengetahui batasan teoretis kinerja Firestore sebelum bermigrasi ke Firestore. Saya tidak ingin bermigrasi dan kemudian menyadari bahwa itu tidak cukup baik untuk kasus penggunaan saya.
Joon
2
Hai, ada juga pertimbangan cose di sini. Katakanlah saya telah menyimpan daftar semua ID teman saya dan jumlahnya adalah 500. Saya bisa mendapatkan daftar dalam 1 biaya baca tetapi untuk menampilkan Nama dan photoURL mereka, saya akan dikenakan biaya 500 kali dibaca.
Tapas Mukherjee
1
Jika Anda mencoba membaca 500 dokumen, dibutuhkan 500 kali pembacaan. Jika Anda menggabungkan informasi yang Anda butuhkan dari semua 500 dokumen menjadi satu dokumen tambahan, itu hanya membutuhkan satu kali baca. Itu disebut duplikasi data semacam ini cukup normal di sebagian besar database NoSQL, termasuk Cloud Firestore.
Frank van Puffelen
1
@FrankvanPuffelen Misalnya, di mongoDb, Anda dapat menggunakan ObjectId seperti ini stackoverflow.com/a/32264630/648851 .
Sitian Liu
2
Seperti yang dikatakan @FrankvanPuffelen, duplikasi data cukup umum di database NoSQL. Di sini Anda harus bertanya pada diri sendiri seberapa sering data ini harus dibaca, dan seberapa mutakhirnya. Jika Anda menyimpan 500 informasi pengguna, katakanlah nama + foto + id mereka, Anda bisa mendapatkannya dalam satu kali baca. Tetapi jika Anda membutuhkannya terkini, Anda mungkin harus menggunakan fungsi cloud untuk memperbarui referensi ini setiap kali pengguna memperbarui nama / foto mereka, oleh karena itu menjalankan fungsi cloud + melakukan beberapa operasi tulis. Tidak ada implementasi yang "benar" / "lebih baik", ini hanya bergantung pada kasus penggunaan Anda.
schankam
10

Anda bisa menggunakan fungsi seperti ini:

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

Itu bisa dipanggil dengan satu ID:

getById('collection', 'some_id')

atau serangkaian ID:

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

Tentunya cara terbaik untuk melakukannya adalah dengan mengimplementasikan kueri Firestore yang sebenarnya di Cloud Function? Kemudian hanya akan ada satu panggilan pulang pergi dari klien ke Firebase, yang tampaknya menjadi apa yang Anda minta.

Anda benar-benar ingin menyimpan semua logika akses data Anda seperti sisi server ini.

Secara internal kemungkinan akan ada jumlah panggilan yang sama ke Firebase itu sendiri, tetapi semuanya akan berada di seluruh interkoneksi super cepat Google, bukan jaringan eksternal, dan dikombinasikan dengan pipelining yang dijelaskan oleh Frank van Puffelen, Anda akan mendapatkan kinerja yang sangat baik dari pendekatan ini.

Chris Wilson
sumber
3
Menyimpan implementasi di Cloud Function adalah keputusan yang tepat dalam beberapa kasus ketika Anda memiliki logika yang kompleks, tetapi mungkin tidak dalam kasus di mana Anda hanya ingin menggabungkan daftar dengan beberapa id. Apa yang Anda kehilangan adalah cache sisi klien dan format pengembalian standar dari panggilan biasa. Ini menyebabkan lebih banyak masalah kinerja daripada yang diselesaikan dalam beberapa kasus di aplikasi saya ketika saya menggunakan pendekatan tersebut.
Yeremia
5

Jika Anda menggunakan flutter, Anda dapat melakukan hal berikut:

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

Ini akan mengembalikan Masa Depan yang berisi List<DocumentSnapshot>yang dapat Anda ulangi sesuai keinginan Anda.

Monis
sumber
2

Inilah cara Anda melakukan hal seperti ini di Kotlin dengan Android SDK.
Mungkin tidak harus dilakukan dalam satu perjalanan pulang pergi, tetapi ini mengelompokkan hasil secara efektif dan menghindari banyak callback bersarang.

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
}

Perhatikan bahwa mengambil dokumen tertentu jauh lebih baik daripada mengambil semua dokumen dan memfilter hasilnya. Ini karena Firestore menagih Anda untuk kumpulan hasil kueri.

Markymark
sumber
1
Bekerja dengan baik, persis seperti yang saya cari!
Georgi
1

Saya harap ini membantu Anda, ini berhasil untuk saya.

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
sumber
0

Ini sepertinya tidak mungkin dilakukan di Firestore saat ini. Saya tidak mengerti mengapa jawaban Alexander diterima, solusi yang dia usulkan hanya mengembalikan semua dokumen dalam koleksi "pengguna".

Bergantung pada apa yang perlu Anda lakukan, Anda harus mencari duplikat data relevan yang perlu Anda tampilkan dan hanya meminta dokumen lengkap saat diperlukan.

Horea
sumber
-1

Yang terbaik yang dapat Anda lakukan adalah tidak menggunakannya Promise.allkarena klien Anda harus menunggu .allpembacaan sebelum melanjutkan.

Ulangi bacaan dan biarkan mereka menyelesaikannya sendiri. Di sisi klien, ini mungkin bermuara pada UI yang memiliki beberapa gambar pemuat kemajuan yang diselesaikan ke nilai secara independen. Namun, ini lebih baik daripada membekukan seluruh klien sampai .allpembacaan selesai.

Oleh karena itu, segera buang semua hasil sinkron ke tampilan, lalu biarkan hasil asinkron masuk saat diselesaikan, satu per satu. Ini mungkin tampak seperti perbedaan kecil, tetapi jika klien Anda memiliki konektivitas Internet yang buruk (seperti yang saya miliki saat ini di kedai kopi ini), membekukan seluruh pengalaman klien selama beberapa detik kemungkinan akan menghasilkan pengalaman 'aplikasi ini menyebalkan'.

Ronnie Royston
sumber
3
Ini asinkron, ada banyak kasus penggunaan untuk menggunakan Promise.all... tidak perlu "membekukan" apa pun - Anda mungkin perlu menunggu semua data sebelum Anda dapat melakukan sesuatu yang berarti
Ryan Taylor
Ada beberapa kasus penggunaan ketika Anda memang perlu memuat semua data Anda, oleh karena itu penantian (seperti spinner dengan pesan yang sesuai, tidak perlu "membekukan" UI apa pun seperti yang Anda katakan) bisa sangat dibutuhkan oleh Promise.all .. Ini sangat tergantung pada jenis produk yang Anda buat di sini. Komentar semacam ini menurut pendapat saya sangat tidak relevan dan tidak boleh ada kata "terbaik" di dalamnya. Ini benar-benar bergantung pada setiap kasus penggunaan yang berbeda yang dapat dihadapi seseorang dan apa yang dilakukan aplikasi Anda untuk pengguna.
schankam