MPP 分析型数据库架构变迁

 从架构特点到功能缺陷,重新认识分析型分布式数据库 (juejin.cn)

 

 

 

 

 

###################################

MPP on HDFS

 

MPP架构

这是MPP架构分布式数据库的简单示意图。MPP数据库通过将数据切片分布到各个计算节点后并行处理来解决海量数据分析的难题。每个MPP数据库集群由一个主节点(为了提供高可用性,通常还会有一个从主节点)和多个计算节点组成。主节点和每个计算节点都有自己独立的CPU,内存和外部存储。主节点负责接收客户端的请求,生成查询计划,并将计划下发到每个计算节点,协调查询计划的完成,最后汇总查询结果返回给客户端。计算节点负责数据的存储以及查询计划的执行。计算节点之间是没有任何共享依赖的(shared nothing)。查询在每个计算节点上面并行执行,大大提升了查询的效率。

我们接下来要讲的开源分布式数据库Greenplum Database就是基于PostgreSQL的MPP数据库。对应到这个架构图,每个节点上面的数据库实例可以简单的认为是一个PostgreSQL实例。

并行查询计划

我们首先通过一条简单的查询,感性地认识一下Greenplum Database是如何执行一条查询的。

下略

。。。

。。。

 

 

 

高可用

介绍完Greenplum Database的查询组件和系统状态组件后,我们再看看它是如何提供高可用性的。首先是管理节点的高可用。我们采取的方式是,启动一个称为Standby的从主节点作为主节点的备份,通过同步进程同步主节点和Standby节点两者的事务日志,在Standby节点上重做系统表的更新操作,从而实现两者在全局系统表上面的信息同步。当主节点出故障的时候,我们能够切换到Standby节点,系统继续正常工作,从而实现管理节点的高可用。

计算节点高可用性的实现类似于管理节点,但是细节上有些小不同。每个Segment实例都会有另外一个Segment实例作为备份。处于正常工作状态的Segment实例我们称为Primary,它的备份称为Mirror。不同于管理节点日志重放方式,计算节点的高可用是通过文件复制来实现的。对于每一个Segment实例,它的状态以文件的形式保存在本地存储介质中。这些本地状态可以分成三大类:本地系统表、本地事务日志和本地表分区数据。通过以文件复制的方式保证Primary和Mirror之间的状态一致,我们能够实现计算节点的高可用。

GoH

Hadoop出现之前,MPP数据库是为数不多的大数据处理技术之一。随着Hadoop的兴起,特别是HDFS的成熟,越来越多的数据被保存在HDFS上面。一个自然的问题出现了:我们怎样才能高效地分析保存在HDFS上面的数据,挖掘其中的价值。4,5年前,SQL-on-Hadoop远没有现在这么火,市场上的解决方案也只有耶鲁大学团队做的Hadapt和Facebook做的Hive,像Impala,Drill,Presto,SparkSQL等都是后来才出现的。而Hadapt和Hive两个产品,在当时无论是易用性还是查询性能方面都差强人意。

我们当时的想法是将Greenplum Database跟HDFS结合起来。与其它基于connector连接器的方式不同,我们希望让HDFS,而不是本地存储,成为MPP数据库的数据持久层。这就是后来的Apache HAWQ项目。但在当时,我们把它叫做Greenplum on Hadoop,其实更准确的说法应该是,Greenplum on HDFS。当时的想法非常简单,就是将Greenplum Database和HDFS部署在同一个物理机器集群中,同时将Greenplum Database中的Append-only表的数据放到HDFS上面。Append-only表指的是只能追加,不能更新和删除的表。这是由于HDFS本身只能Append的属性决定的。

除了Append-only表之外,Greenplum Database还支持Heap表,这是一类能够支持增删改查的表。结合前面提到的Segment实例的本地状态,我们可以将本地存储分成四大类:系统表、日志、Append-only表分区数据和非Append-only表分区数据。我们将其中的Append-only表分区数据放到了HDFS上面。每个Segment实例对应一个HDFS的目录,非常直观。其它三类数据还是保存在本地的磁盘中。

总体上说,相比于传统的Greenplum Database, Greenplum on HDFS架构上并没有太多的改动,只是将一部分数据从本地存储放到了HDFS上面,但是每个Segment实例还是需要通过本地存储保存本地状态数据。所以,从高可用性的角度看,我们还是需要为每个实例提供备份,只是需要备份的数据少了,因为Append-only表数据的高可用性现在是由HDFS数据的高可用性来保证。

Greenplum on HDFS作为一个原型系统,验证了MPP数据库和HDFS是可以很好地整合起来工作的。基于这个原型系统,我们开始将它当成一个真正的产品来打造,也就是后来的HAWQ。

