最近一段时间笔者有幸开始接触OLAP领域的业务。百忙之中抽出时间结合自己对ClickHouse、Spark、Doris等引擎了解,写篇关于OLAP引擎的文章。通过对OLAP引擎原理的剖析、各大厂对OLAP引擎的一些使用情况给读者一个对OLAP引擎直观的了解和感受。
笔者思索如果自己从头设计一个OLAP的分析引擎,应该如何做,核心关键点是什么?这边文章就是笔者结合clickhouse等引擎及自己的思考总结的一篇关于OLAP引擎的文章。
结合ClickHouse和Hbase等框架,笔者认为从海量数据中进行高效率查询的核心思想是:能够快速把待搜索的数据范围降低到原来的1/n,然后再结合索引或者热点数据放在内存等思路,就能实现高效率的查询。
笔者直观上能想到的方法包括:
LSM Tree 论文中有这样的图片介绍,图片里面有C0 tree,C1 tree,C0 tree存储在内存,C1 tree存储在硬盘中。
C0 tree C0 tree是在内存存储的有序列表,Google BigTable 称为memtable,HBase 称为MemStore。这种数据结构通过跳表实现。
搜索
查询
删除
ClickHouse并不像其他分布式系统那样,拥有高度自动化的分片功能。ClickHouse提供了本地表(Local Table)与分布式表(Distributed Table)的概念。一张本地表等同于一份数据的分片。而分布式表本身不存储任何数据,它是本地表的访问代理,其作用类似分库中间件。借助分布式表,能够代理访问多个数据分片,从而实现分布式查询。
这种设计类似数据库的分库和分表,十分灵活。例如在业务系统上线的初期,数据体量并不高,此时数据表并不需要多个分片。所以使用单个节点的本地表(单个数据分片)即可满足业务需求,待到业务增长、数据量增大的时候,再通过新增数据分片的方式分流数据,并通过分布式表实现分布式查询。
分布式的查询不可避免会有聚合过程,这个过程中shuffle大量的数据往往会带来网络I/O的性能瓶颈。诸如spark、clickhouse等引擎都使用到了预聚合的技术。
以spark为例。spark map-side预聚合,在每个节点本地对相同的key进行一次聚合操作,类似于MapReduce中的本地combiner。
map-side预聚合之后,每个节点本地就只会有一条相同的key,因为多条相同的key都被聚合起来了。其它节点在拉取所有节点上的相同key时,就会大大减少需要拉取的数据数量,从而也就减少了磁盘IO以及网络传输开销。
通常来说,在可能的情况下,建议使用reduceByKey或者aggregateByKey算子来替代掉groupByKey算子。因为reduceByKey和aggregateByKey算子都会使用用户自定义的函数对每个节点本地的相同key进行预聚合。而groupByKey算子是不会进行预聚合的,全量的数据会在集群的各个节点之间分发和传输,性能相对来说比较差。
下图可以形象的展示预聚合的原理
1、Ordinary引擎:默认引擎,如果不指定数据库引擎创建的就是 Ordinary 数据库2、Dictionary引擎:此数据库会自动为所有数据字典创建表3、Memory引擎:所有数据只会保存在内存中,服务重启数据消失,该数据库引擎只能够创建 Memory 引擎表4、MySQL引擎:改引擎会自动拉取远端 MySQL 中的数据,并在该库下创建 MySQL 表引擎的数据表5、Lazy延时引擎:在距最近一次访问间隔 expiration_time_in_seconds 时间段内,将表保存在内存中,仅适用于 Log 引擎表
第一:MergeTree 表引擎主要用于海量数据分析,支持数据分区、存储有序、主键索引、稀疏索引、数据 TTL 等。MergeTree 支持所有 ClickHouse SQL 语法,但是有些功能与 MySQL 并不一致,比如在 MergeTree 中主键并不用于去重。第二:为了解决 MergeTree 相同主键无法去重的问题,ClickHouse 提供了 ReplacingMergeTree 引擎,用来做去重。ReplacingMergeTree 确保数据最终被去重,但是无法保证查询过程中主键不重复。因为相同主键的数据可能被 shard 到不同的节点,但是 compaction 只能在一个节点中进行,而且 optimize 的时机也不确定。第三:CollapsingMergeTree 引擎要求在建表语句中指定一个标记列 Sign(插入的时候指定为 1,删除的时候指定为 -1),后台 Compaction 时会将主键相同、Sign 相反的行进行折叠,也即删除。来消除 ReplacingMergeTree 的限制。第四:为了解决 CollapsingMergeTree 乱序写入情况下无法正常折叠问题,VersionedCollapsingMergeTree 表引擎在建表语句中新增了一列 Version,用于在乱序情况下记录状态行与取消行的对应关系。主键相同,且 Version 相同、Sign 相反的行,在 Compaction 时会被删除。第五:ClickHouse 通过 SummingMergeTree 来支持对主键列进行预先聚合。在后台 Compaction 时,会将主键相同的多行进行 sum 求和,然后使用一行数据取而代之,从而大幅度降低存储空间占用,提升聚合计算性能。第六:AggregatingMergeTree 也是预先聚合引擎的一种,用于提升聚合计算的性能。与 SummingMergeTree 的区别在于:SummingMergeTree 对非主键列进行 sum 聚合,而 AggregatingMergeTree 则可以指定各种聚合函数。
以上介绍了设计OLAP引擎的关键点,并展示了一些通用的引擎对基本原理的应用。
clickhouse基本集成了上面的原理的关键点,性能出众。
在 1 亿数据集体量的情况下,ClickHouse 的平均响应速度是 Vertica 的 2.63 倍、InfiniDB 的 17 倍、MonetDB 的 27 倍、Hive 的 126 倍、MySQL 的 429 倍以及Greenplum 的 10 倍。详细的测试结果可以查阅:https://clickhouse.tech/benchmark/dbms/。
目前国内社区火热,各个大厂纷纷跟进大规模使用情况:
但是其也存在缺点:
1、没有完整的事务支持2、稀疏索引导致 ClickHouse 不擅长细粒度或者 key-value 类型数据的查询需求3、缺少高频率,低延迟的修改或删除已存在数据的能力。仅能用于批量删除或修改数据4、不擅长 join 操作。