简介:本文介绍了云原生数据仓库产品 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 起步,传统数仓采纳烟囱式架构,数据冗余,数据同步代价高的问题,咱们心愿提供跨实例的数据共享能力,重构数仓架构。
原文链接
本文为阿里云原创内容,未经容许不得转载。