为我们的内容应用优化 Firestore 查询

Optimizing Firestore queries for our content app

我们正在使用 Firestore 构建内容应用。 基本要求是有一个大师合集,比方说'content'。文档数量可以运行变成1000s。

content1, content2, content3 ... content9999

我们希望为我们的用户提供来自该集合的内容,确保他们不会看到相同的内容两次,并且每次他们在应用程序中时都会为他们提供新内容。 同时,我们不希望向每个用户提供相同顺序的内容。一些随机化会很好。

user1: content9, content123, content17, content33, content902 .. and so on
user2: content854, content79, content190, content567 ... and so on

我一直在思考如何在不复制主集合的情况下实现这个解决方案。复制主集合会非常昂贵,但可以完成工作。

此外,我们如何才能编写具有成本效益和性能优化的查询,尤其是当我们想要在这些内容片段的序列中保持随机化时?

这是我的建议。请将其视为伪代码,因为我没有 运行 它。

如果内容文档 ID 不可见

您必须存储和维护哪个用户看过哪些内容,例如在一个集合中:/seen/uid_contentId

查看 从集合中获取随机文档的巧妙方法。您需要存储集合的大小,可能作为另一个集合中的文档。所以你可以这样做:

const snapshot = await firestore.doc(`/userSeen/${uid}`).get(); // do it only once
const alreadySeen = snapshot.exists ? snapshot.data.contents : [];

async function getContent(uid) {
  for (let trials = 0; trials < 10; trials++) { // limit the cost
    const startAt = Math.random() * contentCollectionSize;
    const snapshot = await firestore.collection("/contents").startAt(startAt).limit(1).get();
    const document = snapshot.empty ? null : snapshot.docs[0]; // a random content

    if(document.exists && !alreadySeen.includes(document.id)) {
      alreadySeen.push(document.id);
      await firestore.doc(`/userSeen/${uid}`).set({contents: arrayUnion(document.id)}); // mark it as seen
      return document;
    }
  }

  return null;
}

在这里您可能需要对 Firestore 进行多次查询(上限为 10 次以限制成本),因为您无法在客户端计算内容文档 ID。

如果内容文档 ID 遵循简单模式:1、2、3,...

为了节省成本和性能,您应该将每个用户看到的所有内容存储在一个文档中(限制为 1MB,即超过 250,000 个整数!)。然后你为每个用户下载一次这个文档,并在客户端检查是否已经看到了随机内容。

const snapshot = await firestore.doc(`/userSeen/${uid}`).get(); // do it only once
const alreadySeen = snapshot.exists ? snapshot.data.contents : [];


async function getContent(uid) {
  let idx = Math.random() * contentCollectionSize;

  for (let trials = 0; trials < contentCollectionSize; trials++) { 
    idx = idx + 1 < contentCollectionSize ? idx + 1 : 0;

    if(alreadySeen.includes(idx)) continue; // this shortcut reduces the number of Firestore queries

    const document = await firestore.doc(`/contents/${idx}`).get();

    if(document.exists){
      alreadySeen.push(idx);
      await firestore.doc(`/userSeen/${uid}`).set({contents: arrayUnion(idx)}); // mark it as seen
      return document;
    }
  }

  return null;
}

如您所见,如果您为内容使用可预见的文档 ID,成本会低很多。但也许有人会有更好的主意。

我有另一个想法。您可以生成内容标量:D

  1. 创建另一个集合 - 标量
  2. 添加字段类型数组
  3. 编写一个函数,它将遍历内容集合并随机生成内容项集或考虑其他属性,如流行度、人口统计、用户行为。
  4. 在标量集合中生成 1000 组内容项,例如每月执行一次。
  5. 您甚至可以衡量每个标量在吸引回访用户和推广更具吸引力的用户方面的有效性。
  6. 一旦您拥有包含集合项集的标量集合,您就可以将用户分配给标量。并相应地呈现内容项。