从Greenplum on HDFS到HAWQ,我们主要针对本地存储做了系统架构上的调整。我们希望将计算节点的本地状态彻底去掉。本地状态除了前面提到的系统表(系统表又可以细分成只读系统表(系统完成初始化后不会再发生更改的元数据,主要是数据库内置的数据类型和函数)和可写系统表(主要是通过DDL语句对元数据的修改,如创建新的数据库和表))、事务日志、Append-only表分区数据和非Append-only表分区数据,同时还有系统在查询执行过程中产生的临时数据,如外部排序时用到的临时文件。其中临时数据和本地只读系统表的数据都是不需要持久化的。我们需要考虑的是如何在Segment节点上面移除另外四类状态数据。

Append-only表分区数据前面已经提到过,交给HDFS处理。为了提高访问HDFS的效率,我们没有采用Hadoop官方提供的HDFS访问接口,而是用C++实现了原生的HDFS访问库,libhdfs3。针对非Append-only表数据的问题,我们的解决方案就比较简单粗暴了:通过修改DDL,我们彻底禁止用户创建Heap表,因为Heap表支持更新和删除。所以,从那时起到现在最新的Apache HAWQ,都只支持表数据的追加,不支持更新和删除。没有了表数据的更新和删除,分布式事务就变得非常简单。通过为每个Append-only表文件对应的元数据增加一列,逻辑EoF,即有效的文件结尾,只要能够保证EoF的正确性,我们就能够保证事务的正确性。而且Append-only表文件的逻辑EoF信息是保存在主节点的全局系统表中的,它的正确性通过主节点的本地事务保证。为了清理Append-only表文件在追加新数据时事务abort造成的脏数据,我们实现了HDFS Truncate功能。

对于本地可写系统表,我们的做法是将Segment实例上面的本地可写系统表放到主节点的全局系统表中。这样主节点就拥有了全局唯一的一份系统表数据。查询执行过程中需要用到的系统元数据,我们通过Metadata Dispatch的方式和查询计划一起分发给每个Segment实例。

无状态Segment

通过上述的一系列策略,我们彻底摆脱了Segment节点的本地状态,也就是实现了无状态Segment。整个系统的高可用性策略就简单很多,而且也不再需要为Segment节点提供Mirror,系统的利用率大大提升。

数据的高可用交给了HDFS来保证。当一个Segment节点出故障后,我们可以在任意一台有空闲资源的机器上重新创始化一个新的Segment节点,加入到集群中替代原来出故障的节点,整个集群就能够恢复正常工作。

我们也做到了计算和存储物理上的解耦合,往彻底摆脱传统MPP数据库(例如Greenplum Database)计算和存储紧耦合的目标迈出了有着实质意义的一步。

逻辑集成

虽然在HAWQ 1.x的阶段,我们做到了计算和存储物理上的分离,但是逻辑上两者还是集成的。原因是,在将本地表分区数据往HDFS上面迁移的时候,为了不改变原来Segment实例的执行逻辑流程,我们为每个Segment指定了一个其专有的HDFS目录,以便跟原来本地数据目录一一对应。每个Segment负责存储和管理的数据都放在其对应的目录的底下,而且该目录底下的文件,也只有它自身能够访问。这种HDFS数据跟计算节点逻辑上的集成关系,使得HAWQ 1.x版本依然没有摆脱传统MPP数据库刚性的并发执行策略:无论查询的复杂度如何,所有计算节点都需要参与每条查询的执行。这意味着,系统执行一条单行插入语句所使用的计算资源,和执行一条对几TB数据进行复杂多表连接和聚合的语句所使用的资源是一样的。这种刚性的并行执行策略,极大地约束了系统的扩展性和吞吐量,同时也与Hadoop基于查询复杂度来调度计算资源的弹性策略相违背。

逻辑分离

我们决心对HAWQ的系统架构做一次大的调整,使其更加地Hadoop Native,Hadoop原生,而不仅仅是简单地将数据放到HDFS上面。当时,我们内部称这个新版本为HAWQ 2.0,也就是大家现在在github上面看到的Apache HAWQ。

其中最重要的一步是,我们希望计算和存储不仅物理上分离,逻辑上也是分离。数据库中的用户表数据在HDFS上不再按照每个Segment单独来组织,而是按照全局的数据库对象来组织。举个例子,我们将一张用户表对应的多个数据文件(因为往该表插入数据的时候,为了提高数据插入的效率,系统会启动了多个QE进程同时往HDFS写数据,每个QE写一个单独文件)放到同一个目录底下,而不是像原来那样,每个QE进程将文件写到自己对应的Segment目录底下。这种改变带来的一个直观结果就是,由于所有的数据文件都放在一起,查询执行的时候,根据需要扫描的数据量不同,我们既可以使用一个Segment实例去完成表扫描操作,也可以使用多个Segment实例去做,彻底摆脱了原来只能使用固定数量个Segment实例来执行查询的刚性并行执行策略。

当然,HDFS数据目录组织的改变只是实现HAWQ 2.0弹性执行引擎的一步,但却是最重要的一步。计算和存储的彻底分离,使得HAWQ可以像MapReduce一样根据查询的复杂度灵活地调度计算资源,极大地提升了系统的扩展性和吞吐量。

资源调度

我们简单比较一下HAWQ 1.x和HAWQ 2.0的资源调度。

