执行查询时,Firestore 必须能够以连续方式遍历索引。
这个introduction video 有点过时(因为现在可以使用“in”运算符进行“OR”查询),但它确实很好地可视化了 Firestore 在运行查询时所做的事情。
如果您的查询只是postsRef.orderBy("like_count", "desc").limit(10),Firestore 将加载它对降序"like_count" 的索引,提取前10 个条目并返回它们。
要处理您的查询,它必须从降序的"like_count" 索引中提取一个条目,将其与您的"time_posted" 要求进行比较,然后丢弃它或将其添加到有效条目列表中。一旦它拥有所有最近的帖子,它就需要按照您的指定对结果进行排序。由于这些步骤不使用连续读取索引,因此不允许这样做。
解决方案是根据最近的帖子建立自己的索引,然后从中提取结果。因为在客户端上这样做是不明智的,所以您应该使用云函数来为您完成工作。以下代码使用了Callable Cloud Function。
const MS_TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
export getRecentTopPosts = function.https.onCall((data, context) => {
// unless otherwise stated, return only 10 entries
const limit = Number(data.limit) || 10;
const postsRef = admin.firestore().collection("posts");
// OPTIONAL CODE SEGMENT: Check Cached Index
const twentyFourHoursAgo = Date.now() - MS_TWENTY_FOUR_HOURS;
const recentPostsSnapshot = await postsRef
.where("time_posted", ">", twentyFourHoursAgo)
.get();
const orderedPosts = recentPostsSnapshot.docs
.map(postDoc => ({
snapshot: postDoc,
like_count: postDoc.get("like_count"),
time_posted: postDoc.get("time_posted")
})
.sort((p1, p2) => {
const deltaLikes = p2.like_count - p1.like_count; // descending sort based on like_count
if (deltaLikes !== 0) {
return deltaLikes;
}
return p2.time_posted - p1.time_posted; // descending sort based on time_posted
});
// OPTIONAL CODE SEGMENT: Save Cached Index
return orderedPosts
.slice(0, limit)
.map(post => ({
_id: post.snapshot.id,
...post.snapshot.data()
}));
})
如果此代码预计会被许多客户端调用,您可能希望缓存索引以通过将以下段插入到上面的函数中来避免不断重建它。
// OPTIONAL CODE SEGMENT: Check Cached Index
if (!data.skipCache) { // allow option to bypass cache
const cachedIndexSnapshot = await admin.firestore()
.doc("_serverCache/topRecentPosts")
.get();
const oneMinuteAgo = Date.now - 60000;
// if the index was created in the past minute, reuse it
if (cachedIndexSnapshot.get("timestamp") > oneMinuteAgo) {
const recentPostMetadataArray = cachedIndexSnapshot.get("posts");
const recentPostIdArray = recentPostMetadataArray
.slice(0, limit)
.map((postMeta) => postMeta.id)
const postDocs = await fetchDocumentsWithId(postsRef, recentPostIdArray); // see https://gist.github.com/samthecodingman/aea3bc9481bbab0a7fbc72069940e527
// postDocs is not ordered, so we need to be able to find each entry by it's ID
const postDocsById = {};
for (const doc of postDocs) {
postDocsById[doc.id] = doc;
}
return recentPostIdArray
.map(id => {
// may be undefined if not found (i.e. recently deleted)
const postDoc = postDocsById[id];
if (!postDoc) {
return null; // deleted post, up to you how to handle
} else {
return {
_id: postDoc.id,
...postDoc.data()
};
}
});
}
}
// OPTIONAL CODE SEGMENT: Save Cached Index
if (!data.skipCache) { // allow option to bypass cache
await admin.firestore()
.doc("_serverCache/topRecentPosts")
.set({
timestamp: Date.now(),
posts: orderedPosts
.slice(0, 25) // cache the maximum expected amount
.map(post => ({
id: post.snapshot.id,
like_count: post.like_count,
time_posted: post.time_posted,
}))
});
}
您可以添加到此功能的其他改进包括:
- 字段掩码 - 即不返回发布文档的每个部分,而只返回标题,如计数、发布时间和作者。
- 可变发布时间(而不是 24 小时)
- 可变的最小喜欢计数
- 按作者过滤