MongoDB 分片键
MongoDB Sharding Key
我们有一个很大的 MongoDB 集合,我们想开始分片。该集合有 3.4B 条记录,大小约为 14.6TB(磁盘上压缩了 5.3TB)。该集合通常每小时写入约 5M,但我们预计这一数字将逐年增长。此集合的索引大小约为 220GB。
所有记录都有 feedId
,所有查询都将针对属于特定 feedId
的记录。目前大约有 200 个独特的 feedId
值,但每个值的分布是高度非线性的。在低端,一些 feedId 每天可能只能看到几十条记录。另一方面,前 5 个 feedId 占数据集的 ~75%。
记录也有一个 timestamp
,查询将始终针对给定的日期范围。 timestamp
字段或多或少是单调的。
feedId
和 timestamp
上已经存在复合索引。
此集合的典型工作集只是最近几周的数据,因此只占实际数据的很小一部分。对该数据的查询必须非常快,对历史数据的较慢查询是可以接受的。因此,我们计划使用 "tags" and/or "zones" 将较旧的数据移动到具有更大 HDD 的节点,并使用具有 SSD 的节点来存储 "hot" 数据。
基于这些因素,使用 {feedId: 1, timestamp: 1}
的片键是否合理?我的感觉是,由于feedId
的非线性和timestamp
的单调性,可能会导致"hot"个节点。将 "hashed" 字段添加到密钥是否会使其成为 better/worse?
所以让我们一点一点地来吧!
The collection has 3.4B records and is ~14.6TB in size (5.3TB compressed on disk)
分片的性质使得在第一次通过时就把它做好很重要。我将在这里详细介绍,但 TL;DR 是:
- 将数据集的一部分(例如使用
mongodump --query
)提取到暂存集群(例如使用 mongorestore
)
- 将示例工作负载指向暂存集群以模拟您的生产环境
- 测试一个或多个分片键组合。 Dump/reload 根据需要,直到您对性能感到满意为止。
现在,让我们深入探讨:
There are currently ~200 unique feedId values, but the distribution across each value is highly non-linear. On the low end, some feedId's may only see dozens of records per day. On the other hand, the top 5 feedId's make up ~75% of the dataset.
因此,支持大量查询的一个字段的频率非常低。如果你只是在这个字段上分片,你肯定会看到热点 1
Records also have a timestamp and queries will always be for a given date range. The timestamp field is more-or-less monotonic.
所以另一个字段支持你的大部分查询,但也不适合分片 2
Records also have a timestamp and queries will always be for a given date range. The timestamp field is more-or-less monotonic.
这对我来说有点暗示您要查询的主要字段是基于 time 的。对于给定的 time 时间段,请提供具有指定 feedID 的文档。您还将获得有针对性的查询,因为您经常查询分片键(例如,在某个时间范围内,或者某个时间范围内 + feedId
)。 3
这也支持你的分区想法:
As such, we're planning to use "tags" and/or "zones" to move older data to nodes with larger HDD's and use nodes with SSD's for the "hot" data.
通过分区,您可以使用分片键中的任何键,只要您包含通向该键的 整个 前缀即可。因此 { feedId: 1, timestamp: 1 }
将主要支持 feedId 和 时间戳上的区域,这并不是您想要的。 4
仅凭这一点,我敢说 { timestamp : 1, feedId : 1 }
会是一个不错的选择。您的测试需要研究的是是否添加
low-frequency 字段到单调递增字段提供良好的块分布。
现在,就散列而言:
Would adding a "hashed" field to the key make it better/worse?
如果您的意思是,您的文档 已经 有一些散列字段,那么您绝对可以添加它只是为了随机性。但是,如果您谈论的是 散列分片键 ,那就另当别论了。 5
区域和散列分片键不能一起玩。散列分片键的性质意味着块范围(因此区域)表示 散列 分片键值。因此,即使您有两个文档的值彼此非常接近,它们也很可能以完全不同的块结束。因此,在一系列散列分片键值上创建区域可能不会执行您想要的操作。您 可以 做一些事情,比如使用带有散列分片的区域将整个 collection 移动到集群中的分片子集,但这不是您想要做的。 6
现在有一个您可能 运行 进入的关键问题 - 您有一个 巨大 collection。您选择的分片键可能会导致初始拆分出现问题,其中 MongoDB 尝试将您的数据分成块。请查看我们文档中的以下部分:Sharding an Existing Collection。那里有一个公式供您使用来估计您的分片键可以支持的最大 collection 大小以及配置的块大小(默认情况下为 64MB)。我猜测您最初需要将块大小增加到 128MB 或 256MB。这仅在 initial 分片过程中需要。之后,您可以将块大小恢复为默认值,让 MongoDB 处理其余部分。
请注意,这会对性能产生影响。您将有块在分片之间迁移,加上实际块拆分的开销。我建议您 post 到我们的 Google Group 以获得更具体的指导。
我们有一个很大的 MongoDB 集合,我们想开始分片。该集合有 3.4B 条记录,大小约为 14.6TB(磁盘上压缩了 5.3TB)。该集合通常每小时写入约 5M,但我们预计这一数字将逐年增长。此集合的索引大小约为 220GB。
所有记录都有 feedId
,所有查询都将针对属于特定 feedId
的记录。目前大约有 200 个独特的 feedId
值,但每个值的分布是高度非线性的。在低端,一些 feedId 每天可能只能看到几十条记录。另一方面,前 5 个 feedId 占数据集的 ~75%。
记录也有一个 timestamp
,查询将始终针对给定的日期范围。 timestamp
字段或多或少是单调的。
feedId
和 timestamp
上已经存在复合索引。
此集合的典型工作集只是最近几周的数据,因此只占实际数据的很小一部分。对该数据的查询必须非常快,对历史数据的较慢查询是可以接受的。因此,我们计划使用 "tags" and/or "zones" 将较旧的数据移动到具有更大 HDD 的节点,并使用具有 SSD 的节点来存储 "hot" 数据。
基于这些因素,使用 {feedId: 1, timestamp: 1}
的片键是否合理?我的感觉是,由于feedId
的非线性和timestamp
的单调性,可能会导致"hot"个节点。将 "hashed" 字段添加到密钥是否会使其成为 better/worse?
所以让我们一点一点地来吧!
The collection has 3.4B records and is ~14.6TB in size (5.3TB compressed on disk)
分片的性质使得在第一次通过时就把它做好很重要。我将在这里详细介绍,但 TL;DR 是:
- 将数据集的一部分(例如使用
mongodump --query
)提取到暂存集群(例如使用mongorestore
) - 将示例工作负载指向暂存集群以模拟您的生产环境
- 测试一个或多个分片键组合。 Dump/reload 根据需要,直到您对性能感到满意为止。
现在,让我们深入探讨:
There are currently ~200 unique feedId values, but the distribution across each value is highly non-linear. On the low end, some feedId's may only see dozens of records per day. On the other hand, the top 5 feedId's make up ~75% of the dataset.
因此,支持大量查询的一个字段的频率非常低。如果你只是在这个字段上分片,你肯定会看到热点 1
Records also have a timestamp and queries will always be for a given date range. The timestamp field is more-or-less monotonic.
所以另一个字段支持你的大部分查询,但也不适合分片 2
Records also have a timestamp and queries will always be for a given date range. The timestamp field is more-or-less monotonic.
这对我来说有点暗示您要查询的主要字段是基于 time 的。对于给定的 time 时间段,请提供具有指定 feedID 的文档。您还将获得有针对性的查询,因为您经常查询分片键(例如,在某个时间范围内,或者某个时间范围内 + feedId
)。 3
这也支持你的分区想法:
As such, we're planning to use "tags" and/or "zones" to move older data to nodes with larger HDD's and use nodes with SSD's for the "hot" data.
通过分区,您可以使用分片键中的任何键,只要您包含通向该键的 整个 前缀即可。因此 { feedId: 1, timestamp: 1 }
将主要支持 feedId 和 时间戳上的区域,这并不是您想要的。 4
仅凭这一点,我敢说 { timestamp : 1, feedId : 1 }
会是一个不错的选择。您的测试需要研究的是是否添加
low-frequency 字段到单调递增字段提供良好的块分布。
现在,就散列而言:
Would adding a "hashed" field to the key make it better/worse?
如果您的意思是,您的文档 已经 有一些散列字段,那么您绝对可以添加它只是为了随机性。但是,如果您谈论的是 散列分片键 ,那就另当别论了。 5
区域和散列分片键不能一起玩。散列分片键的性质意味着块范围(因此区域)表示 散列 分片键值。因此,即使您有两个文档的值彼此非常接近,它们也很可能以完全不同的块结束。因此,在一系列散列分片键值上创建区域可能不会执行您想要的操作。您 可以 做一些事情,比如使用带有散列分片的区域将整个 collection 移动到集群中的分片子集,但这不是您想要做的。 6
现在有一个您可能 运行 进入的关键问题 - 您有一个 巨大 collection。您选择的分片键可能会导致初始拆分出现问题,其中 MongoDB 尝试将您的数据分成块。请查看我们文档中的以下部分:Sharding an Existing Collection。那里有一个公式供您使用来估计您的分片键可以支持的最大 collection 大小以及配置的块大小(默认情况下为 64MB)。我猜测您最初需要将块大小增加到 128MB 或 256MB。这仅在 initial 分片过程中需要。之后,您可以将块大小恢复为默认值,让 MongoDB 处理其余部分。
请注意,这会对性能产生影响。您将有块在分片之间迁移,加上实际块拆分的开销。我建议您 post 到我们的 Google Group 以获得更具体的指导。