左边展现的是HAWQ 1.x在同时处理三个查询(分别来自三个不同的会话)时的资源调度情况。和传统MPP数据库一样,无论查询的复杂度如何,每个Segment实例都会参与每条查询的执行。换句话说,每个Segment实例都会启动一个QE进程处理分配给它的任务。在这种情况下,系统能够支持的并发查询数量,跟集群的计算节点数没有任何关系,完全由一个计算节点上面的资源量决定(这里,我们先不考虑主节点成为瓶颈的问题)。一个4个节点的HAWQ集群能够支持的并发查询数量和一个400个节点的集群是一样的。

右边展现的是HAWQ 2.0在同样并发查询下的资源调度情况。和Hadoop的MapReduce一样,我们能够根据查询的复杂度决定需要调度多少计算资源参与查询的执行。为了方便阐述,这里假设每条查询只需要两个计算资源单元。而且,执行单元可以根据资源管理器的调度算法分配到不同的物理计算节点上面。这两点灵活性:计算资源的数量可变和计算资源的位置可变,正是HAWQ 2.0弹性执行引擎的核心。在这种情况下,系统能够支持的并发查询数量,跟集群的计算节点数量呈线性关系:计算节点越多,系统能够支持的并发查询数量越多(再次提醒,这里,我们先不考虑主节点成为瓶颈的问题)。

所以,可以说,HAWQ 2.0成功解决了传统MPP数据仓库中计算节点首先成为吞吐量瓶颈的问题。同时,由于并不是所有计算节点都需要参与到每条查询的执行中,HAWQ 2.0同时也解决了传统MPP数据库由于单个计算节点性能下降直接影响整个集群性能的问题(这导致MPP集群不能包含太多的计算节点,因为根据概率,集群节点到达一定值后,出现单个计算节点性能下降的概率将会非常高),从而也很大程度上解决了扩展性问题。

云端数据仓库

通过将计算和存储彻底分离成功解决了计算节点成为系统吞吐量瓶颈的问题后,现在系统的唯一瓶颈就剩下主节点。

如前面提到,主节点的功能主要分成两类:元数据管理,包括系统表存储和管理、锁管理和分布式事务等等,和计算资源调度管理和执行。前者我们可以看成是状态管理,后者是没有状态的组件。通过将状态管理提取出来成为单独一个功能层,我们让主节点跟计算节点一样变得没有状态。这样,我们能够根据系统并发查询的变化,动态增加或者减少主节点的数量。这个设计借鉴了Hadoop YARN的设计。YARN的框架通过将原来的JobTracker的功能分成了Resource Manager和Application Manager,从而解决Hadoop集群吞吐量的问题。

这是一个云端数据仓库的架构图。实际上,我们在HashData希望通过云端数据仓库解决企业用户使用数据仓库时碰到的多种难题,包括商业上的和技术上的。在这里,我们只关注技术上的。

在这个系统架构中,我们将管理即元数据、计算和存储三者分离,每一层都能单独动态伸缩,在解决系统吞吐量和扩展性问题的同时,提供了多维度的弹性。

我们利用云平台的对象存储服务,如AWS的S3和青云QingCloud的QingStor,作为系统数据的持久层。除了按需付费的经济特性外,云平台的对象存储服务在可扩展性、稳定性和高可用性等方面远胜于我们自己维护的分布式文件系统(如HDFS)。虽然对象存储的访问延迟远高于本地磁盘访问,但是我们可以通过本地缓存的策略很大程度减轻延迟问题。

同样的,我们利用云平台提供的虚拟机作为我们的计算资源,也能够一定程度上实现资源的隔离,从而保证不同的工作负载之间没有相互影响。

云平台提供的近乎无限的计算和存储资源(相对于数据仓库应用来说),使得云端数据仓库能够存储和处理的数据达到一个全新的高度。

总结

最后,我们做一个简单的总结。从PostgreSQL到Greenplum Database,我们通过大规模并行处理(MPP)技术,实现了处理海量数据时的低延迟目标。从Greenplum Database到Apache HAWQ,通过计算和存储分析的策略,我们提升了系统的并发处理能力和扩展性。从Apache HAWQ到Cloud Data Warehouse,我们借助云平台近乎无限的计算资源和存储资源,以及管理、计算和数据三者分离,还有计算资源严格隔离,我们能够取得近乎无限的并发处理能力和扩展性。

MPP数据库采取的是流水式的执行引擎,中间的每个阶段是不带检查点的。这意味着,只有有一个参与到查询执行的QE进程出错,整条查询将会失败,只能从头开始重新执行这条查询。而我们知道,当参与到查询执行的QE进程达到一定数量的时候,QE进程出错将是必然的,特别是在一个资源共享的环境中。这时候,即使是重新提交查询重跑,失败还是必然的。换句话说,我们几乎无法成功执行需要调度大量计算资源的查询。

展望未来,我们希望实现带检查点的流水式执行引擎,从而使得系统能够处理任意大的查询(单个查询需要同时调度成千上万的计算资源)。

posted @   乌鸦嘴-raven  阅读(1229)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示