瑶池数据库大讲堂|PolarDB HTAP:为在线业务插上实时分析的翅膀
内容介绍
一、MySQL在线业务的分析需求与目前的解决方案
二、PolarDB HTAP场景的解决方案
三、PolarDB HTAP针对分析型负载的优化
四、近期性能及客户体验的改进
今天分享在PolarDB HTAP结构上做的实时分析的功能,主要为两方面,一方面是技术方面的介绍,包括存储和执行各个方面的架构和优化等。另外一方面是最近一年在产品的应用线和性能上做出的努力,希望能够帮助客户更好的使用PolarDB HTAP实时分析业务。
主要分为四块,首先是客户在线业务的分析需求,介绍客户分析需求,以及客户他们为需求所做的努力。第二是相较于已有解决方案, PolarDB是怎样通过架构去优化客户的解决方案。第三点是针对分析型负载上做的比较细节的优化。最后是近期性能及客户体验的改进。
一、MySQL在线业务的分析需求与目前的解决方案
介绍一下业务中数据库业务中负载的两种分类,一种是在线业务的简单修改与查询。又称TP查询,如银行系统的转账,店铺库存的增减等等。这类负载的特点是发生的次数非常频繁,但是每次仅仅设计很少的数据表。如对于转账,只是修改两方,同时负载的pattern固定性也很强,可以通过二级索引去很好的处理这些负载。
另一方面是分析型查询,通常是对历史数据进行分析,把它用作商业决策。查询的特点是扫描数据量大,因为数据科学家总会提出不同的观点,查询本身比较复杂,查询的维度也比较多。如对数据进行分析,简单使用二级索引不能满足这类需求。为了处理需求,通常会使用数据仓库去处理这些复杂的查询,早期采用spark,现在其它的相对更受欢迎。这些系统的查询的性能非常出色,能够很好的处理复杂的查询,同时也支持批量导入数据。但存在问题,首先不支持事务,无法很好的去应对技术上的复杂,架构通常会选择类似于ETL的架构,即通过TP的数据库通过数据传输,如ETS把数据从TP库导入AP库,就可以在HTAP进行查询。但是由于使用的ETL为异步的复制,所以放弃了数据库和数仓之间的数据一致性,同时引入了多个组件,无形中也增加整个架构的不稳定性。只要有一个组件出了问题,链路可能就不能正常工作。同时,当用户需要一个数据仓库,还需要一个数据传输的服务,组件需要付出额外的成本。但架构一定是合理的,首先并不是所有用户都存在一致性方面的诉求,但是更多用户可能只仅仅不能在数据库层面直接解决问题,因为用户一般他是属于哪个数据会用到,其实数据库是不应该严格的区分成GP和AP的数据库,对用户只是一条又一条不一样的SQL。所以处理这些负载的需求是一直存在的,只是用户受限于把MySQL原先的性能,它面对现实妥协,就像发明汽车一样,如果你发明汽车之前去做市场调研的话,答案一定是消费者需要得到一定更快的马车,但消费者只是想跑得更快。
二、PolarDB HTAP场景的解决方案
介绍HTAP场景上的解决方案。对于PolarDB的工作负载问题,引入PolarDB-IMCI,即索引概念。在此帮助下,用户能够通过一个数据库同时处理在线业务和实时分析的负载。为了实现,团队针对复杂查询的场景,进行彻底优化计算引擎。用户的复杂查询跑的时候更快。另一方面存储也能保证行列一致性。这一方话就能帮助用户抛开同步延迟,摆脱ETL架构中常见的困扰。最后通过良好的兼容性,当用户不再需要对多个数据库有深度的了解,比如既要多买CPU,还要懂使用这些AP数据库。
介绍PolarDB-IMCI结合之后的整体架构。
首先看图。IMCI-MySQL在前端共用大量的基础设施,如词法的解析器,逻辑执行计划这些东西。如行情优化器,这些也为兼容性提供最基本的保证,因为很多东西是复用了之后,后期再去,相当于在适配到数据上面,再经过行情计划之后,查询会根据被计算出的代价分别被调入到行存和列存执行。这是为了将有限的CPU放在真正复杂的产权上,因为行存查询都非常简单,所以行存很适合这样轻量级的小查询,而列存因为会有各种各样的变形,在操作还有优化的过程比较复杂,所以启动的代价会稍微大一点,这是为了通过路由,是为了更好的去支持并发以及充分的CPU利用。最后是底层的存储,通过polarDB的物理复制去做到了事物的强烈一致性以及一写读的扩展能力。在使用中就不必关注ETL架构会带来的一系列问题。
该图是用户的一个使用场景,通过在同一张表上同时支持行索引和列索引,存储引擎内部维护索引一致性。用户其实就会得到一种自己在使用更高级的MySQL的感受。相较以前的复杂的架构,目前架构这边展示的样子与MySQL相同,补充点得到内核里处理掉了用户,不需要操心别的事情。
这是一个IMCI的接口。IMCI存储以一个特别的二级缩影的形式去存在。类似于图中在C2、C3、C4列上建立了一个内存索引,就是一个二级索引。如果你要列式索引加速,只需要GDL增加一个二级索引,就可以非常简单,易用,同时也兼容MySQL的语法。可原地直接升级版本,用户不需要迁移数据,列存索引数据是通过指定的四五节物理复制,去保证数据一致的用户不需要特别关注。
讲述细节。首先内存的存储是完全追加写入的,一个插入会首先去除。在INNO DB处理首先更新主索引,更新二级索引,这些更新都结束之后,进入到更新列索引的部分,这时一行数据就会按列拆分写入RowGroup的缓存中,update被拆解为事物的delete 和insert。每个RowGroup都类似于原信息的东西,可以看到下面图有PACK META,这部分首先保存事物的版本号,删除位图,同时还保留了当前MIN MAX。在查询过滤时,可以通过MIN MAX去辅助加速过滤效果。如要查大于500的数据,但是ROW GROUP最大其实是300,也就知道这一部分行中无想要的。同时基于查询性能的考虑,如果一个row group要删除的行过多,素材现在会对数据做重整,因为数据被拆解为delete和insert,delete是通过修改删除的位图去实现的,如果不重整,表会逐渐失败,做成品之后,旧的部分会被空间会被释放,也能提升查询所有的扫描的效率,每个入库的一个行记录达到阈值之后会被冻结,被顺序的写在字牌上,可以看到写入的过程其实非常轻量级的,如要把数据写进内存,后续的操作都是异步进行的。
另外一个为兼容性以及查询执行的路由策略,上文提及是根据行业优化器进行优化之后的代价去决定入到什么地方。但是现在有三种执行策略,一种是最基本的,就是官方在把制度的执行路径,异步线程就去跑查询,另外一个是polarDB MySQL研发的并行执行策略,相当于同时使用多核,但是依然使用行存的基础设施,如基本的执行结构等等,如果cost更大,被录入到列存,在列存进行执行。如果查询是基于主件和二级索引检查,则有行存处理,提供很好的并发能力。如果是处于大量数据的连接和巨额操作,就可以通过并行查询或是列存索引去进行优化,为这些查询提供加速。在兼容性方面,因为复用前端的东西,前端在语法兼容的基础上,也支持绝大部分的功能,语法是100%兼容的,大部分功能也是兼容的。通过大量的测试,保证了这些知识的功能行列行为的一致性。
三、PolarDB HTAP针对分析型负载的优化
介绍本项目为分型查询做的优化。第一个是向量化执行,首先对比一下另外两种不同的策略,他们也能够在数据库系统里经常看到。首先是每次处理一行数据tuple-at-a-time经营方式。发出的特点是每次处理一行数据。如Pipeline的火山模型,优点为内存占用少,因为同一个快慢中,同时只存在一行数据,但是问题在于处理每一行的时候,CPU的overhead会比较高,因为想做一个A+B,可能更多的时间用在了分支判断,函数调用以及对数据的解析等等。MySQL只有10%的CPU时间,真正的花费到了希望执行的部分,剩下的很多地方都在处理虚函数调用这些东西,这就导致ipc很低,每一条指令需要很多个cycle才能去执行。但是适合高并发的简单查询,因为首先在高密发的情况下,内存是有限的,每一套发行要尽可能的减少内存占用。同时,因为简单查询的处理的数据很少,所以overhead还可以接受。如处理两行和处理10行,其实在这简单方向区别是不大的,可能只是从一毫秒变成了三毫秒,往往用户都是可以接受的。
另一个方式,其实跟之前的方式是一个是属于两个极端的,是每次处理一列数据,即column-at-a-time,该执行策略曾经被Monet DB之前的版本使用过,每次运作时中会存一整列的数据,测评策略就完全解决了上一个执行策略中的缺点,不会因为一行一行的处理带来很大的overhead,如本来overhead每一行都有一次,现在可能每个列就只有一次了,如分支预测函数调用的很多开销就被很大程度上的均摊了。同时也能够利用现代处理器和编辑的特性,如处理器有动态分支预测,这样可以减少分支预测失败概率,同时编辑器能很好利用CPU,但问题为内存开销大。因为内存中驻留了整个列的中间,结果可能有数GB大小,容易耗尽内存,仅适合低并发的全内存数据库,两种策略互补。
最后一种为前两种方式的折中,它能够解决前两种执行方式的问题。首先查询选择不同,既不是保留一行,也不是保留整列。可能只保留一整列中的一部分,如2000行这样的大小,首先这样也能很好的利用CPU,因为开销均摊是一个边际效应,如均摊到1/2000和均摊到1/1万。可能这时候查询上的效率区别就不是很大的。收益在慢慢变少的,所以既能继承column-at-a-time的优点,也能通过处理器和编辑的特性去对代码根据预测性能做优化,内存开销略高于TUPLE-at-a-time,但低于column-at-a-time。因为只保留了一小部分,而不是整个,在保证查询效率的前提下也有效的减少了内存的占用。第三点通过调整,保留的行数的多少,可以让数据能够放进CPU cache,在流转,加速查询执行。
该图为CPU的内存层次结构,向量化处理能够通过恰当的大小,可以让流水线中传递的数据驻留在L2cache中。相比于其它,它们的向量化处理能够有效的提高运行效率,开销变得很少。同时又能提高数据访问的效率,数据要去访问内存,考虑到有硬件预取,访问内存的顺序访问类型的时间用十大秒左右,但如果是向量化处理,数据驻留在L cache,访谈时间可能只有4ns,是比上面要快速两倍。
另一个是在向量化之后,因为数据已经是一半是执行的,所以可以通过单指令多数据的指令集去优化的执行效率。SIMD可以通过一条指令,同时对多行进行操作,相当于用一条向量化指令代替了多长的标量指令,以上图为例,前面是4个乘法,后者讲CD,用一条乘法就可以就可以搞定。同时查每条指令的就是执行时间是差不多的,一般称它叫做核内定型,在单核中类似于起到多核并行的效果。
要让程序使用SIMD有两种方式,一种是让编写的代码循环,让编写能够识别到,如你在做一个加法或者做一个编写能够通过向量去优化循环的代码,如直接通过调用SIMD的内置函数去做优化。前者优点代码便于移植,如从inter CPU移植到err不会有问题。因为代码都是很普通的指令,问题是编辑的识别能力是有限的,很可能不会对代码进行SIMD优化。而后者恰恰相反,可以保证代码SIMD优化。但当移植到别的机器上的时候,可能要做出其他的额外适配才可以,但优点就是性能会比电气优化的好很多。以图为例,前者是一个STD代码,在此只是简单的循环,对于数组做加法,这样的循环中优化其实可以识别出在做一个循环的加法的,并且它会用SIMD进行优化,但是看后者是一个稍微复杂例子,同时也是数据库中常见的逻辑。就当这对数据进行相加之后,需要判断数据加到时候,是不是溢出了。如果溢出需要返回一个错误,对于这种判断的类型的编辑还不够聪明,所以使用了SIMD函数进行编程,希望最大化带来SIMD性能优化。另一方面存在难以移植问题,如环境比较复杂,既有老的英特尔CPU,又有新的英特尔CPU,如增加服务器等,即一致性问题。
但云上的服务器统一环境很好的解决这一点,不需要考虑CPU需要使用什么样的指定。同时云上的机器也都统一的能够使用这款更宽的AVX-512指令,跟类似ABS2不一样,它的宽度是其两倍。ABS2一条指令只能处理256BT的数据,但是AVX-512可以处理512BT的数据,指令能同时处理的数据更宽,同时处理更多的数据。指令效率会更高,指定有特点是数据的宽度会影响吞吐,因为曾经处理的bit是固定,对于INT32、INT64,一条指令能处理的数字个数就会有区别。但是即使是最宽的六四的INT,其版本仍然有4~6倍的性能提升,可在图中发现,即使加法,SIMD指定也有530M 的INT64/S。标量加法仅有80M INT64/S,,如果你用512÷64,会等于8,但并不是一个正正好好的8倍。因为处理溢出这种类似一个逻辑会有些额外的开销。这是因为不要让处理器加法溢出了,会设置相关系数,需要额外的判断处理事情。
下面介绍多核并行处理。
是数据会被切分成若干个batch在pipeline执行。关键的算子如HashJoin、Groupby,它是算子内部感知并行的。内部感知并行,会尽量的减少同步的操作,不同的数据会被不同的pipeline拿走。如有些算子拿到的数据,他处理的比较快,有些算子拿到数据处理的比较慢,通过work stealing减少算子之间的数据倾斜,保证查询中数据结构竞争减少,不需要进行加速。另外每个算子执行的时间是几乎均衡的,不存在数据倾斜,导致类似一方有难,八方围观的情况发生。
通过这些各方面的优化,IMCI的用户提供了有竞争力的实时分析能力。首先是单核,通过现代化与先进的优化单核性能相比,行存拥有2~10倍不等的提升。另一方面,IMCI的设计在大数据下执行性能几乎随着CPU核数线性的扩展,用户不用担心之前手机CPU出现问题时核数多但无法及时解决。
四、近期性能及客户体验的改进
最后讲一下近一年对性能以及用户体验相关的改进。性能是用户体验很重要的一个部分,但不是全部。用户体验做的好不好,不仅由图中的肉饼决定。其他的边界边角的功能也同样重要。近一年优化性能的目的为两类,当用户足够的了解系统,用户就可以通过恰当的配置去获得更高的性能,这是性能方面的优化。另一是当用户对系统知道的比较少,系统也应该尽可能的保证它的好用,即健壮性和易用性。
近一年主要完成上图中的事情。
首先为索引数据的排序。列索引按照pack组织数,每个pack会存储当前pack的数据特征即系数索引,如图中pack Meta中的min max辅助过滤,用于减少io,加快查询效率。
举例,如select count from t1 C2小于200,要找所有小于200,在查询时首先不会去直接扫数据,先通过pack meta 的min max了解其所需要的,如图中最大值就是200,则C2小于200,一定是在整行都满足条件,就不需要去扫C2列,可以把数据直接拿出来,送到上面系统继续新的操作。
但是如果如每个pack meta 的min max都没有说落在200的左边或者右边,如最小值是0,最大值是500,c2虽然小于200,但在此min max无法得到准确结果。只能每个pack进行扫描,如黄色部分,如果不是可疑的,有需要的数据会需要去扫这四个pack。
如果能够对列进行排序,即提供列索引,数据排序的功能,可以通过设立排序键,数据通过列重新进行排序,进行重整,所有过滤数据会更加有效。以TC2为例,C2就会按从小到大的大小去排序,可观察数据min max非常有序。第一个是0~200,第二是200~400,当查询Sort KEY(c2)小于200,先扫第一个pack,就知道pack的数据都是需要的,再往后进行扫描如400~600,只要pack meta,就知道pack是不需要的,
对比排序之前的结果需要扫整4个pack,提高了效率。
第二点是行列索引的混合查询。即IMCI擅长进行包含大量扫描的复杂查询。但当扫描巨大,查询需要大量的IO,IO很大程度上拖慢的查询的执行效率。另外,由于列存索引的存储架构,因为每个列存在不同的地方,如果要访问大宽表大量列的这样一个明细查询,相较于行存,访问一行数据需要多次io,因为数据按列存储先去做一次IO,达到C1再去做一次,到C2。另一方面Innodb二级索引,可以通过索引去查询,这样也能减少数据的扫描操作。因此开发了行列索引混合查询的功能,首先,通过行存索引将明细查询的多次io合并为行存上的一次IO,查询的效率会增加。另一个是通过行存索引可以进行过滤和作用,部分的场景能够有效的减少代价,综合使用效果最好,它会同时超过单独使用行存。当查询非常复杂,使用二级索引会更好。有一部分就想要扫描很多数据,列存索引就更好。对这样的查询,混合的执行是最佳的。
通过主键索引去将多采用合并的Excel的例子。采用的是on time的数据集,数据为110列,在冷启动条件下进行查询。即SELECT FROM ontime ORDER BY ArrTime LIMT 1000,如果是在行情上的话其中需要232秒。在列存下的话,只需要2.56秒。如果是行列混合,对1000行数据的数据的提取,整个产品的速度会提高到0.33秒。使用行列混合执行比单独使用行或者列存效果更好。
第三个是IMCI行列自适应分流。Polar DB是根据查询的预估代价去选择是否通过列式索引,去执行查询的。通常情况预估的代价选择都是正确,但是因为优化器总是在预估,误差也会存在。存在用户觉得跑得很慢,并没有走到列存这样的问题。为了处理,团队开发IMCI上的行业自适应分流功能,前端的逻辑一样,就是根据查询或者大家去进行分流,代价很小就选行存执行,存在不同是当同时追踪查询的实际执行代价。如果如在查询COS只有100,觉得他可能会在500毫秒内就能完成了,实际查询执行代价与误差大,会把查询重新落入到列存去执行,使用户避免通过Hint方式手动进行分流。
可发现刚才之前的架构,分流是简单的通过代价,小于5万,小于50万,大于50万,如果大于50万就分配到列存,如果小于5万的话就分流到最普通的MAX官方的执行路径。
目前追加了两条路径。在追踪过程会实际的追踪查询的实际代价。如果代价大于50万以及更高,会再把它录入到列存去执行,实际代价减少。
最后是相关工具。存在用户提出异议,查询计划时不知道MySQL只能观察到查询的耗时长,其余无法进行详细了解。为了解决此类问题,开发了IMCI SQL Profiling功能,可以观察到查询中算子处理的实际的数据量与耗时,可查询返回多少数据,以及查询所用CPU时间,结合explain展示的查询计划,更详细的解读目前在官网上已经发布,用户基于这些具有的初步分析慢SQL的能力,用户就能够结合性能分析与业务上的引进的条件,如租户ID和ID是隐私的一一对应的关系。两个表用ID做join,但是用租户ID做过滤,会隐私的认为用户会知道这两个表上对应的数据ID相同,用户就可以把租户ID的条件加上,但因为数据库不会做这种引擎的假设,如果用户有他自己业务上特有的特点,用户就可以依靠这些进行优化MySQL。
以上就是今天的全部内容,谢谢大家。