【问题标题】:Firestore, how to structure a "likedBy" queryFirestore,如何构建“likedBy”查询
【发布时间】:2018-02-27 14:14:20
【问题描述】:

我在思考如何最好地构建我的(非常简单的)Firestore 应用程序时遇到了一些麻烦。我有一组这样的用户:

users: {
   'A123': {
      'name':'Adam'
   },
   'B234': {
      'name':'Bella'
   },
   'C345': {
      'name':'Charlie'
   }
}

...每个用户都可以“喜欢”或“不喜欢”任意数量的其他用户(例如 Tinder)。

我想构建一个“喜欢”表(或 Firestore 等效表),以便列出我尚未喜欢或不喜欢的人。我最初的想法是在用户表中创建一个“喜欢”对象,其布尔值如下:

users: {
   'A123': {
      'name':'Adam',
      'likedBy': {
         'B234':true,
      },
      'disLikedBy': {
         'C345':true
      }
   },
   'B234': {
      'name':'Bella'
   },
   'C345': {
      'name':'Charlie'
   }
}

这样,如果我是 Charlie 并且我知道我的 ID,我可以列出我还没有喜欢或不喜欢的用户:

var usersRef = firebase.firestore().collection('users')
.where('likedBy.C345','==',false)
.where('dislikedBy.C345','==',false)

这不起作用(每个人都被列出)所以我怀疑我的方法是错误的,尤其是 '==false' 部分。有人可以为我指出如何构建它的正确方向吗?作为一个额外的额外问题,如果有人更改了他们的名字会发生什么?我是否需要更改所有嵌入的“likedBy”数据?或者我可以使用云功能来实现这一点吗?

谢谢!

【问题讨论】:

  • 这个.where('likedBy.C345','==',false) 将返回likedBy.C345 的值为false 的用户。它不会返回对likedBy.C345 没有任何价值的用户。我认为没有办法查询后者。另见this answer。嗯....毕竟我找到了解决方案:twitter.com/abeisgreat/status/929808518428762112。如果可行,我会尝试并写下答案。
  • 谢谢弗兰克。看起来“orderBy”技巧能够过滤掉不为空的记录,但我猜想相反。查找尚未为该 ID 设置“likedBy”字段的记录。我可能一开始就错误地构建了数据库吗?我猜这是一个非常典型的应用案例。
  • 是的,我刚刚发现了同样的情况:它只返回具有该字段的文档。我已经在想我们到底是如何获得这种性能的。 :-) 这意味着我知道如何实现这个用例的唯一方法是像我给出的第一个链接一样预先填充字段,这可能包括扇出添加到系统中的任何新用户的 UID。相当繁重的工作,所以我肯定会考虑这是否真的是一个值得努力的常见用例。
  • 也许我理解错了,但肯定有很多用例,其中一个用户喜欢另一个用户,而您想列出不喜欢的用户。是否有类似的关系可以帮助我正确地构建它?干杯
  • 我能想到的唯一方法是为每个未投票的用户预先填充false

标签: node.js firebase google-cloud-firestore


【解决方案1】:

对于这个问题没有一个完美的解决方案,但是根据你想要的权衡,你可以做一些替代方案。

选项:过扫描与欠扫描

请记住,Cloud Firestore 仅允许扩展独立于数据集总大小的查询。

这对于防止您构建可以在测试中使用 10 个文档的东西非常有帮助,但是一旦您投入生产并变得流行,就会崩溃。不幸的是,这种类型的问题不适合这种可扩展的模式,而且您拥有的个人资料越多,人们创建的赞越多,在这里回答您想要的查询所需的时间就越长。

然后,解决方案是找到一个或多个可扩展且最能代表您想要的查询。我能想到的有两种选择可以以不同的方式进行权衡:

  1. 过扫描 --> 进行更广泛的查询,然后在客户端进行过滤
  2. 欠扫描 --> 执行一个或多个可能遗漏一些结果的窄查询。

过扫描

在 Overscan 选项中,您基本上是在通过增加成本来获得 100% 的准确度。

鉴于您的用例,我想这实际上可能是您的最佳选择。由于个人资料的总数可能比个人喜欢的个人资料数量大几个数量级,因此增加的过度扫描成本可能无关紧要。

