PolarDB-X 面向 HTAP 的混合执行器

作者:玄弟

早期数据库受限于硬件水平,IO、内存和CPU资源都非常昂贵,比如计算层的数据一多,内存容易爆掉;且只做单核计算,更谈不上用分布式去解决计算加速问题。可见在当时背景下执行器能够做的加速优化微乎其微。但今时不同往日,由于硬件水平的高速发展,分布式技术的日益成熟,执行器在大数据量的加速优化也越来越被重视,而我们的PolarDB-X执行器也就是在这个背景下不断迭代成长起来的。

目前PolarDB-X执行器在混合负载场景下,能确保TP和AP工作负载不相互影响,在能保证TP负载低延迟的前提下,AP依然可以保持一个不错的吞吐。在PolarDB-X企业版主实例32核128 GB下,在开启智能读写分离模式并存在只读实例情况下,TPC-C流量将会被路由至主实例,而TPC-H流量将会被路由至只读实例。OLTP负载与OLAP负载能够分别在主实例和只读实例上做到物理资源隔离,同时只读实例可以提供MPP能力,能够充分利用计算资源。

备注:

  • 开始通过100并发的纯TPC-C流量,吞吐可以达到23万tpmC;
  • 关闭智能读写分离,加入TPC-H流量混跑。TPC-C吞吐下降明显,TPC-H总耗时为840s;
  • 开启智能读写分离,TPC-C吞吐恢复到23万tpmC,TPC-H总耗时也可以恢复到最初的274s。

发展历程

PolarDB-X数据库历经10年的发展,而其执行器起起伏伏也沉底了10年。其最早可以追溯到执行器经典架构:Volcano 模型的计算架构。我们可以先回顾下传统数据库在执行器领域过去几十年的发展,同时也介绍下我们对技术选型的思考。

执行器模型:Pull vs. Push

早期的执行器多半都采样经典的Volcano 模型[1],如下图所示。执行计划中的每个算子都需要实现next函数,上游算子每一次调用,内部都会调用其输入的next函数, 递归调用,再层层计算返回结果。目前MySql/SQLServer/DB2/Oracle等早期数据库基本都采用这种计算模型。

Volcano模型简单灵活,且这种设计不用占用过多的内存。在当时内存是非常昂贵的,火山模型将更多的内存资源用于IO的缓存设计而没有优化CPU的执行效率,这在当时的硬件基础上是很自然的权衡。但是现在CPU的硬件环境与大数据场景下,性能表现却差强人意。究其原因。主要有如下几点:

  • 每次 next 都是一次虚函数调用过程是被动拉数据,编译器无法对虚函数进行inline优化,同时也带来分支预测的开销,且很容易预测失败,导致CPU流水线执行混乱。

  • Volcano Style的代码对数据的局部性并不友好,往往造成cache miss。我们知道CPU cache是存储着连续数据空间,每次可以对连续数据进行集中处理,将受益最大。而Volcano模型每次调用只处理一行。

鉴于火山模型每次处理一行一行数据,而next调用代价又比较高。所以批量处理模型在业界被提出,在算子间传递数据不再是一条一条记录,而是一批数据,算子每次执行的时候都会在内部攒一批数据,数据大小尽可能和CPU cache对齐,不仅大大提高了cache命中率,而且有效地减少了函数调用次数。

除此之外,业界也提出了Push计算模型,如上图所示。可以直观看到push执行模式相对于pull模型来说有效减少了指令的跳转,优化了CPU执行效率。通常做法就是用visitor方式遍历在优化器提供的执行计划,构建出push执行的物理计划。Push模式的算子实现大大提高了算子复杂度,比如merge join在原先pull模型中可以直接在算子内部控制两端输入的数据流。

编译执行 vs. 向量化执行

当我们提到编译执行的时候到底是在讲什么呢?通常我们是先编写代码,再编译,最后运行。而对于这里提到的编译执行,更多的是运行时期的代码生成生成技术。在执行过程中生成编译生成执行代码,避免过多的虚函数调用和解析执行,因为在执行之初我们是知道关系代数的schema信息。在具备Schema信息的情况下,事先生成好的代码,可以有效减少很多执行分支预测开销。这里直接参考自Impala论文[2]给出来的代码比对。

