OLAP平台介绍
在两年多以前,哔哩哔哩还没有专门的OLAP平台,但是业务一直都有这种交互式分析的需求,所以业务各自自建自己的OLAP引擎。当时有在使用的引擎五花八门,包括Apache Kylin、ES、Druid、ClickHouse、Presto等等,缺乏一套统一的接入规范和平台工具,维护成本非常高,稳定性也比较差。
阶段一:数据服务引擎收敛到ClickHouse
我们所有组件OLAP平台除了平台工具的建设,在引擎侧首先做的事情,就是把OLAP引擎进行尽可能地收敛,简化在引擎维护的成本,从而能够将资源投入到更有价值的方向。因此,我们第一个做的事情,就是把大部分数据服务的引擎统一收敛到ClickHouse上。
为什么选型ClickHouse,主要是基于以下的几个理由:首先ClickHouse的性能非常强大,相信大家只要用过的都会有这个感受,基于它本地文件系统存储,计算存储一体设计的优势,以及它采用native语言实现,性能优先的工程实现思路,向量化执行引擎,到今天可能依然是性能最快的分布式OLAP引擎。当然某些场景ClickHouse支持得不是很好,比如大表关联等需要大量数据shuffle的场景,因为它主要还是采用的scatter-gather这种分布式执行模型,而不是MPP架构。
同时ClickHouse的能力非常丰富,比如它丰富又灵活的built-in Function,以及各种MergeTree存储引擎、物化视图、索引等等,这使得它的能力覆盖了很多之前业务自建的不同业务特点选型的其它OLAP引擎。
另外,然后在我们选型的时候,ClickHouse在业界也已经被大规模使用,经过业界实际场景的检验,社区也比较活跃。
我们基于ClickHouse支持的一些典型的数据服务场景包括:用户行为分析平台、标签圈选平台,内容分析平台等等。
我这里主要讲我们在用户行为分析平台的实践案例。用户的行为数据分析可能是每一个互联网公司内部进行精细数字化运营管理必不可少的一环,用户行为数据的特点就是量级会非常大,因为可能用户在你的app上的每个行为都会产生一条数据,一般都是公司最大的几个数据流之一,在B站的话每天这个流有千亿级别的数据。然后我们要对行为数据进行各种各样的分析,包括像天、城市等各种维度分组的UV/VV统计、留存分析、漏斗分析、行为路径分析等等。最开始的时候第一版用户行为分析平台是使用Spark去执行分析任务的,基本上单次分析都要10分钟甚至半个小时以上,所以用户的使用体验非常差,消耗的资源也非常多,迁移到ClickHouse后,我们当前是使用了64个节点组成的ClickHouse集群,总计大概5PB的数据量,P90的查询都能够在4S以内响应,基本上做到了交互式响应的用户体验。
在建设的过程中,我们碰到的第一个问题是:数据写入的问题。因为数据量特别大,而且还有波峰波谷,数据的写入以及写入新数据引发的ClickHouse merge小文件的操作会占用大量的磁盘IO,严重影响查询的性能,而如果预留足够的资源给写入,由于数据有波峰波谷,会有很大的资源浪费,所有我们实现了一个基于Spark ClickHouse BulkLoad。在Spark Task内使用ClickHouse本地进程进行ClickHouse内部数据文件的生成和合并,然后利用两阶段提交协议,把文件投递到ClickHouse集群中,并加载文件到表内。通过这种方式,把大部分写的IO代价从ClickHouse集群移到了Spark任务中,从而保证了查询的稳定响应。
解决写入问题后,第二个就是如何保证查询的效率,这方面是引擎和行为分析服务协同,包括UserID的字典映射成正整形,然后通过物化视图构建Bitmap,将UV、漏斗、人群分组等业务需求都转化成高效的bitmap的交并差计算,ClickHouse在这方面提供了非常丰富和强大的相关Function实现,还有像是数据sharding存储,将全局分布式算子转化为逻辑上等价的local算子操作。之前我们团队有发布一篇《B站基于ClickHouse的海量用户行为分析应用实践》,大家有兴趣可以去看看。
阶段二:文本检索迁移到ClickHouse
在第一个阶段,我们把数据服务场景的引擎都统一收敛到了ClickHouse,Kylin和Druid集群则全部下线了。第二个阶段,我们主要做个事情是把文本检索的场景也迁移ClickHouse上。
我们把ES承载的业务分成两类,一类是文本检索,最典型的业务就是日志平台,用户需要根据一些关键词去检索日志做troubleshooting,或是使用日志数据进行数据分析,它的特点是不需要进行打分排序,只是进行精确的文本检索。另外一类是搜索排序,比如我们很多C端业务的搜索类需求,这个是需要进行打分排序的。B站之前的日志平台主要也是采用业界非常流行的ETK技术栈去实现,我们在第二阶段把这部分业务迁移到了ClickHouse上。
这样做的原因有以下几点,第一是由于日志量非常大,ES有明显的写分词瓶颈,同时数据存储成本非常高,对于机型的CPU、内存都有比较高的要求。并且,ES的数据分析能力较弱,再入一份数据到大数据平台代价又很大,在B站需要大几百台机器来支持日志平台,因此,我们主要的驱动力是做降本增效。
将日志平台从ES迁移到ClickHouse,收益就是写入性能提升了大概10倍,存储成本降低至原来的1/3,结构化字段的查询性能提升了2倍,P90的查询都能够在3s内响应,满足这种交互式troubleshooting的需求。
这里面稍微讲一些关键的点:首先绝大部分的日志查询都是在一个时间段范围检索某一个app_id的日志数据,可能还有更多的维度筛选条件和检索关键字,我们利用ClickHouse Primary Key和正向索引的能力,把ClickHouse需要扫描的数据限定在一个相对较小的范围内,同时在message字段是建token bloomfilter索引,这种索引能够对日志文本进行分词然后构建bloomfilter索引,是一个非常轻量化的反向索引,通过关键词和tokenbloomfilter索引可以进一步缩小需要扫描的数据量,最后是利用clickhouse强悍的现场计算能力处理数据。
日志的很多场景除了一些公共字段,它的schema是会不断变化的,最常见的就是新增字段。ES可以很好的支持这种schema free的数据类型,但是对于ClickHouse来说,这个动态字段一般是放到Map数据类型中,这个就会带来两个问题:一个是存储压缩效率会受到很大影响,第二个是查询的效率,访问Map中数据的时候要把整个Map读出来,而且用户使用存储在Map中的字段做过滤的时候,就很难使用正向索引去过滤数据了,这对我们的查询性能是一个非常大的挑战。
我们仔细分析了这种场景,发现虽然这些场景数据schema是不断变化的,它需要日志平台提供这种能力,但是schema变化的频率本身不会很高,比如说大部分业务实际上是在发布上线新版本的时候日志schema出现了变化,这个周期是以周甚至月来计算的,在ClickHouse引擎内部,在一个存储文件内,我们实际上可以认为数据的schema是不太会发生变化的。
基于这个观察发现,我们设计实现了一种新的Map类型的实现,它的访问方式是Map,但是实际在ClickHouse内部存储的时候,是把Map展开存储按列存储的,而且支持用户按照这种隐式类定义索引,所以它实际存储和查询的效率和打平按列存储基本上是一样的。
到现在,B站的ClickHouse集群基本上覆盖了所有业务的交互式数据分析场景,每天承载千万次查询,万亿条数据写入,P90的查询都能够在200ms内响应。
阶段三:湖仓一体降本增效
在完成上两个阶段的工作目标后,我们第三个阶段的工作主要是围绕湖仓一体进行进一步降本增效展开的:
所谓的湖仓一体,我的理解就是基于传统Hadoop/Spark和基于HDFS的数据湖生态,开放的查询引擎和统一的数据存储和元数据管理服务,再加上像Iceberg这样的表存储格式以及数仓高级的一些能力,包括比如数据组织优化、索引、预计算、实时数据可见,upsert支持等能力。既保留了湖的灵活性,又能够接近或者尝试达到专门分布式OLAP引擎的查询效率和能力。
在B站,我们是以Iceberg为核心,构建我们的湖仓一体技术栈,Spark和Flink可以接入流或者批的数据写入Iceberg,我们自研了一个Iceberg的数据管理和优化服务,叫Magnus,所有Iceberg表的写入事件都会向它汇报,然后根据配置的策略对Iceberg表的数据拉起Spark任务进行异步的数据优化工作,比如合并小文件,优化数据排序方式,生成索引等等。然后通过Trino支持针对Iceberg表的查询。还引入了Alluxio用于Icebergmetadata、索引等文件的缓存加速。
湖仓一体主要应用在什么场景,它能够给我们带来什么收益?我们的理解是它查询性能和使用场景处在离线数据分析和分布式OLAP引擎的中间位置。相比于离线数据分析,它能够提供更好的查询性能,同时Iceberg表能够提供较粗力度的ACID事务能力,保证数据查询的正确性,还有它能提供实时的数据可见性,将原来离线表、天表、小时表的可见性提升到分钟级,对于离线数据分析能够支持更丰富的场景,同时在报表、数仓分析层建模等场景可以提供更好的查询体验和计算效率。
另外一方面,相比于ClickHouse这样的OLAP引擎,湖仓一体的好处是它可以自然作为离线数仓Hive表分层的一部分,无需数据在HDFS和ClickHouse冗余存储和数据同步,它的计算和存储是分离架构,能够有更好的弹性,一般的公司大数据平台的工具链都比较完备,Iceberg表可以非常小成本接入,因为它本来就可以认为是一个特殊存储类型的Hive表,所以在历史数据上只会很低频的访问,放在Iceberg中会比ClickHouse成本更低,它也可以作为ClickHouse中数据的一个低成本副本存储方案,或者直接支持一些性能要求没有那么高的数据服务。简单总结来说,湖仓一体相比于离线分析可以提供更好的查询体验和更高的查询效率,相比于ClickHouse,查询效率比不上,但是资源和用户使用成本则更低。
为了能够做到更接近分布式OLAP引擎的查询效率,我们也基于开源Iceberg进行了较多的增强。在数据组织方面,支持Iceberg表可以定义文件间和文件内的数据排序方式,支持Z-Order这种排序方式。同时在索引层面也进行了增强,支持BloomFilter、BitMap、TokenBloomFilter、TokenBitmap等索引类型,在预计算层面,也支持用户针对单表和星形模型定义预计算,在查询时自动优化执行计划,直接使用预计算结果响应匹配的查询。以上就是湖仓一体具体的应用场景。
指标服务是我们内部的一个统一指标取数平台,湖仓一体作为其中一个重要的引擎来支撑指标的取数,服务本身根据用户对于指标取数的SLA要求使用不同的引擎来存储和响应查询。目前在指标平台上,每天Iceberg表响应大概20万个查询,P90在1.2S内响应。关于指标取数服务的详细介绍,大家也可以去看我们的文章《B站取数服务演进之路》。
日志平台3.0的建设,是我们另外一个场景正在落地的一个项目。因为很多日志的历史数据要存储很久,使用ClickHouse虽然比ES成本低了很多,所以我们想要进一步降低历史日志的成本。因此,我们把日志的历史数据存储在Iceberg表上,虽然通过湖仓一体提供不如ClickHouse,但其日志检索性能业务整体可接受,整体的资源成本也比ClickHouse会有更进一步50%以上的减少。
B站OLAP平台的引擎选择
所以总结下来,我们现在的OLAP引擎分为三个:ES、ClickHouse和湖仓一体。在引导业务用户选型的时候,我们首先按照业务类型,搜索排序使用ES,然后对于文本检索和数据分析,我们主要根据用户业务对于性能指标的需求进行划分,大于秒级以上建议湖仓一体,秒级以下用ClickHouse。
总结
首先,ClickHouse是一款非常优秀、综合能力非常强大的OLAP引擎,基本可以覆盖数据分析的各种场景的需求。
第二,在像日志这样的文本检索场景,ClickHouse是一个成本更低的选择,如果你也在做降本增效,这是一个我们验证过的可行方案。
第三,我认为湖仓一体至少目前无法取代ClickHouse这样的OLAP引擎,更多的是互补的关系,相比于ClickHouse,在部分场景下是成本更低的加速离线数据分析的方案。
最后,我们当前,像湖仓一体的Trino和ClickHouse还是独立的计算引擎,其实从技术组件维护的角度,合并为一个研发投入的成本会更低,现在如StarRocks、Doris都有往这个方向演进的趋势,甚至和ETL的计算引擎比如Spark也可以合二为一,这个也是我们后面比较感兴趣和想要研究的一个方向。