Google Firestore - làm cách nào để lấy tài liệu theo nhiều id trong một chuyến đi khứ hồi?


100

Tôi đang tự hỏi liệu có thể nhận được nhiều tài liệu theo danh sách id trong một chuyến đi khứ hồi (cuộc gọi mạng) đến Firestore hay không.


4
Có vẻ như bạn cho rằng các vòng tua đang gây ra các vấn đề về hiệu suất trong ứng dụng của bạn. Tôi sẽ không cho rằng điều đó. Firebase có lịch sử hoạt động tốt trong những trường hợp như vậy, vì nó phân phối các yêu cầu . Mặc dù tôi chưa kiểm tra cách Firestore hoạt động trong trường hợp này, nhưng tôi muốn xem bằng chứng về sự cố hiệu suất trước khi giả định rằng nó tồn tại.
Frank van Puffelen

1
Hãy nói rằng tôi cần tài liệu a, b, cđể làm một cái gì đó. Tôi yêu cầu cả ba song song trong các yêu cầu riêng biệt. amất 100ms, bmất 150ms và c3000ms. Do đó, tôi cần đợi 3000ms để thực hiện tác vụ. Nó sẽ là maxcủa họ. Sẽ có nhiều rủi ro hơn khi số lượng tài liệu cần tìm nạp lớn. Phụ thuộc vào trạng thái mạng, tôi nghĩ điều này có thể trở thành một vấn đề.
Joon

1
SELECT * FROM docs WHERE id IN (a,b,c)Mặc dù vậy, việc gửi tất cả chúng như một đơn lẻ sẽ không mất cùng một khoảng thời gian sao? Tôi không thấy sự khác biệt, vì kết nối được thiết lập một lần và phần còn lại được gắn kết với điều đó. Thời gian (sau khi thiết lập kết nối ban đầu) là thời gian tải của tất cả các tài liệu + 1 chuyến khứ hồi, giống nhau cho cả hai cách tiếp cận. Nếu nó hoạt động khác với bạn, bạn có thể chia sẻ một mẫu (như trong câu hỏi được liên kết của tôi) không?
Frank van Puffelen

Tôi nghĩ rằng tôi đã mất bạn. Khi bạn nói đó là pipelined, bạn có nghĩa là Firestore tự động nhóm và gửi các truy vấn đến máy chủ của họ trong một chuyến đi vòng quanh cơ sở dữ liệu?
Joon

FYI, ý tôi là một chuyến đi khứ hồi là một cuộc gọi mạng đến cơ sở dữ liệu từ máy khách. Tôi đang hỏi liệu nhiều truy vấn có được Firestore tự động nhóm thành một chuyến khứ hồi hay Nhiều truy vấn được thực hiện song song dưới dạng nhiều chuyến khứ hồi.
Joon

Câu trả lời:


90