只需选择与您拥有的任何其他条件匹配的所有配置文件,然后在客户端,过滤掉用户已经喜欢的任何配置文件。

首先,获取用户喜欢的所有个人资料:

var likedUsers = firebase.firestore().collection('users') .where('likedBy.C345','==',false)

然后获取所有用户,检查第一个列表并丢弃任何匹配的内容。

var allUsers = firebase.firestore().collection('users').get()

根据规模,您可能希望优化第一步,例如每次用户喜欢某人时,为该用户在单个文档中为他们喜欢的每个人更新一个数组。这样您就可以在第一步中简单地获取一个文档。

var likedUsers = firebase.firestore().collection('likedUsers') .doc('C345').get()

由于此查询确实会根据结果集的大小进行扩展(通过将结果集定义为数据集),Cloud Firestore 可以回答它,而无需进行大量隐藏的不可扩展工作。不可扩展的部分留给您优化(上面有 2 个示例)。

欠扫描

在欠扫描选项中,您基本上是在交易准确性以获得更窄(因此更便宜)的结果集。

此方法更复杂,因此您可能只想在由于某种原因喜欢与不喜欢的比率不像我在 Overscan 选项中所怀疑的那样时才考虑它。

基本想法是,如果您确实喜欢某个人,则将其排除在外,并接受权衡,即您可能还会排除您尚未喜欢的人 - 是的,基本上是 Bloom filter

在每个用户配置文件中存储一个从0mtrue/false 值映射(稍后我们将了解m 的含义),其中所有内容最初都设置为false .

当用户喜欢个人资料时,计算用户 ID 的哈希值以插入 Bloom 过滤器,并将映射中的所有这些位设置为 true

如果我们使用m = 4,假设C345 哈希为0110,那么您的地图将如下所示:

likedBy: { 
   0: false,
   1: true,
   2: true,
   3: false }

现在,要找到您绝对不喜欢的人,您需要使用相同的概念对地图中的每个位进行查询。对于 0m 的任何位,您的哈希为真,查询它是否为假:

var usersRef = firebase.firestore().collection('users')
.where('likedBy.1','==',false)

等等。 (当我们将来支持 OR 查询时,这将变得更容易)。任何拥有false 值且您的用户ID 散列为true 的人肯定不会被他们喜欢。

由于您不太可能希望显示所有配置文件,仅足以显示单个页面,您可能可以随机选择一个 ID 的哈希位为真,然后对其进行查询。如果您的配置文件用完了,只需选择另一个正确的配置文件并重新启动。

假设大多数个人资料被点赞 500 次或更少,您可以使用 m = 1675 将误报率保持在 ~20% 或更少。

有方便的在线计算器可帮助您计算每个个人资料的点赞率、所需的误报率以及mfor example here

过扫描 - 奖金

您很快就会发现,在“过扫描”选项中,每次运行查询时,都会显示用户上次不喜欢的相同配置文件。我假设你不想要那个。更糟糕的是,用户喜欢的所有内容都将出现在查询的早期,这意味着您最终将不得不一直跳过它们并增加您的成本。

有一个简单的解决方法,使用我在这个问题上描述的方法,Firestore: How to get random documents in a collection。这将使您能够从集合中提取随机配置文件,从而为您提供更均匀的分布,并减少偶然发现许多以前喜欢的配置文件的机会。

欠扫描 - 奖金

我怀疑您在使用欠扫描选项时会遇到的一个问题是非常流行的配置文件。如果某人几乎总是被喜欢,那么如果该配置文件的大小不合理而无法保存在单个文档中,您可能会开始超出布隆过滤器的用处(您希望 m 小于 8000 以避免运行Cloud Firestore 中每个文档的索引限制)。

对于这个问题,您希望仅为这些配置文件组合 Overscan 选项。使用 Cloud Functions,任何将地图的 x% 以上设置为 true 的配置文件都会将 popular 标志设置为 true。对热门标志上的每个人进行过度扫描,并将它们编织到您的欠扫描结果中(请记住进行丢弃设置)。

【讨论】:

    猜你喜欢
    • 2021-05-28
    • 2019-04-08
    • 1970-01-01
    • 2021-01-10
    • 2014-03-15
    • 1970-01-01
    • 2023-01-17
    • 1970-01-01
    • 2021-06-05
    相关资源
    最近更新 更多