如上图右边的代码非常紧凑,有效消除了字段个数,字段大小,字段类型,对于数据量特别多的处理场景,可以大大减少CPU开销,提高性能,但实际仍有一个问题,如何生成右边的代码?业界常用的代码生成框架有ASM/LLVM IR等。但是每个表达式和算子都需要单独编译,如何减少编译开销?在这个基础上发展出来了Pipeline Compilation技术,就是将多个operator融合在一起编译,减少开销;此外还有Operator Cache技术,将事后编译好的代码cache起来,类似的查询语句可以直接复用编译好的代码,进一步减少编译开销时间[3]。

另一种加速思路是向量化执行,简单来说就是通过 batch 的方式均摊开销:假设每次通过 operator tree[4]生成一行结果的开销是C的话,经典模型的计算框架总开销就是CN,其中N为参与计算的总行数,如果把计算引擎每次生成一行数据的模型改为每次生成一批数据的话,因为每次调用的开销是相对恒定的,所以计算框架的总开销就可以减小到CN/M,其中M是每批数据的行数,这样每一行的开销就减小为原来的1/M,当 M 比较大时,计算框架的开销就不会成为系统瓶颈了。

这样说很多人会误解,这个是不是就是之前提到的批量处理模型呢?看似差不多,实际上要做到向量化执行,需要对算子和表达式做大量的改造,基于SIMD向量化指令操作的思想去重构整个表达式计算。向量化执行可以减少分支预测的开销,充分发挥SIMD指令并行计算的优势;还可以和列式存储有效结合在一起,减少数据额外转换的overhead。我们所知道ClickHouse就是采样了向量化(vectorized query execution)机制。

并行:SMP vs. MPP

随着多处理器结构硬件的出现,执行器开始往SMP架构发展,既单机并行计算,充分利用多核能力加速计算。在这样的系统中,所有的CPU共享全部资源,如总线,内存和I/O系统等,操作系统或管理数据库的副本只有一个,这种系统有一个最大的特点就是共享所有资源。但是单机并行执行器的扩展能力非常有限,在计算过程中也只能充分使用一台 SMP 服务器的资源,随着要处理的数据越来越多,这种有限扩展的劣势越来越明显。

但是 SMP 架构只能利用单个机器的计算能力(scale up),不能扩展到多台机器(scale out)。MPP 就是将计算分布到多个节点的集群中,集群中的内存、CPU等资源理论上可以无限扩展,使得资源不再轻易成为计算的瓶颈。但分式并行计算在充分发挥集群各台机器的CPU等能力的同时,会带来新的问题。调度上如何做到均衡调度,避免更多的网络传输,避免单个节点成为计算瓶颈?分布式计算过程中如何确保资源利用最大化?可见构建一个分布式并行计算比之前的系统复杂的多,要考虑的因素也非常多。

HTAP: Single System vs. Separate Systems

HTAP最早的概念是Gartner在2014年的一份报告中使用混合事务分析处理(Hybrid Transactional and Analytical Processing,HTAP)一词描述新型的应用程序框架,以打破OLTP和OLAP之间的隔阂,既可以应用于事务型数据库场景,亦可以应用于分析型数据库场景。实现实时业务决策。这种架构具有显而易见的优势:不但避免了繁琐且昂贵的ETL操作,而且可以更快地对最新数据进行分析。

在近期的顶会论文中也不乏出现HTAP架构的身影,推荐一篇SIGMOD的论文《Hybrid Transactional/Analytical Processing: A Survey》,高度总结和归纳了下HTAP架构的技术方案。实现HTAP技术主要两个分类:

Single System(一套系统解决 TP/AP)

  1. Decoupled Storage,比如SAP HANA、Hyper(行式内存+列式扩展)、SQLServer(列存索引)、MemSQL(行式内存+刷盘时转列存)等;
  2. Unified Storage,主要为SQL-on-Hadoop类,比如Impala with Kudu(SQL-On-hadoop类型,支持变更)

Separate Systems(两套系统分别应对 TP/AP)

  1. Decoupled Storage,比如Lambda架构、以及传统的在线+数仓组合, 技术上使用ETL进行数据同步;
  2. Unified Storage,比如基于Hbase构建TP写入一份数据,AP侧是Spark SQL-base使用同一份数据。

PolarDB-X 执行器架构

PolarDB-X 是由阿里巴巴自主研发的云原生分布式数据库,是一款基于云架构理念,并同时支持在线事务处理与在线分析处理 (Hybrid Transactional and Analytical Processing, HTAP)的融合型分布式数据库产品,因此在PolarDB-X优化器、执行器上针对HTAP混合负载都有对应的设计体现。

