在MongoDB中快速搜索数十亿个小型文档的策略

23

我需要存储数十亿个小的数据结构(每个约200字节)。到目前为止,将每个元素作为单独的文档存储效果很好,Mongo每秒提供大约10,000个结果。我使用20字节的哈希作为每个文档的_id,并在_id字段上使用单个索引。经过测试,对于500万个文档的数据集,这种方式是可行的。

在操作中,我们将每秒进行大约10,000次请求,每秒更新现有文档约1,000次,并且可能每秒或更少地插入新文档100次。

当我们无法将整个索引存储在RAM中时,如何管理更大的数据集?如果我们将几个元素合并到每个文档中,MongoDB是否会表现得更好--用于更快速地搜索索引,但在每个查询中返回更多的数据?

与SO上的其他问题不同,我不仅对我们可以将多少数据塞入Mongo感兴趣。它显然可以管理我们所看到的数据量。我的担忧在于,如何在受限制的RAM条件下最大程度地提高对巨大集合的find操作的速度。

我们的搜索往往是聚集的;约50,000个元素将满足约50%的查询,但剩余50%将在所有数据上随机分布。通过将这50%移动到自己的集合中,以便始终将最常用的数据的较小索引保留在RAM中,我们可以期望获得性能提升吗?

将_id字段的大小从20字节减少到8字节,是否会对MnogoDB的索引速度产生重大影响?


由于你将拥有比 RAM 更多的文档,因此我建议尽可能缩小文档的大小,以增加可以容纳在 RAM 中的数据量。例如,确保字段名称只有一个或两个字符。你打算进行分片吗?将数据移动到同一服务器上的不同集合中不会改变 RAM 的使用情况,因为它是由操作系统管理的。 - WiredPrairie
随着数据的增长,我们将进行分片。 - Neil
将最常使用的记录放入不同的集合只是一个想法,为了保持RAM中较小集合的索引,并尝试防止其被交换出。我认为这可能有些幼稚,但我不确定为什么或为什么不。 - Neil
索引的内存不是独立于工作集合所需的内存进行管理的。它们都由操作系统进行管理。如果索引被频繁使用(比数据更频繁),则应该保留在内存中。 - WiredPrairie
1
坦白地说,没有更多的细节很难确定,但在我看来,优化MongoDB大规模N查询性能并不是解决这个问题的正确方法。你关于将不常使用的数据移动到单独的集合中的问题是朝着这个方向迈出的一步,但我会进一步思考:保留完整数据集在Mongo中,并在处理请求的地方拥有那50k个高容量记录的独立副本。但现在我们涉及到你的一致性要求了……也就是所谓的“有趣领域”。 - AdamKG
1个回答

32

有几个策略可以考虑:

1)使用一个独立的集合/数据库来存储 'hot' 文档。

如果您知道哪些文档属于热点集,则将它们移动到单独的集合中会有所帮助。这将确保热门文档共存于同一扩展/页中。 它也会使得这些文档的索引更有可能完全在内存中。 这是因为它更小并且被(完全?)更频繁地使用。

如果热门文档与其他文档随机混合,则加载文档时需要缺页更多B-Tree索引叶元素,因为另一个文档最近已经加载或访问索引块的概率很小。

2)缩短索引 值。

索引值越短,就越多的值适合于单个B-Tree块中。 (注意:键不包括在索引中。)单个桶中的更多条目意味着较少的桶以及索引所需的总内存较小。这意味着块在内存中停留的概率更高/寿命更长。 在您的示例中,20->8个字符的缩减比50%的节省更好。 如果可以将这8个字节转换为长整型,则可以获得更多的节省,因为长整型没有长度前缀(4个字节)和尾随空值(总共5个字节)。

3)缩短键名。

字段名越短,每个文档占用的空间就越小。 这具有减少可读性的不幸副作用。

4)分片

这确实是保持在整个语料库上进行的读取性能的唯一方法,该操作耗尽内存并逐渐使用磁盘带宽。 即使进行分片,您仍然需要分片“hot”集合。

5)将磁盘上的预读调整为较小的值。

由于“非热”读取正在从磁盘加载随机文档,因此我们只想读取/缺页该文档以及周围尽可能少的文档。 大多数系统会尝试一次从文件的一部分读取大块数据。这完全相反于我们想要的。

如果您看到系统频繁缺页,但mongod进程的常驻内存未达到系统可用内存,则可能会看到操作系统读取无用数据的影响。

6)尽量使用单调递增的键值。

这将触发优化(针对基于ObjectId的索引),当索引块分裂时,它将以90/10而不是50/50进行。 结果是您的索引中大多数块都接近容量,因此您需要更少的块。

如果仅事后知道“hot” 50,000个文档,则按索引顺序将其添加到单独的集合中也会触发此优化。

罗布


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接