Apache Doris (incubating) 从2008年第一个版本开始到今天(2019年)已经走过了11个年头。期间,Doris从最初的只为解决百度凤巢报表的专用系统,已经成长为目前国内唯一的分析型数据库孵化项目。一路走来,Doris的初心从未改变。

「Apache Doris —— 为分析而生」

从诞生之日起,Doris的每一步都是为了解决切实的业务痛点,每一次转变都是在面对不同的业务挑战。一路上,Doris砥砺前行,凝结了众多前辈的心血。相信未来,Doris还会有更多的新鲜血液加入,我们一起走的更快,更远。

Doris发展历程

Doris自第一版诞生以来,经过了11年的发展,中间做过无数改进。这里只罗列对Doris发展来说比较重要的关键节点与事件。

Doris1 ,「筑巢引凤」的重要基石

早年,百度最主要的收入来源是广告。广告主需要通过报表服务来查看广告的展现、点击、消费等信息,并且能够需要通过不同维度来获得广告的消费情况,用以指导后续的广告的投放策略。

在Doris1诞生之前,百度使用MySQL Sharding方式来为广告主提供广告报表支持。随着百度本身流量的增加,广告流量也随之增加,已有的MySQL Sharding方案变得不再能够满足业务的需求。主要体现在以下几个方面:

第一,大规模数据导入会导致MySQL的读性能大幅降低,甚至还有锁表情况,在密集导入数据的情况下尤为明显。同时在数据导入时,MySQL的查询性能大幅下降,导致页面打开很缓慢或者超时,用户体验很差;

第二,MySQL在大查询方面性能很差,因此只能从产品层面来限制用户的查询时间范围,用户体验很差;

第三,MySQL对数据量的支持是有限的。单表存储的数据有限,如果过大,查询就会变慢。对此的解决方案只有拆表、拆库、迁移数据。随着数据量的快速增长,已经无法维护。

当时数据存储和计算成熟的开源产品很少,Hbase的导入性能只有大约2000条/秒,不能满足业务每小时新增的要求。而业务还在不断增长,来自业务的压力越来越大。在这种情况下,Doris1诞生了,并且在2008年10月份跟随百度凤巢系统一起正式上线。

Doris1的主要架构如上图所示。数据仍然通过用户ID进行Hash,将同一个用户ID的数据交由一台机器处理。其中Hm-Storage负责数据的存储。ODP、OMG负责将业务数据导入到Hm-Storage中。AS负责解析、规划查询请求,并将查询请求发给Hm-Storage处理,并对Hm-Storage返回的数据进行一些业务相关的计算后将查询结果返回给用户。

相比于MySQL的方案,Doris1主要在如下几个方面进行了改进。

首先,Doris1的数据模型将数据分为Key列,Value列。比如一条数据的Key列包括:用户ID、时间、地域、来源等等,value列包括:展现次数、点击次数、消费额等。这样的数据模型下,所有Key列相同的数据Value列能够进行聚合,比如数据的时间维度最细粒度为小时,那同一小时多次导入的数据是能够被合并成一条的。这样对于同样的查询来说,Doris1需要扫描的数据条目相比MySQL就会降低很多。

其次,Doris1将MySQL逐条插入改成了批量更新,并且在通过外围模块将同一批次数据进行排序以及预聚合。这样一个批次中相同Key的数据能够被预先聚合,另外排序后的数据能够在查询的时候起到聚集索引的作用,提升查询时候的性能.

最后,Doris1提供了天表、月表这种类似物化视图的功能。比如用户是想将数据按天进行汇聚展现,那么对于这种查询是可以通过天表来满足的。而天表相对于小时表数据量会小几倍,响应的查询性能也会提升几倍。

通过Doris1的工作,完全解决了MySQL Sharding遇到的问题。并于2008年10月于凤巢系统一起上线,完美的支撑了广告统计报表需求。

Doris2,解「百度统计」燃眉之急

2008年的百度统计服务大约有50-60台MySQL,但是业务每天有3000万+条增量数据,由于MySQL的存储和查询性能无法满足需求,对存量数据的支撑已经到了极限,问题频出,万般无奈之下百度统计甚至关闭了新增用户的功能,以减少数据量的增加。

