我们有一个很大的MongoDB集合,我们想开始分片。这个集合有3.4b条记录,大小约为14.6tb(磁盘上压缩了5.3tb)。这个收藏通常每小时大约500万字,但我们预计这将继续逐年增长。此集合上的索引大小约为220GB。
所有记录都有一个feedId
,所有查询都是针对属于特定feedId
的记录。目前约有200个唯一的feedId
值,但每个值的分布都是高度非线性的。在低端,一些feedid可能每天只能看到几十条记录。另一方面,前5个feedid占数据集的75%。
记录也有一个timestamp
并且查询总是针对给定的日期范围。timestamp
字段或多或少是单调的。feedId
和timestamp
上已经存在一个复合索引。
此集合的典型工作集仅为最后几周的数据,因此仅占实际数据的很小百分比。对这些数据的查询必须非常快,对历史数据的查询速度较慢是可以接受的。因此,我们计划使用“标记”和/或“区域”将较旧的数据移动到具有较大HDD的节点,并使用具有固态硬盘的节点来处理“热”数据。
基于这些因素,使用切分键{feedId: 1, timestamp: 1}
是否合理?我的感觉是,由于feedId
的非线性和timestamp
的单调性,它可能导致“热”节点。在密钥中添加一个“散列”字段会使其更好/更糟吗?
最佳答案
所以让我们一点一点地接受这个!
该集合有3.4b条记录,大小约为14.6tb(磁盘上压缩了5.3tb)
切分的性质是如此的重要,使这是正确的第一次通过。我将在这里详细介绍,但是tl;dr是:
将数据集的一部分(例如,使用mongodump --query
)提取到分段群集(例如,使用mongorestore
)
将一个示例工作负载指向临时群集以模拟生产环境
测试一个或多个分片键组合。根据需要转储/重新加载,直到您对性能满意为止。
现在,让我们深入了解一下:
目前有大约200个唯一的feedid值,但是每个值的分布都是高度非线性的。在低端,一些feedid可能每天只能看到几十条记录。另一方面,前5个feedid占数据集的75%。
因此,一个支持大量查询的字段的频率非常低。如果你只是在这个领域进行切分,你肯定会看到热点1
记录也有时间戳,查询总是针对给定的日期范围。时间戳字段或多或少是单调的。
因此,另一个字段支持大多数查询,但也不太适合分片2
记录也有时间戳,查询总是针对给定的日期范围。时间戳字段或多或少是单调的。
这对我来说意味着你查询的主字段是基于时间的。在给定的时间段内,给我指定feedid的文档。您还将获得目标查询,因为您经常查询shard键(例如,在一个时间范围内,或在一个时间范围内+thefeedId
)。3
这也支持您的分区想法:
因此,我们计划使用“标记”和/或“区域”将较旧的数据移动到具有较大HDD的节点,并使用具有固态硬盘的节点来处理“热”数据。
使用分区,您可以使用shard密钥中的任何密钥,只要包含指向该密钥的整个前缀。所以{ feedId: 1, timestamp: 1 }
主要支持feedid和timestamp上的区域,这并不是您想要的。4
单凭这一点,我敢说{ timestamp : 1, feedId : 1 }
是个不错的选择。您的测试需要考虑的是
低频场到单调递增场提供了良好的块分布。
现在,就散列而言:
在密钥中添加一个“散列”字段会使其更好/更糟吗?
如果您的意思是,您的文档已经有了一些散列字段,那么您肯定可以添加这些字段只是为了随机性。但如果你说的是散列碎片密钥,那就另当别论了。5
区域和散列碎片键不能一起播放。散列shard key的性质意味着块范围(因此区域)表示散列shard key值。因此,即使您有两个值非常接近的文档,它们很可能会以完全不同的块结束。因此,在一系列散列的shard键值上创建一个区域可能做不到您希望它做的事情。您可以做一些事情,比如使用带有散列碎片的区域将整个集合移动到集群中碎片的子集上,但这不是您想要做的。6
现在有一个关键问题你可能会遇到-你有一个巨大的收藏。选择shard key可能会导致mongodb尝试将数据分成块的初始拆分出现问题。请查看我们文档中的以下部分:Sharding an Existing Collection。这里有一个公式供您使用,用于估计shard键在配置的块大小(默认为64MB)下可以支持的最大集合大小。我猜你首先需要将块大小增加到128MB或者256MB。这仅在初始分片过程中需要。然后,您可以将块大小减小到默认值,并让mongodb处理其余的部分。
请注意,这将对性能产生影响。将有块跨碎片迁移,加上实际块分割的开销。我建议您在这里发到我们的Google Group获得更具体的指导。