nếu bạn đang ở trong 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])}`);
* });
*/

Điều này dành riêng cho SDK máy chủ

CẬP NHẬT: "Cloud Firestore [sdk phía máy khách] Hiện hỗ trợ truy vấn!"

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

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


29
Đối với bất kỳ ai muốn gọi phương thức này với một mảng tham chiếu tài liệu được tạo động, bạn có thể thực hiện như sau: firestore.getAll (... arrayOfRefferences) .then ()
Horea

1
Tôi xin lỗi @KamanaKisinga ... Tôi đã không thực hiện bất kỳ nội dung firebase nào trong gần một năm và thực sự không thể giúp được gì vào thời điểm này (xem này, tôi thực sự đã đăng câu trả lời này một năm trước vào ngày hôm nay!)
Nick Franceschina

2
SDK phía máy khách hiện cũng cung cấp chức năng này. xem câu trả lời của jeodonara để làm ví dụ: stackoverflow.com/a/58780369
Frank van Puffelen

4
cảnh báo: bộ lọc trong hiện được giới hạn ở 10 mục. Vì vậy, có thể bạn sẽ thấy nó vô dụng khi bạn sắp bắt đầu sản xuất.
Martin Cremer

6
thực sự bạn cần sử dụng firebase.firestore.FieldPath.documentId()và không'id'
Maddocks

20

Họ vừa công bố chức năng này, https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html .

Bây giờ bạn có thể sử dụng các truy vấn như, nhưng hãy nhớ rằng kích thước đầu vào không được lớn hơn 10.

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


Có một truy vấn whereIn hơn là where. Và tôi không biết cách thiết kế truy vấn cho nhiều tài liệu từ danh sách id tài liệu thuộc bộ sưu tập cụ thể. Xin vui lòng giúp đỡ.
Lỗi biên dịch kết thúc

17
@Compileerrorend bạn có thể thử cái này không? db.collection('users').where(firebase.firestore.FieldPath.documentId(), 'in',["123","345","111"]).get()
jeadonara

cảm ơn bạn, đặc biệt là vìfirebase.firestore.FieldPath.documentId()
Cherniv

10

Không, hiện tại không có cách nào để ghép nhiều yêu cầu đọc bằng Cloud Firestore SDK và do đó không có cách nào để đảm bảo rằng bạn có thể đọc tất cả dữ liệu cùng một lúc.

Tuy nhiên, như Frank van Puffelen đã nói trong các nhận xét ở trên, điều này không có nghĩa là việc tìm nạp 3 tài liệu sẽ chậm gấp 3 lần so với việc tìm nạp một tài liệu. Tốt nhất là thực hiện các phép đo của riêng bạn trước khi đi đến kết luận ở đây.


1
Vấn đề là tôi muốn biết giới hạn lý thuyết đối với hiệu suất của Firestore trước khi chuyển đến Firestore. Tôi không muốn di chuyển và sau đó nhận ra rằng nó không đủ tốt cho trường hợp sử dụng của tôi.
Joon

2
Xin chào, cũng có một sự cân nhắc của cose ở đây. Giả sử tôi đã lưu trữ danh sách tất cả ID của bạn bè và con số là 500. Tôi có thể lấy danh sách trong 1 lần đọc nhưng để hiển thị Tên và photoURL của họ, tôi sẽ mất 500 lần đọc.
Tapas Mukherjee

1
Nếu bạn đang cố đọc 500 tài liệu, thì bạn phải mất 500 lần đọc. Nếu bạn kết hợp thông tin mà bạn cần từ tất cả 500 tài liệu thành một tài liệu bổ sung duy nhất, thì chỉ cần một lần đọc. Điều đó được gọi là loại trùng lặp dữ liệu là khá bình thường trong hầu hết cơ sở dữ liệu NoSQL, bao gồm Cloud Firestore.
Frank van Puffelen

1
@FrankvanPuffelen Ví dụ: trong mongoDb, bạn có thể sử dụng ObjectId như stackoverflow.com/a/32264630/648851 này .
Sitian Liu

2
Giống như @FrankvanPuffelen đã nói, việc trùng lặp dữ liệu là khá phổ biến trong cơ sở dữ liệu NoSQL. Ở đây, bạn phải tự hỏi mình tần suất đọc những dữ liệu này và mức độ cập nhật của chúng. Nếu bạn lưu trữ 500 thông tin người dùng, giả sử tên + ảnh + id của họ, bạn có thể nhận được thông tin đó trong một lần đọc. Nhưng nếu bạn cần chúng cập nhật, có thể bạn sẽ phải sử dụng chức năng đám mây để cập nhật các tham chiếu này mỗi khi người dùng cập nhật tên / ảnh của họ, do đó, chạy chức năng đám mây + thực hiện một số thao tác ghi. Không có cách triển khai "đúng" / "tốt hơn", nó chỉ phụ thuộc vào trường hợp sử dụng của bạn.
schankam

10

Trong thực tế, bạn sẽ sử dụng firestore.getAll như thế này

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

hoặc với cú pháp hứa hẹn

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
đây thực sự nên là câu trả lời được chọn vì nó cho phép bạn sử dụng hơn 10 id
sshah98

10

Bạn có thể sử dụng một chức năng như sau:

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

Nó có thể được gọi bằng một ID duy nhất:

getById('collection', 'some_id')

hoặc một mảng ID:

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

5

Chắc chắn cách tốt nhất để làm điều này là thực hiện truy vấn thực tế của Firestore trong một Hàm đám mây? Sau đó sẽ chỉ có một cuộc gọi khứ hồi duy nhất từ ​​khách hàng tới Firebase, đây dường như là những gì bạn đang yêu cầu.

Bạn thực sự muốn giữ tất cả logic truy cập dữ liệu của mình giống như phía máy chủ này.

Trong nội bộ, có thể sẽ có cùng một số lượng cuộc gọi đến chính Firebase, nhưng tất cả chúng sẽ được thực hiện trên các kết nối siêu nhanh của Google, thay vì mạng bên ngoài và kết hợp với đường ống mà Frank van Puffelen đã giải thích, bạn sẽ có được hiệu suất tuyệt vời từ cách tiếp cận này.


3
Lưu trữ việc triển khai trong Hàm đám mây là quyết định đúng đắn trong một số trường hợp bạn có logic phức tạp, nhưng có lẽ không phải trong trường hợp bạn chỉ muốn hợp nhất một danh sách với nhiều id. Những gì bạn mất là bộ nhớ đệm phía máy khách và định dạng trả về tiêu chuẩn hóa từ các cuộc gọi thông thường. Điều này gây ra nhiều vấn đề về hiệu suất hơn so với cách giải quyết trong một số trường hợp trong ứng dụng của tôi khi tôi sử dụng phương pháp này.
Jeremiah

3

Nếu bạn đang sử dụng Flagship, bạn có thể làm như sau:

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

Điều này sẽ trả về Tương lai có chứa List<DocumentSnapshot>mà bạn có thể lặp lại khi bạn cảm thấy phù hợp.


2

Đây là cách bạn làm điều gì đó như thế này trong Kotlin với Android SDK.
Có thể không nhất thiết phải trong một chuyến khứ hồi, nhưng nó có hiệu quả nhóm kết quả và tránh nhiều lệnh gọi lại lồng nhau.

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
}

Lưu ý rằng việc tìm nạp các tài liệu cụ thể tốt hơn nhiều so với việc tìm nạp tất cả các tài liệu và lọc kết quả. Điều này là do Firestore tính phí bạn cho tập hợp kết quả truy vấn.


1
Hoạt động độc đáo, chính xác những gì tôi đang tìm kiếm!
Georgi

0

Điều này dường như không thể xảy ra ở Firestore vào lúc này. Tôi không hiểu tại sao câu trả lời của Alexander được chấp nhận, giải pháp mà anh ấy đề xuất chỉ là trả lại tất cả các tài liệu trong bộ sưu tập "người dùng".

Tùy thuộc vào những gì bạn cần làm, bạn nên xem xét sao chép dữ liệu liên quan mà bạn cần hiển thị và chỉ yêu cầu một tài liệu đầy đủ khi cần thiết.


0

Điều tốt nhất bạn có thể làm là không sử dụng Promise.alllàm khách hàng của mình, sau đó phải đợi .allđọc trước khi tiếp tục.

Lặp lại các lần đọc và để chúng giải quyết độc lập. Về phía máy khách, điều này có thể bắt nguồn từ việc giao diện người dùng có một số hình ảnh trình tải tiến trình phân giải thành các giá trị một cách độc lập. Tuy nhiên, điều này tốt hơn là đóng băng toàn bộ khách hàng cho đến khi.all các lần đọc được giải quyết.

Do đó, hãy kết xuất tất cả các kết quả đồng bộ vào chế độ xem ngay lập tức, sau đó để các kết quả không đồng bộ xuất hiện khi chúng phân giải riêng lẻ. Điều này có vẻ giống như sự khác biệt nhỏ, nhưng nếu khách hàng của bạn có kết nối Internet kém (như tôi hiện đang có ở quán cà phê này), việc đóng băng toàn bộ trải nghiệm của khách hàng trong vài giây sẽ có thể dẫn đến trải nghiệm 'ứng dụng này tệ quá'.


2
Nó không đồng bộ, có rất nhiều trường hợp sử dụng để sử dụng Promise.all... nó không nhất thiết phải "đóng băng" bất cứ thứ gì - bạn có thể cần đợi tất cả dữ liệu trước khi có thể làm điều gì đó có ý nghĩa
Ryan Taylor

Có một số trường hợp sử dụng khi bạn cần tải tất cả dữ liệu của mình, do đó, Promise.all hoàn toàn cần đợi (giống như một vòng quay với thông báo thích hợp, không cần "đóng băng" bất kỳ giao diện người dùng nào như bạn nói). . Nó thực sự phụ thuộc vào loại sản phẩm bạn đang xây dựng ở đây. Những bình luận kiểu này theo ý kiến ​​của riêng tôi rất không liên quan và không nên có bất kỳ từ "tốt nhất" nào trong đó. Nó thực sự phụ thuộc vào mọi trường hợp sử dụng khác nhau mà người ta có thể gặp phải và ứng dụng của bạn đang làm gì cho người dùng.
schankam

0

Tôi hy vọng điều này sẽ giúp bạn, nó hiệu quả với tôi.

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);
          });
        });
    });
  }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.