前言
在数据仓库领域,OLAP(On-Line Analytical Processing,联机分析处理)数据库产品是数据分析计算与存储的核心平台,在生产环境中,MPP(Massively Parallel Processing)架构的数据库产品(例如GreenPlum、ClickHouse等)是最常用的计算平台。在传统MPP数据库(以下简称MPPDB)的基础上,MPP云数仓则基于云化基础设施,采用计算与存储分离的架构,获得了更加灵活高效的扩展性。本文将详细介绍MPP云数仓的底层数据架构,分析它的优势,以及新的挑战和解决方法。
一、MPPDB的数据分布
MPP是一种share nothing的完全无共享结构,这种架构中的每一个节点(segment)都是独立、自给的,各个节点的CPU、内存和存储资源完全独立,并且每一个节点都有独立的SQL引擎,架构如图1所示。

图1 share nothing典型架构
这种架构的优势是扩展性较好,能支持复杂的结构化查询。但无共享即意味着数据需要在集群中进行拆分,每一个节点都被分配一部分数据分片,各个节点互不干扰地进行SQL解析、执行,并返回结果给中央控制节点(master)进行汇总。所以,一个事务的处理,需要所有并行运算的节点都返回结果,才能完成事务的提交。在MPP架构中,分布式运算应该均匀地分摊到各个节点,这种均匀的任务分割靠的就是合理的数据分布(data distribution)。
在MPPDB中,决定数据分布的键值称为为分布键(distributed key),一般选择结构化数据表中的某一列作为该表的分布键。基于该分布键的键值,MPPDB会进行哈希运算,再将取得的hash值对集群规模(segment实例数量)进行取模运算,最终将每一行数据按照哈希运算的结果映射到固定的实例节点。一个具体的场景如图2所示,在分布键较为合理,没有严重数据倾斜的情况下,每一张表的数据都会被分布到3个节点。

图2 MPP架构数据分布示例
这种数据分布策略,优势是计算简单,对于节点数量固定的集群,维护代价较小。但它有两个非常明显的缺点:
数据分布是否均匀,完全取决于分布键的设置,容易产生数据倾斜。如果分布键的选择不合理(例如选择了日期等唯一值较少的列),则会使大量数据集中在部分节点,造成严重的数据倾斜。前面讲过,MPPDB由各个节点并行执行SQL,其效率由集群中执行最慢的节点决定。所以数据倾斜最终会带来计算倾斜,达不到MPP并行计算的目的。
当集群节点数发生变化时(例如扩容、缩容),所有节点上的数据都需要重新进行哈希分布。假设有n个计算节点,则数据和节点的映射关系为md(key,n),当扩容或缩容m个节点时,映射关系变为md(key,n+m)或md(key,n-m),由哈希运算的单向唯一性可知,原有的映射关系几乎全部失效,整个集群中的数据都需要进行重新分布。在规模较大的生产集群中,重分布的数据总量十分巨大,且各个节点之间大量数据发生移动,会产生巨大的网络和I/O开销,往往需停库进行扩容及重分布操作。
二、一致性哈希分布
为了解决上述问题,MPPDB引入了一致性哈希,这也是MPP云数仓目前采用的数据分布设计。一致性哈希原本是分布式存储与网络领域的一个重要算法,能够在动态的网络拓扑中,对数据进行快速的分发和路由,当节点数量发生变化时,能够很好地减少数据移动的开销。一致性哈希的大致过程是:
构建hash环——由于一致性哈希取模之后的结果,范围在0到2^32-1之间,所以我们可以将整个哈希值的空间组成一个虚拟的圆环。0点的位置值为0,按顺时针方向取值递增,在接近12点钟的位置,取值接近2^32-1,0与2^32-1在12点的位置几乎重合。那么所有一致性哈希运算的结果,都会落在hash环上。
将节点和数据映射到hash环——对各个节点和数据进行哈希运算,得到的结果值均可以对应到hash环上的某个点,由此所有的节点和数据都会映射到hash环上的固定位置。
将hash环上的数据映射到节点——将hash环上的每一个对象,按顺时针方向进行查找,遇到的第一个节点即为数据的归属节点。例如图3所示,Object B在hash环上顺时针方向遇到的第一个节点是Node B,所以它将被映射到B节点。

图3 一致性哈希分布
考虑节点扩容的场景:如图4所示,假设扩容一个Node X,且节点X落在hash环上的位置处在节点B和节点C之间。由数据顺时针映射的关系可知,此时对象A、B、D的映射关系不变,只有对象C需重新映射到X节点。所以扩容一个节点时,仅仅是新节点与上游节点(逆时针方向的第一个节点)之间的部分对象需要重新分布。

图4 扩容节点X时的数据移动
节点缩容时的原理也一样,由此可见,无论是扩容还是缩容,一致性哈希使得移动的数据量大大减少。正是基于这种优势,采用一致性哈希分布后,集群可以获得快速高效的动态伸缩能力。
三、hash倾斜的优化
由于哈希运算具有一定的随机性,所以当节点数量较少时,极有可能导致节点在hash环上的位置分布不均匀,那么最终的数据分布同样会产生倾斜。为了解决这个问题,MPPDB会预先确定一个固定的虚拟segment数,虚拟segment的数量会远大于物理segment,那么在hash环上,大量的虚拟segment就会分布地更加均匀。此时我们先将数据映射到虚拟segment,数据在虚拟segment中分布均匀,然后再将虚拟segment映射到物理segment。
这种处理方法的好处是,大量的虚拟segment保障了数据的均匀分布,同时虚拟segment的数量是固定的,所以在集群扩容或缩容的时候,数据到虚拟segment的映射关系保持不变,只有虚拟segment到物理segment的映射会发生改变。由上面讲到的一致性哈希原理,在n个物理segment组成的MPP集群中,增加1个物理segment,只有1/n的虚拟segment到物理segment的映射关系需要改变,也就是说只有1/n的数据需要重分布。
四、虚拟映射的优化
MPP云数仓整体的数据分布框架与上述MPPDB相同。但在实现方式上,MPPDB仍然存在一些不足,这是因为在多个虚拟segment映射到一个物理segment的过程中,所有虚拟segment的数据会集中成一个文件,最终在物理segment上落盘。数据一旦落盘,物理segment上看到的是一个数据分片(仅考虑单次写入),无法区分数据记录原本属于哪个虚拟segment。

