ByteHouse云数仓版是字节跳动数据平台团队在复用开源 ClickHouse runtime 的根底上,基于云原生架构重构设计,并新增和优化了大量性能。在字节外部,ByteHouse被宽泛用于各类实时剖析畛域,最大的一个集群规模大于2400节点,治理的总数据量超过700PB。本分享将介绍ByteHouse云原生版的整体架构,并重点介绍ByteHouse在查问上的优化(如优化器、MPP执行模式、调度优化等)和对MySQL生态的欠缺(基于社区MaterializedMySQL性能),最初结合实际利用案例总结优化的成果。
在2023云数据库技术沙龙 “MySQL x ClickHouse” 专场上,火山引擎ByteHouse的研发工程师游致远,为大家分享一下《ByteHouse云数仓版查问优化和MySQL生态欠缺》的一些工作。
本文内容依据演讲录音以及PPT整顿而成。
火山引擎ByteHouse的研发工程师游致远
游致远,火山引擎ByteHouse资深研发工程师,负责ByteHouse云数仓版引擎计算模块。之前先后就任于网易、菜鸟团体、蚂蚁团体,有多年大数据计算引擎、分布式系统相干研发经验。
ByteHouse云数仓版查问优化和MySQL生态欠缺
明天我次要分享的内容纲要,分为上面这四个局部。首先次要是跟大家讲一下ByteHouse云数仓版的背景和整体架构、而后重点讲下查问引擎上做的优化和欠缺 MySQL 生态的一些工作,最初是总结。
内容纲要
Clickhouse 是基于 shared nothing 架构,这种架构也带来了比拟极致的性能。字节跳动的话,从2018年就开始在线上 应用 Clickhouse,而后到当初曾经是十分大的机器量和数据量。然而 Clickhouse 的shared nothing 架构,也给咱们带来了很大的艰难,次要是数据的扩缩容比拟难,包含存储和计算资源的绑定,导致咱们想做一些弹性的伸缩也比拟难。而后读写不拆散带来的影响,以及在公共集群上中小业务的查问的影响。
为了彻底解决这个问题,而后咱们在2020年的时候,开始做一个基于云原生架构的Clickhouse,过后外部的代号叫CNCH,当初在火山上叫ByteHouse云数仓版。而后当初CNSH在外部也是有十分大的应用规模,到2022年的时候,咱们决定把这个回馈给社区,过后跟 Clickhouse 社区也进行了一些探讨,起初感觉架构差别太大,而后就独自以ByConity我的项目开源了,在往年1月份曾经在GitHub上开源了。欢送大家去关注和参加一下。
Clickhouse基于shared nothing架构
下图就是 ByteHouse云数仓版的整体架构,这是比拟经典的架构。服务层负责就是数据,事务查问打算的协调,资源的治理。两头这层是可伸缩的计算组,咱们叫做virtual warehouse(VW),也叫虚构数仓,业务是能够按virtual warehouse进行隔离,互相不会影响,能够随便的扩缩容,是一个无状态的计算资源。最上面是数据存储,咱们是形象了虚构的文件层,能够反对HDFS,以及还有对象存储S3等。当然在理论查问的时候,就是咱们也会做一些热数据的local cache.
ByteHouse云数仓版的整体架构
上面重点来讲咱们在查问引擎的优化。咱们晓得ClickHouse的单机执行十分强,而后这个是2021年的ClickHouse的单机执行逻辑,非常简单的count(*)的聚合运算。ClickHouse 首先会生成一个逻辑打算,叫QueryPlan。这里能够通过 EXPLAIN 看到每一步,就query plan step,就是读表,而后做聚合。
ClickHouse的单机执行
而后再通过 QueryPlan 会生成一个 QueryPipeline。这个过程中能够看到,query plan step被翻译成了QueryPipeline外面的一步,叫做processor,或者叫做物理算子。
QueryPlan 会生成一个 QueryPipeline
ClickHouse的单机模型其实是十分的强的,而后整体Pipeline驱动模式能够参考上面这个图,这里就不再具体开展。
ClickHouse的单机模型
接下来咱们就看下另外一个场景,分布式执行。这是一个分布式表,而后有三个分片。做一个简略的count,在ClickHouse这块的话,就是把它改写成三个本地执行的子查问,而后别离计算,生成两头的Partial merge result,最初在coordinator节点上进行聚合,最初生成一个残缺的后果返回给用户。
ClickHouse分布式执行
这个模型特点就是十分的简略,而后实现起来也是十分高效,然而在理论业务中也发现一些毛病。首先对于两阶段的话,第二个阶段的计算如果比较复杂,Coordinator 的计算压力会十分的大,很容易呈现瓶颈。在聚合运算的时候,比方count distinct的常常会呈现OOM或者算不进去,它整个架构是没有Shuffle的。如果有Hash Join,右表的大小不能放到一个单机的内存外面,基本上就是跑不进去。整个打算层的话,下发ast或者sql的形式,表达能力是十分无限的,咱们之前是想基于这个做一些简单优化,也是不太好做,灵便度也比拟低。最初的它只有一个基于规定的优化,像一些比拟重要的join reorder的排序也是没法做。
ClickHouse模型的优缺点
基于下面提到的问题,咱们是基本上重写了分布式执行的查问引擎。次要做了两点,一个是就是反对多阶段执行,这也是大部分支流的MPP数据库,以及一些数仓产品的做法。第二个咱们自研的整个优化器。上面是一个比拟残缺的执行图。能够看到,相比于方才二阶段执行,一个查问过去之后,他的第二阶段就是Final agg能够在两个节点上了。TableScan做完之后,通过肯定的规定进行shuffle。这个是通过exchange。而后最初的后果再会集到Coordinator。当然这里还有ByteHouse云数仓的一些其余组件,这里不再细讲。
ByteHouse分布式执行的查问引擎
为了反对多阶段的执行模型,咱们就引入了PlanSegment。简略说就是每一个worker上的一段逻辑的执行打算。从实现上来讲,它其实就是单机打算的QueryPlan,再加上输入输出的一些形容。而后这边就是PlanSegment的介绍,输出的PlanSegment和输入要到输入到哪个PlanSegment。
多阶段的执行模型概念PlanSegment
理解PlanSegment之后,能够就会问这个PlanSegment是从哪里来的。其实方才介绍了,就是通过优化器进行打算生成和优化得来的。整体的一个流程就是从Parser把一个SQL变成了一个AST(形象语法树),而后在优化器这个模块外面,在interpreter外面变成了一个PlanSegmentTree,切分成一组PlanSegment再下发给各个worker。
PlanSegment整体流程
优化器,次要就是查问打算的变换。分为rule based optimizer和cost based optimizer,就是基于规定和基于代价。基于规定的话,咱们是实现了一个种基于visitor的一个改写框架,次要做一些全局的改写,反对从上到下,从下到上的形式,包含一些condition的下推,还有SQL指纹,这种像须要正则化SQL的。咱们还反对基于部分的pattern-match改写,例如。发现两个Filter是相连的,那就会到合并到一起,Projection也是相似的做法。
优化器RBO
CBO,上面是一个通用的CBO的框架。当一个查问打算过去的时候,咱们会通过optimizer Task的规定,和Property来一直的裁减这个grouping。两头这个是memo,记录等价的QueryPlan。而后把所有的QueryPlan生成之后,依据计算的代价,最初抉择代价比拟低的作为输入。当然在具体实现的时候,其实是有很多思考,会包含生成的时候怎么升高等价plan的数量,以及怎么在生成的同时抉择分布式打算最优计划。
优化器CBO
当优化器生成了PlanSegment的时候,就波及到该如何下发。上面就是咱们的调度器模块。当查问生成完一组PlanSegment之后,咱们能够依据调度的类型,当初咱们次要是MPP的多阶段执行。就会把它生成一个子图一次下发,前面也会思考其余的一些调度形式,依据工作类型,包含相似于Spark的BSP,或者是分阶段调度。生成完这个一个子图的调度之后,马上就要抉择PlanSegment到哪些worker执行?
这里的话。就是方才讲service层,congresource manager拿各个worker层的负载信息,调度source的话,咱们是次要思考缓存的亲和度;而后调度计算plansegment的话是worker能够纯无状态,咱们是次要思考负载,就是尽量保障负载平衡来进行调度。这里也是尽量避开一些慢节点,以及一些曾经死掉的节点。当然咱们也在做其余的调度的形式,就是一些资源的预估和估算。这个具体解决问题能够前面再讲。咱们生成完PlanSegment,而后发给worker之后,它的执行就是方才讲的clickhouse的单机执行了。
调度器模块
刚刚提到一点,就是数据的就是的传入和传出,这个是依赖于Exchange模块。Exchange就是数据在PlanSegment的实例之间进行数据交换的逻辑概念,从具体实现上的话,咱们是把它分的数据传输层以及算子层。
Exchange模块
数据传输层的话,其次要是基于定义Receiver/ Sender的接口,而后同过程传输基于队列,跨过程是基于基于BRPC Stream,反对保序、状态码传输、压缩、连接池复用。连接池复用、简略来说,就是把大集群上的两个节点之间的只建设一个连贯,所有的查问都在这个连贯上通信,当然咱们是连接池,所以实际上是两个节点之间是固定数量的一个连贯,这样会比单连贯的稳固好更高。
Exchange数据传输层
算子层的话,咱们是反对了四种场景。一个是一对多的Broadcast。而后多对多的Repartition,以及是多对一的Gather,个别在本过程之间的Round-Robin。这外面也做了一些优化,包含Broadcast怎么样防止反复的序列化,而后Repartition怎么晋升性能,以及sink怎么攒批。在大集群下,怎么通过一个ExchangeSouce读取多个receiver的数据,来升高线程数。
算子层
这里是比拟高阶的一些优化点,第一,RuntimeFilter 就是在执行期间生成的动静filter,比方这是两张表的一个等值join。咱们能够在右表构建哈希table的时候,会生成一个bloom filter(或者其余类型的filter)。而后把各个worker上的bloom filter的收集后merge成一个,而后再发给左表所在的worker,这样在左表进行table scan的时候,能够过滤掉十分多不必要的数据,而后也能够节俭一些计算的资源。这个的话须要优化器整个参加决策,因为生成和传输过程也是有代价的,看哪个代价更低。或者他还会判断一下过滤能力。
高阶优化:RuntimeFilter
另外就是在执行层的话,咱们有一些压缩算法的优化,就比如说表级别的全局字典。咱们晓得社区有一个低级数类型,它的字典是part级别的,曾经能够在一些计算上做到不解压计算了,当咱们扩大成表级别的时候,大部分的计算都能够间接在编码值上或者在字典上进行,就齐全不须要去解压数据了,甚至传输也能够传输编码后数据的。函数计算,聚合运算也是,这块在TPCDS上应该有20%的晋升。
高阶优化:表级别的全局字典
其余的优化,这里能够简略的说一下,包含Windows算子的并行化,而后Windows外面 Partition 的 top下推;公共表达式的复用;以及当初多阶段模型下,对社区为两阶段模型实现的aggregation、join的算子做了一些重构,为了更好的适应这个模型。咱们还反对Bucket Join、简略查问上并发性能的优化。最初就是ClickHouse单机模型的毛病,就是它每个Pipeline是独立的线程池,当并发比拟高的时候线程会比拟多,上下文的切换的开销比拟大。咱们会把它做成协程化,防止过多的线程。
其余优化详情
这是整体的一个成果。而后在社区的两阶段,咱们通过改写,能跑完26个SQL。咱们在多阶段执行和优化器实现之后,基本上是整个TPC-DS的99个SQL都是能够跑完的,性能也是失去了极大的晋升。
整体成果
而后上面讲一下过程碰到的挑战,以及没有解决的问题。第一个就是所有并行计算框架的老大难问题:数据歪斜。如果比拟有热点key,或者聚合件外面的key过少的话,即便有再多的worker,最初也只会在一个worker上进行计算。打算层,其实是能够做两阶段聚合的调整,而后把key过少的问题能够解决,然而热点key的问题还是很难解决,其实能够在执行层做一些自适应的执行,这个还是在摸索阶段,可能相似于Spark的AQE,然而因为MPP的话有很多限度,做不到这么欠缺。
数据歪斜
第二个挑战,超大的MPP集群的问题。业内的话个别超过200个的MPP集群,就是会碰到一些比拟多的慢节点的问题,或短板效应导致线性度急剧下降,稳定性也会降落。咱们在外部曾经有大略将近800个节点的计算组,而后可能马上就会有超过上千个节点的一个计算组。是要怎么样保障这种大超大MPP集群的稳定性和性能,咱们做了一些自适应的管制,进步整体的稳定性。就我方才讲的自适应调度、资源的预估和预占是一个方面,另外就是限度每一个查问的复杂度和应用资源,防止大查问导致把某个work的资源就是占的过满,而后导致的慢节点。最初一个就是对用户无感的一个VW的一个主动划分,划分一些小的子集,这个子集的话是固定的,是为了保障cache的亲和度,咱们会依据查问的大小来主动的抉择,这个也算躲避了超大的问题。
超大的MPP集群
最难的还是怎么构建容错的能力,在这种大集群情景下,如果假如每一个节点的错误率为e的话,那节点数量为N的话,那运行失常概率就是(1-e)^N。节点数量扩充,错误率就会指数级回升。咱们在摸索就是query的状态的snapshot,相似于flink异步的snapshot的计划,能够构建肯定的恢复能力,另外一个咱们是有bucket table,就是会有一些计算是在闭合在bucket外部的,某一个bucket失败可齐全不影响其余bucket,是能够独自去重试的。这是咱们碰到的两个次要的挑战。
构建容错的能力构建容错的能力
这个专场是对于MySQL和ClickHouse,咱们也讲一下ByteHouse在MySQL生态上做的一些事件。咱们晓得把从MySQL数据导入到ClickHouse的话,次要当初有三种计划。一种是ClickHouse的MySQL表引擎,你能够间接通过数据库引擎建一个MySQL的表面,而后用insert select的形式一次性的把数据导入,然而有数据量的限度不能太大,也不能继续的同步。其实在GitHub上有开源的工具,它是基于binlog同步的。但这个操作是比较复杂的,而后并且在曾经进行更新了。社区最近是开发了一个materialized MySQL的一个性能。这个我认为是将来的一个最佳实际。
MySQL数据导入到ClickHouse
Materialized MySQL的话,它的原理也比较简单。用户的话就是创立一个 Materialized MySQL的数据库引擎,这样ClickHouse会有后盾的一个线程,而后异步的去拉取MySQL的Binlog。而后会写到一个Replacing MergeTree外面。这个为什么要用replacing MergeTree,因为它是能够进行逐步的去重。它尽管是那种异步的,但也是能够近似的实现去重工作。而后ClickHouse是做了一些trick,就是在这个replacing MergeTree外面能够给同步的Binlog加两个字段,一个是sign,一个是version,而后后续replacing MergeTree,就依附这两个字段会进行一些去重,sign示意的是。数据的是否删除,version代表的是这次数据的版本,如果你加了final的话,它会就是在查问的时候,会用最高的版本笼罩低的版本。
Materialized MySQL的原理
这个介绍大略的应用,用户从Materialized MySQL的数据库引擎。在ClickHouse外面创立,而后在MySQL外面通过insert语句去写入各种数据,你在ClickHouse外面能够查到,当然还有一些没有展现,就是你在Materialized MySQL外面去创立一些表。而后也会动静的在ClickHouse这边生成,就是DDL的也能够同步过去的。方才我为啥说这个是将来的最佳实际,因为这个还是实验性的性能,它会有很多不欠缺的中央。
Materialized MySQL的数据库引擎
首先,它是不反对不兼容的DDL,只有有一个报错,而后整个同步就进行了,而后进行又是悄无声息,你没有方法去手动的去触发它的再同步。第二点,就是社区的Materialized MySQL的replacing MergeTree其实是一个单机引擎,只能在单点上同步,如果呈现一个单节点的故障的话,就是高可用会成为问题,另外单节点也会有吞吐量的限度。第三个就是方才讲的运维的艰难,看不到同步的状态、当初同步的信息、以及没有同步重启的工作。
Materialized MySQL的劣势和有余
而后ClickHouse的做了一个CNCH的Materialized MySQL的数据库引擎,也是把引擎给云化,修复了社区的一些缺点,真正做到的生产可用。它的原理次要就是通过咱们的service层,依照表的力度去在各个worker下来调度线程,写到咱们的惟一键引擎外面。
Materialized MySQL的数据库引擎原理
当初讲一下解决的这些问题,第一个有十分具体的零碎表,能够看到当初运行的状态。而后也有进行启动重启的各种指令,就是这个整个运维是可用了。咱们反对按表多worker的并发生产。因为是基于原生的架构,存算拆散,如果单个work失败,能够马上主动的从新调度Rebalance。最初咱们是基于惟一键引擎,它是为读优化的,就查问性能会更好。最初是反对配置跳过不兼容的DDL。做了这些工作之后,咱们这个引擎基本上是能够说是生产可用了。
CnchMaterialized MySQL解决的问题
总结一下,明天的一些次要的内容吧,就是次要给大家讲了一下,ByteHouse云数仓版的背景以及整体架构。第二局部是重点讲了在查问引擎上的整体设计和优化点。最初讲了一下咱们生产可用的云数仓版的Materialized MySQL的表引擎,为了欠缺MySQL生态做的一些工作。
2023首届云数据库技术沙龙 MySQL x ClickHouse 专场,在杭州市海智核心胜利举办。本次沙龙由玖章算术、菜根倒退、良仓太炎共创联结主办。围绕“技术进化,让数据更智能”为主题,汇聚字节跳动、阿里云、玖章算术、华为云、腾讯云、百度的6位数据库领域专家,深刻 MySQL x ClickHouse 的实践经验和技术趋势,联合企业级的实在场景落地案例,与宽广技术爱好者一起交换分享。