HTAP架构

PolarDB-X主要面向以OLTP事务型为主的分布式数据库,同时支持OLAP的在线分析的混合负载能力,我们期望以Single System的形态,基于Decoupled Storage存储结构支持HTAP形态。

PolarDB-X HTAP执行器,主要有几个特征:

  1. PolarDB-X 提供一个HTAP的endpoint(可以理解为一个vip),业务所有的TP和AP流量只需要通过这个endpoint访问即可;

  2. PolarDB-X 提供RW(读写)、RO(只读)节点的概念,基于Decoupled Compute/Storage的思路提供HTAP混合负载的能力;

  3. PolarDB-X 提供面向HTAP的优化器、分布式调度能力,可在Decoupled模式下将TP和AP请求做分离,利用物理隔离满足OLTP的稳定性,另外结合分布式事务多副本强一致的能力,实现OLAP的数据一致性。可参考文档:PolarDB-X 强一致分布式事务原理

具体HTAP的工作原理和流程:

总结一下优势:

  1. 强隔离、强一致的混合负载,OLTP请求不会因为日志复制而产生延迟
  2. 引入分布式并行计算,OLAP查询可满足线性扩展能力

MPP 并行执行器

PolarDB-X在混合执行器设计上,借助于PolarDB-X 面向 HTAP 的 CBO 优化器,可以实现TP和AP的识别和路由,可以在一个实例里同时运行OLTP和OLAP业务,保证AP的查询不影响TP流量的稳定性。同时在OLAP能力上,引入Push模型、Chunk执行、向量化、MPP并行计算等特性,可以满足TPC-H/TPC-DS等复杂查询的诉求,后续会专门开一篇文章介绍下PolarDB-X的MPP并行计算的代码设计和理念。

以 TPC-H Q9为例:

关键特性对比

备注:

  • MySQL with Analytics Engine,是MySQL在20年12月2号在其官网推出分析引擎,极大增强MySQL自身的分析能力。

  • OLTP场景会比较注重基于索引的优化,比如基于Index的point select 以及Index Nested Loop Join,在OLAP场景下会比较注重并行能力和执行效率的优化,比如SMP/MPP、vectorized/codegen等,最后针对HTAP场景,更多会注重资源隔离、数据一致性、查询入口是一个还是多个等。

我们还在路上

软件技术的发展总是和硬件技术的发展紧密结合在一起的,迎合硬件技术的发展而改变软件技术栈的相应策略能够使得设计的系统获得更大的受益。这些年PolarDB-X执行器经过多次版本的迭代,早已不再是单机版本的内存执行器了,我们既是一款和分布式事务相耦合的执行器,也可以是一款可以处理海量的数据处理,具备了落盘和MPP的计算能力的执行器。这一切都得益于我们从一开始就把自己定位成了一款HTAP引擎:

  • 是一款可以同时满足事务处理和工作负载分析的业务需求的执行器

  • 是一款分布式并行执行器,具备计算水平扩展的能力

  • 高度统一的执行器代码,真正做到了TP和AP执行器一体化

  • 支持大规模并行处理和复杂查询优化

  • 基于工作负载做资源管理,支持计算资源相互弹缩

但我们知道这些还远远不够,技术永无止境。现在的我们仍会时刻关注业界最新的技术动态,我们会探索一切可能的东西,充分融入到我们PolarDB-X的系统里头去,不断加强我们HTAP数据库的能力,这个过程我们也期待你的加入!

参考文档

  1. Graefe G..Volcano-an extensible and parallel query evaluation system[J].Knowledge & Data Engineering, IEEE Transactions on,1994,6

  2. S. Wanderman-Milne and N. Li, “Runtime Code Generation in Cloudera Impala,” IEEE Data Eng. Bull., vol. 37, no. 1, pp. 31–37, 2014

  3. 向量化与编译执行浅析

  4. A. Kemper, P. Boncz, T. Kersten, V. Leis, A. Pavlo, and T. Neumann, “Everything you always wanted to know about compiled and vectorized queries but were afraid to ask,” Proc. VLDB Endow., vol. 11, no. 13, pp. 2209–2222, 2018.

你可能感兴趣的:(PolarDB-X 面向 HTAP 的混合执行器)