简介:本文介绍了云原生数据仓库产品AnalyticDB PostgreSQL从Cloud-Hosted到Cloud-Native的演进摸索,探讨为了实现真正的资源池化和灵便售卖的底层设计和思考,涵盖内容包含产品的架构设计,关键技术,性能后果,成果实现和后续打算几方面。
作者 | 翰明
起源 | 阿里技术公众号
一 前言
Garner预测,到2022年,所有数据库中有75%将部署或迁徙至云平台。另外一家权威机构IDC也预测,在2025年,超过50%的数据库将部署在私有云上,而中国则会达到惊人的70%以上。云数据库通过多年倒退,经验从Cloud-Hosted (云托管)到 Cloud Native(云原生)模式的转变。
Cloud-Hosted:基于市场和业界的云需要,大部分厂商抉择了云托管作为演进的第一步。这种模式将不再须要用户线下自建IDC,而是依靠于云提供商的标准化资源将数据仓库进行移植并提供高度托管,从而解放了用户对底层硬件的治理老本和灵打算资源的束缚。
Cloud-Native:然而随着更多的业务向云上迁徙,底层计算和存储一体的资源绑定,导致用户在应用的过程中仍然须要考量不必要的资源节约,如计算资源减少会要求存储关联减少,导致有效老本。用户开始冀望云资源可能将数据仓库进行更为细粒度的资源拆解,即对计算,存储的能力进行解耦并拆分成可售卖单元,以满足业务的资源编排。到这里,云原生的最大化价值才被真正凸显,咱们不在着重于打造存算均衡的数据仓库,而是面向用户业务,容许存在大规模的计算或存储歪斜,将业务所须要的资源进行独立部署,并依照最小单位进行售卖。这一刻咱们真正的进入了数据仓库云原生时代。
阿里云在2021云栖大会上,预报了全新云原生架构的数据仓库【1】。本文介绍了云原生数据仓库产品AnalyticDB PostgreSQL(以下简称ADB PG)从Cloud-Hosted到Cloud-Native的演进摸索,探讨为了实现真正的资源池化和灵便售卖的底层设计和思考,涵盖内容包含产品的架构设计,关键技术,性能后果,成果实现和后续打算几方面。(全文浏览时长约为10分钟)
二 ADB PG云原生架构
为了让用户能够疾速的适配到云数据仓库,目前咱们采纳的是云上MPP架构的设计理念,将协调节点和计算节点进行独立部署,但承载于单个ECS上,实现了计算节点存储计算一体的部署设计,该设计因为设计架构和客户侧自建高度适配,可疾速并无损的将数仓业务迁徙至云上,对于晚期的云适配十分敌对且满足了资源可平行扩大的次要诉求。
随着云原生的进一步演进,咱们提供了全新的存算拆散架构,将产品进一步拆分为服务层、计算层和共享存储层,架构图如下:
Master协调节点:保留全局的schema信息,并实现了全局事务管理;
行存引擎:用来保留元数据信息,这里的元数据信息次要指共享存储文件的可见性信息,包含两个局部:
- 一个是文件与表的关系
- 另外一个是被删除的数据的delete bitmap
基于行存咱们能够继承PG的本地事务能力,在增删改查的同时,与PG的事务能力齐全兼容;
本地缓存:通过引入存储团队的DADI来实现高性能的本地缓存,DADI全称是Alibaba Cloud Data Accelerator for Disaggregated Infrastructure,相比开源产品,性能有数量级的晋升;
共享存储:咱们借鉴了ClickHouse的一些要害设计,在存储层实现了一个基于MergeTree的行列混存,此外咱们对共享存储基于文件接口做了一层对立拜访接口,同时高度适配了OSS和HDFS 两种状态的分布式文件系统;
当咱们在架构设计的时候,和同样源自Greenplum的HAWQ比照,HAWQ把元数据都保留在master,在每次写的时候,把批改的元数据带到master来更新,读的时候,先从master读取须要的元数据,而后在执行打算外面把元数据都带上,这样segment就能拿到对应的元数据, 同时segment能够齐全无状态。
然而这个设计会带来2个外围问题:
- 元数据收缩导致master成为瓶颈。
- 受限于元数据的性能,无奈反对高并发的实时写入。
而咱们不这样设计做的起因是咱们心愿在将来可能反对高并发的工作,ADB PG大略花了2年多的工夫,将Greenplum的单点master架构拓展为multi-master,外围是为了解决高并发实时写入的问题,如果把元数据都保留在master上会带来如问题:
- master下面的元数据存储和拜访容易造成单点瓶颈
- 须要对ADB PG的执行层做大量的重构,实现在执行打算外面把元数据都带上,这会急剧的减少查问打算自身的带宽,这个对于高并发的小查问十分不敌对。
所以咱们改良了架构,将元数据扩散到segment,躲避并实现了:
- master的存储和读写都不会成为瓶颈
- 无需对执行层做重构,将元数据扩散缩小单次查问的带宽压力。
将segment上的元数据放到分布式kv上,解决扩缩容的元数据搬迁问题。
共享存储应用OSS的起因在于,随着单个用户业务数据一直增长,须要一个可继续倒退的存储计划,而OSS的低存储老本,高可用性和数据持久性是最好的抉择。
应用OSS的另外一个益处在于按需付费,用户不须要预制存储空间大小,存了多少数据,付多少钱,数据删除后即不再免费;ESSD云盘通常须要依据数据计算存储水位,无奈做到将存储资源真正的按需供给,而且无奈主动缩容,这些都违反了咱们云原生的设计理念。但同时OSS的劣势在于RT:
为了解决OSS的RT问题,咱们为计算节点配置了肯定比例的本地盘,用来做拜访减速。此外,咱们设计了一个高性能的行列混存,借鉴了ClickHouse mergetree存储的核心思想,以有序为外围,文件内相对有序,文件与文件大抵绝对有序,通过merge的异步操作,实现文件和并和排序,基于有序,咱们在文件内设计了3层的统计信息,并做了大量的IO裁剪优化。
上面咱们对每个技术点做进一步介绍。
三 关键技术
1 弹性伸缩
为了实现疾速的弹性伸缩,咱们的形式是数据在共享存储上hash bucket来组织,扩缩容后通过一致性hash把计算节点和bucket做从新映射,为了解决bucket与segment调配的平均性,并升高扩缩容后cache生效的影响,咱们对传统的一致性hash算法做了改良,反对扩缩容时的动静映射。
把数据依据hash bucket分成多个分片,按分片粒度在扩缩容的从新映射对象存储上的数据。如果扩容计算节点超过分片个数的时候,只能重散布数据。为了解决这个问题,咱们反对hash bucket能够后盾决裂和合并,防止从新散布数据。
上述是扩缩容时“数据”的重现映射,而形容数据文件可见性的元数据,因为保留在行表中,咱们还是应用了Greenplum的数据重散布策略,不同的是,为了减速元数据的重散布,咱们做了并行化散布等优化。
咱们以扩容为例进一步细化扩容的流程:
联合ECS资源池化,网卡并行加载和docker镜像预热等技术,16节点内端到端的耗时靠近1分钟。
2 分层存储
分层存储的实现如下:
如上图所示,咱们把存储的资源分成3层,包含内存、本地盘和共享存储。
内存:次要负责行存拜访减速,并负责文件统计信息的缓存;
本地盘:作为行存的长久化存储,并作为远端共享存储的本地加速器;
远端的共享存储:作为数据的长久化存储。
3 读写流程
写入流程如下:
- 用户写入数据通过数据攒批间接写入OSS,同时会在本地磁盘上记录一条元数据。这条元数据记录了,文件和数据表的对应关系。元数据应用PG的行存表实现,咱们通过file metadata表来保留这个信息。
- 更新或者删除的时候,咱们不须要间接批改OSS下面的数据,咱们通过标记删除来实现,标记删除的信息也是保留在本地行存表中,咱们通过visibility bitmap来存这个信息。标记删除会导致读的性能降落,咱们通过后盾merge来利用删除信息到文件,缩小删除带来的读性能影响。
咱们在写入的时候,是依照bucket对segment上的数据做了进一步划分,这里会带来小文件的问题。为了解决小文件问题,咱们做了上面几点优化:
- Group flush:一批写入的数据,能够通过group flush写到同一个OSS文件,咱们的OSS文件采纳了ORC格局,不同bucket写入到对应strip;
- 流水线异步并行:编码攒批,排序是典型的cpu密集型工作,上传到oss是典型的网络IO密集型工作,咱们会把这2种工作类型并行起来,在上传oss的工作作为异步工作执行,同时对下一批数据编码排序,放慢写入性能。
因为远端长久化存储提供了12个9的持久性,所以只有保留元数据的行存才有WAL日志和双副原本保障可靠性,数据自身写穿到共享存储,无需WAL日志和多正本,因为缩小了WAL日志和WAL日志的主备同步,又通过异步并行和攒批,在批量写入场景,咱们写入性能做到了根本与ECS弹性存储版本性能持平。
读取流程如下:
- 咱们通过读取file metadata表,失去须要扫描的OSS文件。
- 依据OSS文件去读取对应文件。
- 读到的文件通过元数据表visibility bitmap过滤掉曾经被删除的数据。
为了解决读OSS带来的提早,咱们也引入了DADI帮忙咱们实现缓存治理和封装了共享文件的拜访,读文件的时候,首先会判断是否本地有缓存,如果有则间接从本地磁盘读,没有才会去 OSS读,读到后会缓存在本地。写的时候会直写OSS,并回写本地磁盘,回写是一个异步操作。对于本地缓存数据的淘汰咱们也通过DADI来治理,他会依据LRU/LFU策略来主动淘汰冷数据。
因为事务是应用PG的行存实现,所以与ADB PG的事务齐全兼容,带来的问题是,咱们在扩缩容的时候须要从新散布这部分数据,咱们从新设计了这块数据的重散布机制,通过预分区,并行拷贝,点对点拷贝等技术,极大缩短了扩缩容工夫。
总结一下性能优化点:
- 通过本地行存表实现事务ACID,反对数据块级别的并发;
- 通过Batch和流水线并行化进步写入吞吐;
- 基于DADI实现内存、本地SSD多级缓存减速拜访。
4 可见性表
咱们在File Metadata中保留了共享存储文件相干的信息,它的构造如下:
Hash bucket:是为了在扩缩容的时候搬迁数据的时候,可能依照bucket来扫描,查问的时候,也是一个bucket跟着一个bucket;
Level:是merge tree的档次,0层代表实时写入的数据,这部分数据在合并的时候有更高的权重;
Physical file id:是文件对应的id,64字节是因为它不再与segment关联,不再只须要保障segment内table的唯一性,须要全局惟一;
Stripe id:是因为一个oss文件能够蕴含多个bucket 的文件,以stripe为单位,不便在segment一次写入的多个bucket合并到一个oss文件中。防止oss小文件,导致性能降落,和oss小文件爆炸;
Total count:是文件行数,这也是后盾合并的一个权重,越大合并的权重越低 。
Visibility bitmap记录了被删除的文件信息
Start_row对应32k对应一个delete bitmap。这个32000 4k,行存应用的32k的page能够保留7条记录。
Delete count是被删除的数量。
咱们无需拜访oss,能够间接失去须要merge的文件,防止拜访oss带来的提早,另外oss对于拜访的吞吐也有限额,防止频繁拜访导致触发oss的限流。
5 行列混存
Mergetree的构造如上图左侧所示,外围是通过后盾merge的形式,把小文件merge成有序的大文件,并且在merge的时候,咱们能够对数据重排,例如数据的有序个性做更多的优化,参考后续的有序感知优化。与leveldb的不同在于:
- 0层实时写入的会做合并,不同bucket的文件会合并成大文件,不同bucket会落到对应的stripe;
- Merge会跨层把合乎merge的文件做多路归并,文件内严格有序,然而文件间大抵有序,层数越高,文件越大,文件间的overlap越小。
每个文件咱们应用了行列混存的格局,右侧为行列混存的具体的存储格局,咱们是在ORC的根底上做了大量优化。
ORC文件:一个ORC文件中能够蕴含多个stripe,每一个stripe蕴含多个row group,每个row group蕴含固定条记录,这些记录依照列进行独立存储。
Postscript:包含文件的形容信息PostScript、文件meta信息(包含整个文件的统计信息,数据字典等)、所有stripe的信息和文件schema信息。
stripe:stripe是对行的切分,组行造成一个stripe,每次读取文件是以行组为单位的,保留了每一列的索引和数据。它由index data,row data和stripe footer组成。
File footer:保留stripe的地位、每一个列的在该stripe的统计信息以及所有的stream类型和地位。
Index data:保留了row group级别的统计信息。
Data stream:一个stream示意文件中一段无效的数据,包含索引和数据两类。
索引stream保留每一个row group的地位和统计信息,数据stream包含多种类型的数据,具体须要哪几种是由该列类型和编码方式决定,上面以integer和string 2种类型举例说明:
对于一个Integer字段,会同时应用一个比特流和整形流。比特流用于标识某个值是否为null,整形流用于保留该整形字段非空记录的整数值。
String类型字段,ORC writer在开始时会查看该字段值中不同的内容数占非空记录总数的百分比不超过0.8的话,就应用字典编码,字段值会保留在一个比特流,一个字节流及两个整形流中。比特流也是用于标识null值的,字节流用于存储字典值,一个整形流用于存储字典中每个词条的长度,另一个整形流用于记录字段值。如果不能用字典编码,ORC writer会晓得这个字段的反复值太少,用字典编码效率不高,ORC writer会应用一个字节流保留String字段的值,而后用一个整形流来保留每个字段的字节长度。
在ORC文件中保留了三个层级的统计信息,别离为文件级别、stripe级别和row group级别。而晋升存储性能的外围是缩小IO,咱们基于ORC的统计信息和索引实现各种下推,帮忙咱们实现IO裁剪。例如Projection下推,咱们只会扫描须要物化的列。Agg下推中,咱们会间接把须要的min,max,sum,unique从统计信息或者索引中读取即可返回,防止了对data stream的解压。对于predicate,咱们还反对把filter下推,通过统计信息间接做过滤,间接跳过不合乎的条件的stripe,咱们反对各种操作符,以及in/not in,以及表达式的等价转换。
此外咱们针对存储格局对性能还做了上面的优化:
- 零拷贝:为了把ORC的数据类型转换成PG数据类型,咱们对于定长类型的做值拷贝,变长类型间接转换成PG的datum做指针援用。
- Batch Scan:面向column采纳batch scan,代替逐行拜访而是先扫完一列,再扫下一列,这样对CPU cache更加敌对。
- 反对Seek read:不便过滤命中状况下的跳转。
6 本地缓存
DADI帮忙咱们实现2个能力,一个是高效的缓存治理,另外一个是对立存储拜访。在理解DADI之前,咱们能够首先看一下,DADI与开源解决方案从RT与throughput 2个维度做了比照测试:
从中看到,DADI相比开源解决方案alluxio在内存命中的场景RT上有数量级的晋升,在throughput上也有显著的劣势。在命中磁盘的场景,也有显著的性能劣势,在局部剖析场景下,咱们会频繁然而大量读取文件统计信息,这些统计信息咱们会缓存在本地,这个劣势带来整体性能的较大晋升。
DADI在缓存命中场景下的性能劣势,能够参考上面的架构:
DADI SDK:通过规范读写接口拜访存储,通过缓存是否命中,抉择短路读(short circuit read),还是IPC过程通信拜访Local DADI Service,或者拜访远端的DADI Service,对应分布式缓存服务,作为lib库嵌入ADB PG的读写过程;
Cache Instance:治理本地缓存,缓存文件形象成虚构块设施来拜访,数据在memory和本次磁盘的冷热以block为单位治理。
这里的外围设计在于:
- 短路读,间接读共享内存,防止通过IPC读;
- 缓存是否命中的数据结构,也是在共享内存外面。通过reference count,联合robust mutex来保障共享内存数据的多线程平安;
- 磁盘读,100us,+ 27us约等于磁盘读自身rt,IPC走shm通信,没有应用本地socket通信。
- 极低的资源应用。
内存:DADI Service应用的内存在100~200M,起因在于基于共享内存的IPC实现,hash表等数据结构,防止多过程架构下内存收缩, 精简的编码方式,1个内存页16k 对应 4byte的治理构造;
CPU:Local DADI Service在磁盘打满的时候单核CPU应用20%左右。CPU的应用在SDK这边,SDK与Local DADI Service通信很少。
此外为了更好的施展DADI在命中内存的劣势,咱们联合行列混存做了以下优化:
- 缓存优先级:反对统计信息高优先级,常驻内存,索引信息常驻本地磁盘。反对维度表数据高优先级缓存在本地。
- 细粒度缓存策略:为了防止大表冷数据拜访,导致本地热数据被全副替换,大表应用专有缓存区。
- 文件异步预取:依据查问状况,把解析的数据文件,事后读取到本地,这个过程不影响以后文件的读写,并且是异步的。
7 向量化执行
ADB PG云原生版本也同样反对向量化执行引擎,外围还是通过攒批的形式进步数据在CPU cache的命中率,通过codegen缩小函数调用次数,缩小简单计算指令跳转,通过SIMD指令减速计算,通过内存池治理,升高算子间的内存拷贝,更多信息能够参考【3】。
8 有序感知
数据的有序次要用在2个方面,基于有序的IO裁剪,另外一个是尽量减少计算过程中的排序,IO裁剪在行列混存以及有较多的探讨,这里次要探讨第二点,这里咱们做的次要工作有:
- 打消多余sorting操作。如果data自身有序,且满足排序要求,则不须要加sort操作。
- 最小化须要排序的列。例如心愿对{c1,c2,..cn}排序,如果有谓词c1=5,则order简化成{c2,..cn},防止排序多一个字段。
- order下推。在初始化阶段,降动向排序操作尽量下推。
咱们通过下列办法来生成sort scan的算子,查问SQL解析生成AST后,会依据一系列启发式规定做变换生成物理执行打算:
- 首先针对不同算子的有序性需要,例如(join/group by/distinct/order by),建设算子的interesting order(即这个算子冀望的有序输出)。
- 其次在sort scan的过程中所生成的interesting order,会尽可能下推到上层算子中(sort-ahead),以尽早满足order属性要求。
- 如果一个算子具备多个interesting order,会尝试将他们合并,这样一个排序就能够满足多个order属性的需要。
此外就是sort scan算子的实现,存储层面只能保障文件内严格有序,文件的大抵有序,咱们通过多路归并的算法来实现。
这里的问题在于sort scan的多路归并须要一条条读取数据,与向量化的batch scan与文件的批量读抵触,咱们通过CBO来选主最优的执行打算。
9 细粒度并行
ADB PG是MPP架构,可能充分发挥节点间并行计算能力,云原生版本因为对数据按bucket做了切分,能帮忙咱们在节点内实现更细粒度的并行,咱们以join为例阐明:
右边是没有节点内并行的join的执行打算,会起2个过程,一个做hash join的build,另外一个做probe,左边是节点内做了并行,咱们会依据segment所调配的bucket来做并行,例如上图每个bucket的数据都能够并行的去做计算,因为数据是依照bucket做的划分,join key是散布健的时候,节点内并行也能完满命中local join的优化。
四 性能后果
1 扩缩容性能
2 读写性能
为了测试性能,咱们应用了4*4C规格的实例,ADB PG的新版云原生与存储弹性版本做了性能比照测试。
写性能测试
测试表选用scale factor = 500的TPC-H lineitem表。通过同时执行不同并发数的copy命令,测得命令执行工夫,用总数据量除以命令执行工夫,失去吞吐量。
在单并发下新版本与存储弹性版本的性能差不多,次要在于资源都没有满;
在4并发下新版本的吞吐是存储弹性的2倍,起因在于应用lineitem表都定义了sort key,新版本在写入数据无需写WAL日志,另外攒批加上流水线并行相比弹性存储版本先写入,再merge,merge的时候也须要写额定的WAL有肯定劣势;
在8并发下新版本与4并发差不多,次要因为4C 4并发曾经把CPU用满,所以再晋升并发也没有晋升。
读性能测试
为了全面的测试读性能,咱们针对3种场景做了测试:
全内存:应用的是TPCH sf为10的数据集,会生成10G的测试数据集。
全本地磁盘缓存:应用的是TPCH sf为500的数据集,会生成500GB的测试数据集。
一半缓存,一半OSS:应用的是TPCH sf为2000的数据集,会生成2000GB的测试数据集。(本地磁盘缓存960GB)
测试后果如下(纵轴为RT单位ms)
全内存
全本地磁盘缓存
一半本地缓存,一半OSS
从上述测试后果来看:
- 云原生版本比照老的弹性存储版本均有1倍多的性能晋升,起因在于细粒度并行带来的减速成果;
- 对于TPCH这种计算密集型的作业,即便数据一半缓存,一半OSS性能也不错,sf 2000数据量是sf 500的4倍,rt减少到原来的2.8倍,次要起因在于4*4C规格的实例没有到OSS的带宽瓶颈,另外因为自身读取的预取等优化。
五 总结
AnalyticDB PostgreSQL新版云原生是充沛的将物理资源进行了池化,将存储和计算能力单元化进行调配,实现灵便的部署。这个个性为使用者提供极致的性价比,做到了算力的最优调配,同时升高用户应用的门槛,让用户专一于业务而无需将大量精力放在算力和存储的布局上,实现体验降级。
- 通过存储计算拆散,用户能够依据业务负载模型,轻松适配计算密集型或存储密集型,存储并按应用计费,防止存储计算一体僵化而造成的资源节约;
- 动静的适配业务负载波峰和波谷,云原生MPP架构计算侧应用了shared-nothing架构,反对秒级的弹性伸缩能力,而共享存储使得底层存储独立不受计算的影响。这升高了用户晚期的规格选型的门槛,预留了前期依据业务的动静调整灵活性;
- 在存储计算拆散根底上,提供了数据共享能力,这真正突破了物理机的边界,让云上的数据真正的流动了起来。例如数据的跨实例实时共享,可反对一存多读的应用模式,突破了传统数仓实例之间数据拜访须要先导入,再拜访的孤岛,简化操作,提高效率,降低成本。
六 后续打算
在上述存储拆散的架构上,咱们后续次要有3个大的方向:
- 能力补齐,这块次要是补齐以后版本的一些限度,例如Primary key,索引,物化视图,补齐写入的能力;
- 性能继续优化,次要优化缓存没有命中场景;
- 云原生架构继续降级,这块次要是在以后存储计算拆散架构下,进一步晋升用户体验;
在云原生降级咱们次要有2个重点方向:
- 存算拆散往Serverless再进一步,扩缩容无感。会进一步把元数据和状态也从计算节点剥离到服务层,把segment做成无状态的,这样的益处在于扩缩容能做到用户无感,另外一个益处在于segment无状态有利于进步零碎高可用能力,以后咱们还是通过主备模式提供高可用,当有节点故障的时候,主备切换缓存生效性能会急剧下降,segment无状态后咱们会间接将它提出集群,通过“缩容”的形式持续进步服务。
- 利用跨实例的数据共享。此外对于剖析型业务,数据规模大,以TB起步,传统数仓采纳烟囱式架构,数据冗余,数据同步代价高的问题,咱们心愿提供跨实例的数据共享能力,重构数仓架构。
原文链接
本文为阿里云原创内容,未经容许不得转载。