当数据集变大时如何缓解 firebase worker 的启动时间过长
How to mitigate against long startup times in firebase workers when dataset gets large
Firebase 有一个有趣的 feature/nuisance,当您侦听数据引用时,您会获得曾经添加到该引用的所有数据。因此,例如,当您收听 'child_added' 时,您会重播从一开始就添加到该 ref 的所有 children。我们正在编写一个评论系统,其数据集看起来像这样:
/comments
/sites
/sites/articles
/users
站点有很多文章,文章有很多评论,用户有很多评论。
我们希望能够跟踪用户发表的所有评论,因此我们认为将评论放在单独的 ref 中而不是按它们所属的文章进行分区是明智的。我们有一个后端侦听器,需要在新评论到达时对其进行处理(增加 child 计数,调整用户的统计信息等)。我担心的是,一段时间后,如果它必须处理每条评论的重播,它将花费很长时间才能启动。
我考虑过可能只在文章中存储评论并在用户 table 中存储对每个评论 siteId/articleId/commentId 的引用,这样我们仍然可以找到给定用户的所有评论,但这会使后端,因为它可能需要为每个站点甚至每篇文章设置一个单独的侦听器,这可能会使管理这么多侦听器变得困难。
想象一下,如果其中一篇文章位于一个非常 high-traffic 的网站上,该网站有数万篇文章,每篇文章都有数千条评论。缩放的答案是以某种方式跟踪每个站点的流量级别并以将它们分配给不同的工作进程的方式设置和分区它们吗?还有启动时间的问题以及每次我们加载我们的工作人员时需要多长时间重播所有数据?
如果您只需要处理一次新评论,您可以将它们放在一个单独的列表中,例如newComments
与 comments
(已处理的)。完成处理后,将它们从 newComments
移动到 comments
。
或者,您可以像今天一样将所有评论保存在一个列表中,并向其中添加一个您最初设置为 true 的字段(例如 "isNew")。完成处理后,您可以使用 orderByChild('isNew').equalTo(true)
和 update({ isNew: false })
进行过滤。
除了 Frank 的回答之外,还有其他几种可能性。
使用一个queue strategy
既然工人真的希望处理一次性事件,那么给他们一次性事件,他们可以从队列中拉出并在完成处理后删除。这优雅地解决了多工作场景,并确保不会因为服务器离线而遗漏任何东西
利用时间戳减少积压
避免工作人员 reboot/startup 期间积压的一个简单策略是为所有事件添加时间戳,然后执行如下操作:
var startTime = Date.now() - 3600 // an hour ago
pathRef.orderByChild('timestamp').startAt( startTime );
跟踪最后处理的 ID
这仅适用于 push ids,因为不按关键字自然排序的格式可能会在将来的某个时候变得乱序。
处理记录时,让您的工作人员通过将该值写入 Firebase 来跟踪它添加的最后一条记录。然后可以使用 orderByKey().startAt( lastKeyProcessed )
来避免积压。恼人的是,我们必须丢弃第一个密钥。但是,这是一个高效的查询,不需要索引的数据存储,并且可以快速实现。
Firebase 有一个有趣的 feature/nuisance,当您侦听数据引用时,您会获得曾经添加到该引用的所有数据。因此,例如,当您收听 'child_added' 时,您会重播从一开始就添加到该 ref 的所有 children。我们正在编写一个评论系统,其数据集看起来像这样:
/comments
/sites
/sites/articles
/users
站点有很多文章,文章有很多评论,用户有很多评论。
我们希望能够跟踪用户发表的所有评论,因此我们认为将评论放在单独的 ref 中而不是按它们所属的文章进行分区是明智的。我们有一个后端侦听器,需要在新评论到达时对其进行处理(增加 child 计数,调整用户的统计信息等)。我担心的是,一段时间后,如果它必须处理每条评论的重播,它将花费很长时间才能启动。
我考虑过可能只在文章中存储评论并在用户 table 中存储对每个评论 siteId/articleId/commentId 的引用,这样我们仍然可以找到给定用户的所有评论,但这会使后端,因为它可能需要为每个站点甚至每篇文章设置一个单独的侦听器,这可能会使管理这么多侦听器变得困难。
想象一下,如果其中一篇文章位于一个非常 high-traffic 的网站上,该网站有数万篇文章,每篇文章都有数千条评论。缩放的答案是以某种方式跟踪每个站点的流量级别并以将它们分配给不同的工作进程的方式设置和分区它们吗?还有启动时间的问题以及每次我们加载我们的工作人员时需要多长时间重播所有数据?
如果您只需要处理一次新评论,您可以将它们放在一个单独的列表中,例如newComments
与 comments
(已处理的)。完成处理后,将它们从 newComments
移动到 comments
。
或者,您可以像今天一样将所有评论保存在一个列表中,并向其中添加一个您最初设置为 true 的字段(例如 "isNew")。完成处理后,您可以使用 orderByChild('isNew').equalTo(true)
和 update({ isNew: false })
进行过滤。
除了 Frank 的回答之外,还有其他几种可能性。
使用一个queue strategy
既然工人真的希望处理一次性事件,那么给他们一次性事件,他们可以从队列中拉出并在完成处理后删除。这优雅地解决了多工作场景,并确保不会因为服务器离线而遗漏任何东西
利用时间戳减少积压
避免工作人员 reboot/startup 期间积压的一个简单策略是为所有事件添加时间戳,然后执行如下操作:
var startTime = Date.now() - 3600 // an hour ago
pathRef.orderByChild('timestamp').startAt( startTime );
跟踪最后处理的 ID
这仅适用于 push ids,因为不按关键字自然排序的格式可能会在将来的某个时候变得乱序。
处理记录时,让您的工作人员通过将该值写入 Firebase 来跟踪它添加的最后一条记录。然后可以使用 orderByKey().startAt( lastKeyProcessed )
来避免积压。恼人的是,我们必须丢弃第一个密钥。但是,这是一个高效的查询,不需要索引的数据存储,并且可以快速实现。