这种实现方式,在发生节点扩容、缩容的时候,即使只有一部分虚拟segment对应的数据需要移动到其他节点,但也只能将所有数据全部读取出来,重新计算每条记录对应的虚拟segment,并筛选需要移动的虚拟segment,通过网络移动到其他物理segment,其余数据则重新写回本地磁盘。
由此可见,在MPPDB中,一致性哈希虽然避免了数据的全量迁移,大大减少了集群内部的网络传输开销,但在需要发生数据移动的节点,总的磁盘I/O并没有减少,数据在该节点上仍然是全量的读、写操作,使得该节点成为集群扩容或缩容中的瓶颈。所以传统的MPPDB仍然无法实现快速的弹性扩展,数据量越大,扩容和缩容的时间也就越长。
MPP云数的优化策略是:虚拟segment在映射到物理segment时,每个虚拟segment的数据都会单独存储成一个文件(仅考虑单次写入),所以在数据持久化落盘的时候,MPP云数仓保留了数据到虚拟segment之间的映射关系。

这种改进主要带来了两个好处:
扩容、缩容过程中,不需要重新计算每条记录对应的虚拟segment关系,消除了节点上全量数据的读开销;
需要移动的数据可以由数据及虚拟segment的映射关系直接获得,在存储与计算分离的架构中,其他节点可直接从对象存储获取虚拟segment对应的那部分数据,不需要由源节点进行网络数据传输,消除了大量的网络开销。
在这种策略下,扩容和缩容的过程只是动态调整了虚拟segment到物理segment的映射关系,底层数据文件不会发生变化。需要移动的数据,仅仅在SQL运行过程中,按需从对象存储拉取,并且仅仅拉取部分虚拟segment对应的数据。所以MPP云数仓的扩容和缩容操作效率很高,执行时间取决于申请虚机并加入集群所消耗的时间,与集群当前的数据量无关,真正实现了分钟级别的快速扩缩容,具备良好的弹性。
五、面临的新问题
从消除数据倾斜的角度看,虚拟segment的数量应该足够大,这时数据到虚拟segment的映射结果更加均匀,在映射到物理segment时,倾斜的比例就会越小。
容易形成碎文件,尤其是小批量加载时,大量的碎片文件对读写操作都会带来较大的负面影响;
每一个数据文件都需要分配单独的进程和资源进行处理,增加了节点负载。例如在数据加载时,需为每一个文件分配独立的内存,用于数据压缩等操作,当文件数过大,可能会触发OOM等问题,在并发数据加载时,出现问题的概率更大。
六、最佳实践及思考
为了解决上面阐述的新问题,目前的优化思路包括:针对大量碎文件,在后台自动进行文件合并,将多个碎文件合并成一个大文件,再进行后续的读写。针对OOM的问题,优化写操作时的内存分配策略,多个文件分配的总内存固定不变。
另外,在虚拟节点数量不宜过多的条件下,一种思路是,虚拟segment的数量尽可能是物理segment数量的整数倍。这时,虚拟segment数量不需要太大,就可以保障数据的均匀分布。由于虚拟segment的数量一开始就固定,而物理segment的数量是变化的。所以一种最佳实践是,选择一个合理大小的虚拟segment数量(例如1024、2048等),并对物理segment数量做一定的限制(例如最大不超过512、1024,且尽量满足2的倍数甚至是2的幂等),这样就能确保虚拟segment能均匀分布到物理segment,从而确保数据在各个物理节点上的均匀分布。
当然,在实际的生产环境中,各种业务场景和数据量的差异很大,除了前面讲到的这些优化策略之外,如果遇到单个集群业务高峰、压力太大,我们也可以临时扩容它的计算节点,使其计算能力得到提升,完成当前的高优任务,在作业完成之后,再执行缩容释放资源。
由上面讲到的数据分布设计可知,目前MPP云数仓实现这种扩容和缩容的能力是比较强的,扩缩容时间短,操作比较灵活。当然,扩缩容之后,仍然涉及部分节点的数据移动,在MPP云数仓的架构中,移动的数据会从对象存储中拉取,大规模的扩缩容会造成短暂的对象存储网络风暴,所以这种操作可以作为应急手段,但不适合对大规模集群做频繁的动态调配。
七、小结
在数据仓库领域,MPP架构因为其良好的扩展性和对分布式事务的支持,使其成为了高性价比的并行结构化数据处理和存储引擎。在MPP架构的处理逻辑中,数据分析和处理请求被分摊到各个独立的节点,最终的计算任务由各个节点上的数据决定,所以数据分布的策略最终影响着集群的扩展性,以及分布式架构的性能。
MPP云数仓通过计算与存储分离的设计,以及改进的一致性哈希分布,让它真正具备了灵活高效的扩展性,有效降低了集群内部的数据倾斜,缓解了MPP架构的木桶效应。

齐心抗疫
END
众志成城




