深入理解MongoDB片键选择原则(一)

片键选择对于Sharded Cluster有非常重大的意义,但在实际接触的案例中往往很多人选择了错误的片键,导致集群性能低下。其实在官方文档中已经对Shard Key有非常详尽的介绍,所有的注意事项和选择原则都十分清楚。所以在阅读本文之前请仔细阅读官方文档中的Shard Keys

基本原则

基于文档中的内容,我们再来仔细理解一下片键选择的这些原则到底都代表了什么意义。

取值基数

即是片键字段的备选值。这里有2个必须了解的知识点,理解它们才能理解为什么取值基数这么重要:

  • chunk定义的是一个连续的片键值范围,文档中的片键字段取值在这个范围内时,文档就属于这个chunk;
  • 在不同shard间均衡时是以chunk为单位,而不是文档;

但是chunk其实只是一个虚拟的概念,它仅存在于元数据中,存放文档的shard并不知道chunk的存在。在一个集群中,文档的分布大致是这样的:
Chunk

也许有人要问,而为什么要有chunk的概念?为什么不是以文档为单位进行均衡?那我们看一下如果以文档为单位进行均衡会带来什么后果:

  • 首先我们必须知道每个文档分布在哪个shard上(元数据)
  • 如果这样的分布情况放在config上,那么元数据数量将和文档数量一样多,代表着config的数据量将会跟shard达到同样的水平

这在实现上是不现实的,因为我们既然选择分片,大部分时候是因为数据容量或处理能力已经超出了单台机器所能承受的范围。所以如果元数据也达到了这样的数量,即代表元数据很有可能也必须分片,那么又会有元数据的元数据,以及元数据的元数据的元数据……现实当中元数据只是一个复制集而已,可见它的数据量要远小于分片中的数据量(最小可达1/250000),而造成这个结果的原因正是因为有chunk的存在。举个容易理解的例子:

如果文档是学生,那么chunk就是班级,运动会的时候老师会以班级为单位指挥大家行动而不是指挥每一个人。同样,均衡的时候是以chunk为单位指挥每个chunk的文档到哪里去,而不是每个文档。

上面说了很多关于chunk存在的必要性的题外话,那么为什么取值基数对于chunk如此重要?取值基数直接决定了理论上最多能有多少chunk。而最多有多少chunk又会影响到什么?再来看一个例子:

假设要存储一个学校的所有的师生情况,选择年龄为片键。人的年龄具有非常固定的范围,假设为[0,99]。可见当学校人数较多的时候,chunk可能被拆得非常细(请暂时忽略什么时候会拆chunk的问题,后文再详细描述),比如(0, 10], (11, 15], (16, 30] …。但是无论怎么拆,因为年龄是整数,最细的情况下也就是一个数字一个chunk,所以我们最多只可以拆出100个chunk。随着数据增长这100个chunk将会越来越大,并且每个chunk的读写压力往往是不均匀的,所以哪个shard存储了压力较大的chunk比较多,相应地这个shard的压力也会比较大。但是此时系统并不会有任何动作,因为对于系统而言,每个片上的chunk数量是均衡的。

综上所述,取值基数直接决定了一共有多少个chunk,从而间接影响到分片的数据量/压力分布。选择时应该尽可能选择基数较大(即可选值较多)的字段作为片键。

片键取值分布

前一条原则讲的是片键的可能取值范围,这一条原则讲的则是片键在可能取值范围内的分布情况。分布情况会对集群造成什么影响?前面已经提到MongoDB的均衡是以chunk为单位进行的,只要chunk数量均衡了,对系统而言就是均衡的。所以我们不难发现,即使chunk数量均衡,文档数量可能并不均衡。如图所示,shard 1上虽然有2个chunk但实际文档数量可能还不如shard 2上的一个chunk多。此时无论是文档数量还是空间大小都是不均衡的,而我们却无能为力。
Imbalance

仍然以上面的例子继续说明:

对于学校而言,人数最多的是7~20岁的学生,可见7,8,9……19,20这几个chunk会非常繁忙,数据量会非常大,导致拥有这些chunk的shard也会非常繁忙并且面临存储压力。如果不巧这些chunk中的大部分正好都分布在同一个片上,那么这个片可能就忙不过来了。

这就体现出了片键取值分布情况的重要性:片键取值分布直接影响到每个片的压力情况,应该尽可能选择取值分布均匀的字段做片键。

发表评论