对于用 NodeJS 编写的递归 MongoDB 存档器来说,这是一个好的架构吗?
Is this a good architecture for a recursive MongoDB archiver written in NodeJS?
我目前面临在 MongoDB 服务器上归档数据的挑战,该服务器已经 运行 一年多了,积累了将近 100GB 的数据(许多集合有 10+ 百万个文档)。令我非常失望的是,数据模型的设计方式与您期望在关系数据库中看到的非常相似;每个模型的集合和将记录相互关联的外键。例如:
// Collection: conversations (~1M documents)
{
_id: ObjectId(),
last_read_at: new Date,
}
// Collection: messages (~100M documents, 0-200k per conversation)
{
_id: ObjectId(),
conversation: ObjectId(),
}
// Collection: likes (~50M documents, 0-1k per message)
{
_id: ObjectId(),
message: ObjectId(),
}
如果我面对的是传统的 RDBMS,我可以很容易地使用 JOIN
找到并销毁所有相关的 chapters
和 pages
,将它们存档,然后 DELETE
他们。可惜我没那么幸运。
我目前的做法是这样的:
- 查询在
n
天内未读过的所有书籍,并将结果通过管道传输到 Archive Strategy
- 存档策略 - 并行:
- 使用来自
Cursor
的 pipe()
将文档存档到文件
- 逐个文档,查询引用文档的其他集合,将结果通过管道传输到新的存档策略流
我们最终得到的是一个依赖树,其中根是我们实际想要存档的集合,树中的每个级别都是一个用于存档其依赖项的流。鉴于我们的数据集和模型的复杂性,事实证明这速度慢得令人无法忍受。我想我有两个问题:
- 我能否以某种方式避免 n+1 次查询,或者这只是 MongoDB 的限制,因为我无法加入?我的下一个想法是使用
$in
批量依赖组中的查询
- 流是处理此类工作负载的最有效方式吗?
我通过进行以下更改成功地重新设计了程序,使速度提高了 100 倍以上:
- 我没有生成流树,而是将每个分支分解为单独的作业,然后放入队列中。这需要将中间数据(
_id
s 和其他需要爬行的键)临时保存到磁盘,但事实证明这对速度没有影响,因为每个分支在其他地方都遇到了瓶颈。这允许爬行的每个分支 运行 以自己的速度进行,而不会减慢更快的分支。树设计最终因争用太多而无法工作。
- 我使用集群模块在
n
工作进程中消耗了上述队列,其中 n
是我拥有的 CPU 个核心数。
- 从磁盘使用
_id
时,我将它们分成 1000 个一组,以最大限度地减少我必须执行的查询数量。
使用上述方法,我能够在大约三个小时内归档超过 100M 的文档,并且对用户体验没有明显影响。该程序现在 CPU 已绑定,但 运行 速度足够快,我不会深入挖掘。
我目前面临在 MongoDB 服务器上归档数据的挑战,该服务器已经 运行 一年多了,积累了将近 100GB 的数据(许多集合有 10+ 百万个文档)。令我非常失望的是,数据模型的设计方式与您期望在关系数据库中看到的非常相似;每个模型的集合和将记录相互关联的外键。例如:
// Collection: conversations (~1M documents)
{
_id: ObjectId(),
last_read_at: new Date,
}
// Collection: messages (~100M documents, 0-200k per conversation)
{
_id: ObjectId(),
conversation: ObjectId(),
}
// Collection: likes (~50M documents, 0-1k per message)
{
_id: ObjectId(),
message: ObjectId(),
}
如果我面对的是传统的 RDBMS,我可以很容易地使用 JOIN
找到并销毁所有相关的 chapters
和 pages
,将它们存档,然后 DELETE
他们。可惜我没那么幸运。
我目前的做法是这样的:
- 查询在
n
天内未读过的所有书籍,并将结果通过管道传输到 Archive Strategy - 存档策略 - 并行:
- 使用来自
Cursor
的 - 逐个文档,查询引用文档的其他集合,将结果通过管道传输到新的存档策略流
pipe()
将文档存档到文件 - 使用来自
我们最终得到的是一个依赖树,其中根是我们实际想要存档的集合,树中的每个级别都是一个用于存档其依赖项的流。鉴于我们的数据集和模型的复杂性,事实证明这速度慢得令人无法忍受。我想我有两个问题:
- 我能否以某种方式避免 n+1 次查询,或者这只是 MongoDB 的限制,因为我无法加入?我的下一个想法是使用
$in
批量依赖组中的查询
- 流是处理此类工作负载的最有效方式吗?
我通过进行以下更改成功地重新设计了程序,使速度提高了 100 倍以上:
- 我没有生成流树,而是将每个分支分解为单独的作业,然后放入队列中。这需要将中间数据(
_id
s 和其他需要爬行的键)临时保存到磁盘,但事实证明这对速度没有影响,因为每个分支在其他地方都遇到了瓶颈。这允许爬行的每个分支 运行 以自己的速度进行,而不会减慢更快的分支。树设计最终因争用太多而无法工作。 - 我使用集群模块在
n
工作进程中消耗了上述队列,其中n
是我拥有的 CPU 个核心数。 - 从磁盘使用
_id
时,我将它们分成 1000 个一组,以最大限度地减少我必须执行的查询数量。
使用上述方法,我能够在大约三个小时内归档超过 100M 的文档,并且对用户体验没有明显影响。该程序现在 CPU 已绑定,但 运行 速度足够快,我不会深入挖掘。