Doris1由于当时时间紧、任务重,所以设计、实现的时候只为了能够满足凤巢的业务需求,并没有兼顾其他的应用需求。由于Doris1方案对于凤巢成功的支持,百度统计同学开始基于Doris1打造Doris2系统,主要将Doris1进行通用化改造,包括支持自定义schema等,使Doris能够应用于其他产品。此外还进行一些优化以此来提升系统的查询、存储性能。

2009年Doris2研发完成后上线百度统计,并且成功支撑百度统计后续的快速增长,成功的助力百度统计成为当时国内规模最大,性能、功能最强的统计平台。由于在凤巢、百度统计上的成功,公司内部后续其他类似统计报表类的需求也都由Doris2进行支持,比如网盟、联盟等报表服务。

Doris3 ,让查询再快一点

百度在2009-2011年发展迅猛,营收每年近100%的速度增长,与之相伴的是广告数据量也随之大幅增长。随着业务数据量的不断增长,Doris2系统的问题也逐渐成为业务发展的瓶颈。

首先体现在Doris2无法满足业务的查询性能需求,主要是对于长时间跨度的查询请求、以及大客户的查询请求。这是因为Doris2通过规则将全部数据按照用户ID进行Sharding,这虽然能够将全部数据分散到多台机器上,但是对于单一用户的数据还是全部落在一台机器上。随着单用户数据量增多,一些查询请求无法快速计算得到结果。

其次,Doris2在日常运维方面基本上都需要停服后手动操作,比如Schema Change、集群扩缩容等,一方面用户体验很差,一方面还会增加集群运维的成本。最后,Doris2本身并不是高可用系统,机器故障等问题还是会影响服务的稳定性,并且需要人肉进行复杂的操作来恢复服务。

为了解决Doris2的问题,团队开始了Doris3的设计研发。Doris3的主要架构如下图所示,其中DT(Data Transfer)负责数据导入、DS(Data Seacher)模块负责数据查询、DM(Data Master)模块负责集群元数据管理,数据则存储在Armor分布式Key-Value引擎中。Doris3依赖ZooKeeper存储元数据,从而其他模块依赖ZooKeeper做到了无状态,进而整个系统能够做到无故障单点。

在数据分布方面Doris3引入了分区的概念。首先数据会按照时间进行分区(比如天分区、月分区);在同一个分区里,数据会根据用户ID再进行Sharding。这样同一个用户的数据会落在不同的分区上,而在查询时多台机器就能够同时处理一个用户的数据了,实现了单用户的分布式计算能力。但是可能还会存在一个分区内部单个用户数据量过大的情况。对于这种情况Doris3设计了后续表功能,会将单个分区内大用户的数据进行拆分,导入到多个分片中,这样能够保证每个分片内单个用户的数据总量最高是有限度的。

另外Doris3在日常运维Schema Change,以及扩容、缩容等方面都做了针对性设计,使其能够自动化进行,不依赖线上人工操作。

在当时,由于种种原因,Doris3最终确定使用了Armor来作为底层存储系统。Armor是一款分布式Key-Value系统,支持多副本强一致,且单表内全Key有序。选用Armor作为底层存储能够使Doris3只负责管理分片,而分片的副本,以及副本的一致性都由Armor来处理。并且,集群的扩、缩容等操作也只需要Armor感知即可,Doris3本身并不需要感知。当然除了这些好处外,这样的选型也有一些弊端。

由于Armor是一个通用的Key-Value系统,并不感知上层的业务数据,它并不支持Doris这种数据模型,既相同Key的数据,Value字段是可以进行聚合的。比如数据导入的批次是五分钟一批,但是数据时间粒度是小时,那么其实一个小时的数据可能是多次导入的,但是逻辑上是可以合并成一条数据的。所以为了实现这个功能,只能是Doris3自身实现了较为复杂的数据合并策略来完成相关数据的合并。

Doris3在2011年完成开发后逐渐替换Doris2所制成的业务,并且成功的解决了大客户查询的问题。而公司内部后续的新需求,也都由Doris3来承担支持。

MySQL + Doris3 ,百度的第一个OLAP平台

2012年随着Doris3逐步迁移Doris2的同时,大数据时代悄然到来。在公司内部,随着百度业务的发展,各个业务端需要更加灵活的方式来分析已有的数据。而此时的Doris3仍然只支持单表的统计分析查询,还不能够满足业务进行多维分析的需求。由于缺少通用的SQL支持,Doris3在面对更加灵活的多维分析场景时有点力不从心。当时,公司内只有Hive以及类似系统支持大数据量的SQL查询,但是他们均是面向解决离线分析场景,而在线多维分析领域缺少一款产品来满足业务方的需求。

