一、Matrixone 的早期架构与难题
MatrixOne 早期的架构与现在有很大区别。早期的架构可以总结为两个词:一个是 NewSQL,一个是 MPP。
NewSQL 是当年谷歌的几篇论文衍生出来的分布式数据库的一套理论体系。其中最重要的一点就是分布式架构,解决的是传统数据库的高可用以及水平扩展的难题。另外一点就是多引擎,用不同的引擎来做不同的事情。
MPP 或者叫大规模并行计算,主要的用途是通过分布式的方式将一些规模比较大的计算任务分布到不同的节点,并且在计算完成之后汇总。充分利用了分布式架构的算力资源。
我们早年的架构确实也是这个样子。上面有一个负责分发负载均衡的 proxy。下面是 MatrixOne Server,每一个 Server 下面有自己的存储。是一个存算一体的架构,每个节点看似是对等的。当然这里面存在着一些问题。
再来看一下组件详解。
最上面这一层我们叫 SQL 前端,是为了兼容 MySQL 的语法协议,无论用 MySQL client,还是 JDBC 都可以直接连。
计算层是传统的 SQL Parser,支持语法树的解析,以及多方言的 SQL。
最下面就是我们自己写的 MPP SQL 执行器,针对基础引擎做了一些向量化的加速,部分操作甚至用汇编语言做了改写。当时使用了一个比较独到的因子化加速的能力。
当时这套架构有着相当不错的性能表现。
分布式框架这一层,叫做 MatrixCube,是一个开源的项目。提供了一个对于多台机器的分布式存储框架。具备高可用多副本,负载均衡强力制等基础能力。当时设想是用它来为 Matrix 计算提供分布式事务的支持能力。里面有 Raft 协议,而且中间会有一个调度器叫做 Prophet。
最下面是计算层和存储层。存储层除了引擎接口之外,有三个引擎。
最中间的叫 AOE,不支持事务,可以往里写数据,但是对于事务、去重等基本上是不支持的。最左边的叫 TPE,用来保存元数据 catalog,它是一个非常繁忙的引擎。最右边的叫TAE,是一个典型的列式存储,能够提供完整的 ACID,同时我们希望它能够去支持比较大规模的 OLAP 能力。所以早期的一个问题是三个存储运行,这也是后来我们为什么从架构层面整个进行重构的一个原因。
接下来讲一讲原有架构存在的问题。
扩展性:
- 存算不分离,每扩展一个单位的节点,就要去扩展相应的存和算两种资源。
- 因为数据以3副本形式保存,只要一个节点加进去,想要真正接管计算任务的时候要先把整个存储完成同步,预热的时间非常长。比如我们大概有 1TB 的数据,要先等着 1TB 数据的3副本完成在这个节点上的同步,才能去开始提供所有的计算负载。
性能:
- 因为 Raft 协议一定是有一个 leader, leader 节点容易成为热点,很多的调度任务都从它这里走。
- 在性能比较差的存储下,整体性能下降会超过预期。比如我们用 SSD 做 benchmark 测试时,预计其性能是10,我们最早预想 HDD 能跑到5或者6,但是实际情况是我们在HDD 上可能跑出来的结果只有3或者4,这成为了一个性能上的瓶颈。
- TPE、 AOE、TAE三个引擎,用途不同而且性能也不一样,经常会出现在某一个业务场景三个引擎当中的一个成为了整个性能的瓶颈。
成本:
- 节点规模越大,成本线性增长,负担越来越重,到了公有云上,有的公有云提供了一个高可用的方案,就成了壳子套壳子,就不是线性而是指数级增长了。可能我要用3个节点的时候,数据库里存了9副本。然后在公有云又可能做了一个3层冗余那就是27层。到后面客户所承担的成本负担实在是太重了。
- 只有高配存储才能发挥出预期,性能较差的存储架构发挥不出来我们想要的性能特性的时候,只能通过不断增加存储成本的方式来满足需求。
二、Matrixone 架构升级之路
面对这些问题,从2022年3月开始,我们对整个架构进行了升级。其实我们是从0.5开始的,0.1到0.4还是在探索,不断思考不断试错,直到0.5的时候,我们终于意识到这个架构走不下去了。
原有架构存在三座大山。
第一座是分布式框架:
- 多副本存储带来存储成本的飙升。
- Leader 选举,人为制造了热点。
第二座是引擎众多:
- 三个存储引擎彼此之间的代码复用率非常低,一个新的功能,代码的维护量是3倍。
- 因子化算法过于激进,除了主开能够驾驭这个算法,其他同事参与度非常低,只能做一些辅助,加一个功能都会非常困难。
第三座是资源分配:
- 存算不分,做各种资源配比的时候,不同业务场景的隔离性非常差。
- Share-nothing 架构,扩展性非常差。必须同时扩展存和算两种资源。
在总结出这三大根本问题之后,我们开始做架构升级。
左侧图可以看到,一层套一层,各层之间又是强依赖的关系,所以第一步就要先把各层彻底打散,做一个更加灵活解耦的整体架构。
最后我们将存储层单独做了一层,可以使用各种存储,并把所有的cache服务放在存储层来做。
最上面是计算层。所有的 MPP 以上的无论是前端还是 Parser,或是执行器,全都放到计算层。
中间我们开发了一个新的层,叫做事务层,可以看作 log service。与传统关系数据库中的 Redo 或者 PG 里的 WAL 日志非常类似。而我们还有一个事务节点 DN,专门用来做事务服务,因为我们是一个分布式数据库,涉及到分布式的事务裁决,包括去重、落盘等任务。所以最终选择开发一个单独的事务层来专门处理这些东西。
三个存储引擎中,我们认为留下 TAE 最合适,它能够提供基于列存的 TP 引擎,并且可以做到完整的事务的原子性、一致性、隔离性和数据一致性,完整的 OLAP 能力,所以是最适合用于新架构的一个引擎,剩下两个引擎的一些功能想办法融到TAE里。所以最终我们看到的是 TAE,它是用列式的编码来存 column family,可以在行和列之间灵活切换。这样做的好处是,可以同时运行 TP 和 AP 的负载,因为我们经常说行存更适合 TP,列存更适合 AP,现在做一个转换之后,行和列就都能够兼顾到了。另外,所有的表都能够实现表级别的快照事务隔离。并且支持主键、唯一键排序、外键索引。
前文提到,数据要保存三副本,而且每个副本在数据库里面是以分片的形式保存,用操作系统自带的 cache 来完成冷热数据的管理。新架构有一个新的要求,就是冷热数据尽量分离,读写请求分离,实现对存储的精细化管理。所以最后我们选择了 AWS 的 S3 对象存储,私有化部署提供 S3 的协议兼容的对象存储。热数据保存在计算节点的缓存cache 上,所有的节点都实现了无状态。并发能力可以通过多启动几个计算节点来线性提升。三个层级之间不再过度依赖。
分布式存储完成之后,就是计算层。之前是因子化算法构建执行计划,做复杂的查询加速,主要是提高AP性能。但表达式和节点的抽象与表述过于复杂,增加修改功能难度很大。并且多个引擎之间的代码复用率太低,导致工作量成倍增长。所以我们开发了新的 MPP 执行引擎,基于 DAG 来构建执行计划,能够实现节点内和节点之间的自适应调度。同时能够满足并行和并发两种请求,因为我们都知道并行是 AP,并发是 TP,通常大家会这么去处理。而且 SQL 能力得到了完善,具备了子查询、窗口函数、CTE,还有内存溢出等能力。未来的优化空间更大,无论是我们的主开还是其他的计算组成员都可以进行优化加新功能。
下面来看一下现在总体的架构。
最下面是 File Service,这是一个统一的文件读写的服务接口,它能够从 S3 对象存储去读数据,并且把这些数据推给日志、计算节点或者事务节点等等。而且事务节点和计算节点的日志又可以通过它去写 S3。所有的节点只需要与 File Service 打交道,就能够完成对存储的读写。全量的存储都可以保存在 S3 上,公有云版的 S3 成本非常低,并且可以无限伸缩。上面的事务层,有两个 DN 节点专门负责管理日志服务和元数据。平时会在里面缓存元数据,做一些事务裁决,并且会指挥 log service 的日志服务来落盘写数据。它自己就是通过三副本的方式来保证从日志级别上的高可用,这里还有一个 Logtail,后面会详细解释 Logtail 和现在的落盘的数据之间是如何共同完成数据写的过程的。
最上面是我们所有 Severless 的计算节点,叫 CN 节点。计算节点是完全无状态的,每个计算节点有自己的 cache,好处是如果计算的负载比较高就可以多起几个 CN,如果业务比较少,就可以把节点全都宕机,节省一些成本。
存储层 TAE 完全实现了列存。大家可看到从数据库到表,再到 segment,再往下是很多列,列的单位是列级别的 block。一次读的时候,会从一个列里面去读一些行作为一个 block,推给上面的计算节点或者日志节点。
大家可能会比较关心如何在 AP 和 TP 之间找到一个平衡点,现在默认建一张表,都是列。如果某些表想要强化一下行存的性能,我们建一个叫做 column family,对某一些行做一些特殊的优化,对一些可能需要频繁在上面做索引,或者需要更新的列,通过 column family 列的方式能够大幅提升其TP性能,最终只需要存一个副本。我们在表上做好一些优化之后,就可以实现行存和列存在各自性能上的优势。这个 column family 现在还正在开发当中,会在未来的一两个版本迭代之后推出一个最初的版本。
计算方面,实现了节点之间的调度。上图中可以看到,所有的计算节点之间都有个双箭头,含义是比如从最左边的计算节点进来,需要去做一个数据的查询,但是我发现 cache 里面没有想要的数据,就会遍历所有其它计算节点去找想要的数据;如果找到,就直接在那个节点里面把计算任务完成,再把结果返回到最初的接受请求的节点。这样的好处是最大限度地利用了不同节点缓存不同的热数据。对于一些常用的查询,会有非常大的性能提升。而且现在我们的计算节点,除了缓存以外,上面还有一个自己写的pipeline,将很多的 SQL 请求拆解成为物理执行计划来执行。上面是目前正在开发的一个优化器,而最上面就是我们一直在迭代的代码复用最多的 MySQL 级别的 Parser。能够对语法做一些解析,同时还能够去做一些方言上的支持。比如对 PG 的语法和方言的支持,其实都是用 MySQL Parser 来做的。
三、Matrixone 架构升级的困难与收获
接下来介绍我们在架构升级中遇到的困难,和解决方案。
第一个难题是如何寻找一个能够对高性能计算引擎匹配的存储。两个核心的需求:
- 一个是更少的冗余。
- 一个是更低的使用成本。
经过很多的论证之后我们发现,AWS 的 S3 对象存储能够完美地匹配我们这两个核心需求。比如我们现在整个单副本,在AWA基本上是一点几个副本。多了约20%的冗余,成本比起之前的三副本大幅下降。使用上,现在匹配 S3 的各种接口,各种方式开发都已经慢慢的成熟起来,还有 S3 自带的冷热分离,一方面将冷数据放在 S3 里面降低成本,而热数据放到计算节点上,基本上完成了用更低的成本来实现冷热数据分离。
第二个难题就是事务层的分工,分布式数据库的分布式事务始终是一个非常大的难点,一开始我们希望我们的 CN,即计算节点只负责计算,所有的事务 ID 生成,事务裁决,还有一致性隔离性,包括数据的读写全都由 DN 也就是事务节点来完成。所有的冲突检测、约束完整性也都由 DN 来完成。但是后来发现,DN 会成为瓶颈。因为我们平时启动的事务节点的数量远远少于计算节点的数量,如果事务节点起的多了,在事务裁决上多个事物节点之间同步又会出现问题。所以当时 DN 成为了一个瓶颈。
于是我们做的第一件事情是引入了 Logtail 的概念。我们平时写数据时首先把数据这个操作写到日志里,然后再落盘去写。这样的好处是如果写的过程当中发生宕机,我们只需要回放日志,就可以保证数据最终还是可以落盘的。现在在日志里保存数据 Logtail,然后 Logtail 会定期把这部分数据写入到 S3 对象存储,就不需要频繁地去写。这样的好处就是我们在写的时候,不再局限于整个 DN 的写的性能。DN 只需要攒够一大批一起往里写一次。当 DN 不怎么忙的时候,可以选择在自己不怎么忙的时候把它写进去。忙的话就全都缓存在 Logtail 里,这样 CN 只需要负责所有的事务和事务逻辑,还有计算。DN 既保留最近一段的数据,同时又负责日志服务,这样就把 DN 在一定程度上解放了出来,使得写入动作的上限被打破了。但是还面临一个问题,就是事务量非常大的时候怎么样保证写的性能。当时我们选择一个新的策略,如果批量写入一大批数据,比如一下写入几百兆的数据的时候,我们不再通过日志,而是直接往对象S3里写。只是告诉日志服务我要通过什么样的操作,在哪个文件里写什么东西。而那些比较小的事务,比如只是更新一两行数据,或者插入一个新数据,仍然还是走原来的从计算节点到事务节点再到对象存储这样一个过程。并且现在我们将约束完整性和冲突检测,都放在了 CN 来做。就在一定程度上让事务 DN 节点更加的灵活,整个的负载更轻。写入性能可以得到明显的提升。
现在还面临一个问题,就是我们如何实现不同业务类型的负载工作。工作负载的隔离,按照现在的架构,首先计算节点先把数据推给事务节点,事务节点再通过日志写到 S3 里。如果是 OLAP 负载,可能直接从 S3 里面去读数据来进行计算就可以了。我们现在选择的方式是用不同的 CN 节点来跑不同的东西。比如成立第一个 CN 组,只跑 TP 业务,第二个 CN 组,只跑 AP 业务,实现计算节点之间的隔离。当然如果系统比较重要,预算比较充裕,那可以选择用机器做服务器级别的隔离,用物理机来部署不同的计算节点。如果想用低成本的机器跑,我们也提供了容器级别的隔离,容器级别可以实现数据和负载的完全隔离。
我们现在首先做了标签化。比如图中有三个打了 AP 的标签,一个打了 TP 的标签。当一个会话进来的时候,优化器会先去判断它是一个 AP 请求还是 TP 请求。如果是 TP 请求,就进TP的计算节点,如果是 AP 就进 AP 的计算节点。这样的好处就是不会出现两种业务上的资源公用。哪边业务更高,我就选择对哪一边分配更多的资源。未来还会实现自动的负载均衡。比如利用优化器,通过某一段时间的统计信息来判断最近可能TP业务更多一些,那么就自动扩容一些 TP 的计算节点,AP 更多的话就自动扩容一些AP的节点。目前公测的0.8版,主要提供给用户的是手动的通过配置标签的方式,让用户把自己的不同类型的负载打到不同的计算节点上来实现。
我们在整个升级过程中,进行了一些技术上的复盘,总结了其中的收获。
我们对一条 SQL 从客户端进入服务器再到完成执行的整个过程进行了重构。对 SQL 的执行有了更深刻的理解,对执行计划、SQL 标准有了更多的认识。
之前我们是多引擎,有的引擎开发的时候不需要考虑事务的 ACID。现在则不同,每一条都要考虑事务的四个特性。
在开发事务层的时候,对 CN 和 DN 的适配有了更多的经验积累。作为分布式事务到底该怎么分工,既能够保证完成事务的 ACID,同时又能够保证让系统的架构和系统的负载不会出现明显的短板。我们经过反复的验证,最终引入 Logtail。并且 CN 和 DN 一个只负责元数据,另外一个负责计算和逻辑以及去重。我们还发现 Logtail 还有一个好处,就是可以实现不同的计算节点对这一部分数据的共享,不需要再从对象存储里直接 load。
存储层,积累了对S3对象存储的开发经验。另外,我们现在自己的file service文件服务基本上已开发完成,很多时候使用不同类型的存储,不再需要考虑接口要怎么写,或是兼容性和性能,统一交给file service去实现即可。
四、总结
最后进行一下总结。
首先,实现了从存算一体到计算、事务、存储三层解耦。存算一体的分布式架构有着其自身的优势,但是存在一些问题,比如容易制造热点,成本较高等等。我们完成了三层的解耦之后,每一个层级可以自行进行扩缩容,不再依赖于其它层面。这种灵活解耦的架构,在不同的业务需求上,可以得到不同的最佳实践。比如有些业务,可能需要更多的计算资源,可以直接加计算资源。
第二,实现了从多引擎到单一 TAE 的 HTAP 融合引擎。多引擎要维护大量代码,并且要考虑每个引擎的特性之间如何搭配。而单引擎,无论工作量还是成本都有所降低。
第三,实现了因子化算法到 DAG。
第四,实现了从多副本存储到对象存储与 Logtail 的引入。存储成本降到了原来的1/3左右。
第五,实现了灵活调整节点分配带来的资源隔离。一方面存算分离,可以更加灵活地分配资源。另外通过标签的方式,将一些请求强制隔离到不同的节点上,避免了不同业务类型对资源的征用。
如果希望进一步了解或探讨,欢迎大家关注我们的企业服务号或加入微信群,会有很多干货,以及我们最新的进展在上面发布。
最后,介绍一下我们公司现在在做的 Beta Program 用户体验计划。这是我们为一些即将有合作意向的客户提供的一个专属的计划。参与该计划,可以获得最新的功能发布信息;并且可能得到匹配您的业务场景的定制;甚至可以参与到产品的设计当中。
目前0.8版本,处于 Beta program 阶段,我们会在第三季度发布正式版。我们现在也提供了公有云版,处于公开招募阶段。如果感兴趣,也可以申请使用。目前我们使用的是serverless计划,可以在上面跑一些 TBC 或者 TBCC 这种比较常见的 benchmark。如果您有一些基于 MySQL 开发的应用程序,可以在我们的产品上面进行测试。
以上就是本次分享的内容,谢谢大家。
五、Q&A
Q1.:后期有没有计划接入更多的存储引擎,比如 minio,或者是 HDFS 之类的引擎?
A1:我们现在私有化的场景就是以 minio 作为私有化部署的方案,当然整个对象存储也会越来越多。在 minio 比较成熟以后,我们也会选择更多的存储对象来支持。现阶段我们的标准的私有化版本是 minio,公有云版本是 S3 或者阿里云的 OSS。
Q2:对企业级用户是否有定制化的支持,比如对于解决方案的设计,运维的设计等等。
A2:是有的。首先我们现在企业付费用户,会有一个单独的运维工具,可以更好地进行集群管理、私有管控等等。如果需要定制化的设计开发,或者一些应用程序的优化等需求,也可以联系我们。
Q3:为什么设计成单独的存储,Logtail 不放在统一的存储层?
A3:我们给 Logtail 配的是比较好的存储,直接在里面缓存的时候,往里写的性能比S3要更好。相当于起到一个中转作用。另外,Logtail 存在 DN 里面的话,不同的 CN 只要数据没有落盘没有被 truncate 之前,所有的 CN 都可以共享。比如恰好就需要 Logtail 数据的时候,直接从 Logtail 里读不需要再走 S3,这样在一定程度上也就实现了对 CN 的加速。
Q4:怎么体现冷热跟读写分离?
A4:首先,S3 自身提供了一个冷热数据分离的机制。它可能读取速度会比冷数据稍微快一些。其次,我们在每一个节点上,不管是 DN 还是 CN,也放了自己的缓冲区。允许用户把一些常用的数据放在自己的内存里,如果除了内存以外,在每个节点上再配一块高性能盘,再缓存一些数据,其实也可以做。实际上冷热数据实现了多机分离。