所以,为了能够支持业务的多维分析需求,Doris3采用了MySQL Storage Handler的方式来进行扩展。通过此种方式,将Doris3伪装成一个MySQL的存储后端,类似于MyISAM、InnoDB一样。这样既能够利用上MySQL对于SQL的支持,也能利用上Doris3对于大数据量的支持。由于这里MySQL是计算单点,为了减轻MySQL的计算压力,Doris3应用了MySQL的BKA(Batched Key Access)以及MRR(Multi-Range Read)等机制尽量的将计算下推到Doris3来完成,从而减轻MySQL的计算压力。

通过MySQL + Doris3这个方案,百度Insight团队为PS、LBS、WISE等产品线提供了百度内部第一个OLAP分析服务平台。

OLAP Engine,突破底层存储束缚

另一方面Doris3支持报表分析场景时,底层通用 Key-Value 存储引擎的弊端也逐渐显露。作为一个通用 Key-Value 存储引擎,在支持报表引擎方面暴露了一些问题。

第一,由于Key-Value系统读取只能够读取全Key,全Value,而报表分析系统中的大部分查询并不需要读取所有列,这样会带来不必要的IO开销;第二,正如前文所说,由于引擎本身不感知业务模型,不能够再进行Merge的同时完成数据的合并,这需要Doris3借助复杂的作业管理在引擎外部完成Merge工作既不简洁,也不高效;第三,为了保证业务的导入原子性,Doris3为每批次导入都赋值一个版本号,并记录在每条数据Key的最后部分。这样在查询的时候,需要对每条数据进行Key的解析,比较版本号,过滤掉不需要的版本。这样一方面需要读取无需读取的数据,一方面需要解析所有Key,从而带来不必要的CPU开销;第四,Key-Value系统无法感知数据内容,只能使用通用压缩算法,进而导致数据的压缩效率不高。这样在查询、读取时都会带来较多的IO负载。

为了能够在底层存储引擎上有所突破,OLAP Engine项目启动了。这个项目的发起者是当时从Google来的高T,为百度带来了当时业界最领先的底层报表引擎技术。OLAP Engine最大的特点包括以下几点。

第一,引擎端原生就支持Schema,并且所有的列分为Key列,Value列。这样就能够跟上层的业务模型能够对应上,查询部分列时,无需加载全部列,减少不必要的IO开销。

第二,独特的数据模型。Value列支持聚合操作,包括SUM、MIN、MAX等。在Key列相同的情况下,Value列就能够按照聚合操作类型完成对应的聚合操作。而引擎本身导入方式类似于LSM Tree,这样在引擎后台进行Merge的同时,就能够将相同Key的数据中的Value字段按照对应的操作进行聚合。这样就无需外部再进行数据合并作业管理,将引擎层与业务层合并合二为一,省去不必要的IO、CPU开销。

第三,数据批量导入,原子生效。对于每个批次的导入,都会有个Delta文件对应,并且会有个版本号。在查询的时候只是在初始化的时候来确定读取哪个文件,这样就只会读取生效版本的数据,而不会读取没有生效版本的数据,更不会浪费CPU来进行版本号比较过滤。

第四,行列式存储。多行(比如1024行)数据存储在一个Block内,Block内相同列的数据一同压缩存放,这样可以根据数据特征利用不同的压缩算法(比如对于时间字段使用RLE等)大幅提高数据压缩效率。

即使分布式层没有采用复杂的分布式管理,只是使用类似Doris2的用户ID Sharding方式,OLAP Engine后续也成功的支持了凤巢,网盟等广告业务。这充分的能够体现OLAP Engine强大的报表分析能力。虽然OLAP Engine取得了成功,但是由于硬Sharding方案带来的不易运维、不易扩展等问题仍然存在。

用PALO,玩转OLAP

底层技术的发展会激发上层业务的需求,而上层业务的需求同时会为底层的技术带来新的挑战。随着第一款OLAP产品的问世,数据分析师们的建模就更加复杂,有时查询SQL会有上千行,人为阅读已经相当吃力。而MySQL + Doris3方案的弊端也就越发突显。因为分析SQL越来越复杂,大量的计算都需要在MySQL中完成,这样MySQL的计算能力就成为整个系统的性能瓶颈,突破这个性能瓶颈也就变得极为紧迫。

因此Doris亟需一款拥有分布式计算能力的查询引擎。幸运的是当时(2013年)各种SQL on Hadoop项目也正蓬勃发展,比如Impala,Tajo,Presto等等。在有限的时间内并不充分调研的情况下,团队选取了Impala作为了后续系统的分布式查询引擎。当时的选择Impala主要的原因是因为其性能较高,并且BE的C++语言跟我们已有系统的语言一致,未来可以省去一部分序列化开销。

由于MySQL + Doris3的方案制约了业务的使用,当时公司的另一个团队邀请了Oracle的Exadata进行POC,这给了Doris团队很大的压力。如果Doris想继续在OLAP领域继续发展,就需要快速的产出原型,并且性能上还要胜出Exadata。为了快速的验证方案的可行性,团队几个月内就把Impala与Doris3进行了集成,并用TPC-H进行了测试,结果是Impala + Doris3性能比Exadata更好。这次原型的成功为我们赢得了一次机会,能够让团队继续改造Doris3从而更好的支持OLAP场景。

新产品的名字命名为PALO,意为玩转OLAP。

PALO1除了增加分布式查询层之外,因为OLAP Engine在统计报表领域的成功,PALO1放弃了Doris3依赖的通用Key-Value系统,选择了OLAP Engine作为自己的单机引擎。因为没有了分布式Key-Value系统,那么PALO1自己完成数据分片管理、副本管理等工作。

PALO1的架构如下所示。其中DM负责管理元数据、数据的分布、分片副本管理等内容,DM本身没有状态,元数据内容都存储在MySQL中。FE负责接收用户的查询请求,并且进行查询规划解析。BE是负责存储数据,以及进行具体的查询执行。

随着PALO1的正式上线,除了迁移所有Doris3已有的的业务外,也成功支持了当时百度内部大部分的OLAP分析场景。

PALO 2,让架构再简单一点

如果说PALO1是为了解决性能问题,那么PALO2主要是为了在架构上进行优化。由于PALO1模块数目较多,并且外部依赖MySQL,这其实还是增加了运维的压力的。所以我们在PALO2项目中力求将系统的架构进行简化。经过简化后的系统架构如下图所示。

PALO2中我们只存在2种模块:FE、BE。FE一方面负责管理、存储元数据,另一方面FE还负责与用户交互,接受用户查询,对查询规划,监督查询执行,并将查询结果返回给用户。FE本身是有状态的,但是它内部通过BDB JE,能够将元数据进行多副本复制,从而能够保证服务的高可用。BE与PALO1功能一致,只是PALO2的BE包含了存储引擎,一方面减少了一个模块,并且在用户查询的时候少了一次数据的序列化、反序列化操作,节约CPU消耗。

通过PALO2的工作,系统架构本身变得相当简洁,并且不需要任何依赖。因为PALO2架构的简洁,我们后续也相对容易的基于PALO2提供了公有云服务以及私有化部署;另一方面,当PALO开源之后其他用户也能够用通过较低的门槛来搭建使用PALO 。在此之后PALO虽然经过几次改进,但是整体架构仍然保持PALO2的架构。

Apache Doris (incubating) ,是更广阔的世界

PALO2在百度内部基本服务了所有的统计报表、多维分析需求,我们相信它一定可以应用到其他公司,能够帮助更多的人更加高效、方便的支持类似的业务需求。因此,我们选择了开源,PALO于2017年正式在GitHub上开源,并且在2018年贡献给Apache社区,并将名字改为Apache Doris(incubating)进行正式孵化。贡献给Apache之后,Doris就不仅仅是百度的项目,而成为了Apache的项目。

随着开源,Doris已经在京东、美团、搜狐、小米等公司的生产环境中正式使用,也有越来越多的Contributor加入到Doris大家庭中。一路走来,Doris从未惧怕过挑战,也从未被困难击倒。时至今日,Doris已经站在了更高的舞台上,准备拥抱更多的机遇与挑战。

希望未来,会有更多的人来续写这篇Doris简史,讲述这个为分析而生的故事。

欢迎扫码关注:

Apache Doris(incubating)官方公众号

相关链接:

Apache Doris官方网站:

http://doris.incubator.apache.org

Apache Doris Github:

https://github.com/apache/incubator-doris

Apache Doris Wiki:

https://github.com/apache/incubator-doris/wiki

Apache Doris 开发者邮件组:

[email protected]