快手:从 Clickhouse 到 Apache Doris,实现湖仓分离向湖仓一体架构升级
导读:快手 OLAP 系统为内外多个场景提供数据服务,每天承载近 10 亿的查询请求。原有湖仓分离架构,由离线数据湖和实时数仓组成,面临存储冗余、资源抢占、治理复杂、查询调优难等问题。通过引入 Apache Doris 湖仓一体能力,替换了 Clickhouse ,升级为湖仓一体架构,并结合 Doris 的物化视图改写能力和自动物化服务,实现高性能的数据查询以及灵活的数据治理。
作者|快手大数据架构师 李振炜、曾斯维、周思闽
在当今这个数据洪流的信息时代下,数据已跃升为企业不可或缺的核心资产。深度挖掘并提炼数据内在价值,成为支撑企业战略决策的重要依据。在此背景下,快手建立了 OLAP 系统,该系统在快手应用极为广泛,每天承载近 10 亿的查询请求,为内外多个业务场景提供数据服务。具体场景包括:
-
ToB 系统:商业化报表引擎、商业化 DMP、商业化磁力金牛、电商选品等
-
内部系统:KwaiBI、春节/活动大屏、APP 分析、数据同步、用户理解中心、APM、CDN 监控、雷达监控系统等
存在的问题
最初,快手 OLAP 系统整体技术架构由离线数据湖和实时数仓这两部分组成,离线数据湖核心引擎为 Hive/Hudi,实时数仓核心引擎为 ClickHouse。OLAP 系统数据源种类非常丰富,全面覆盖结构化、半结构化、非结构化的数据类型,这些数据同步到到数据湖进行 ODS 、 DWD、DWS、ADS 层处理,处理后的数据同步至实时数仓,由数仓对外提供 BI、报表、查询等数据服务。
虽然这套架构已在快手内部稳定运行许久,但随着需求的变化、数据的不断累积,湖仓分离这一架构带来的问题逐渐显现:
- 冗余存储:虽然将数据入仓到 ClickHouse 能够提高查询性能,但同时导致了数据冗余存储,影响数据就绪时间和存储效率。
- 资源占用: to ClickHouse 过程也会占用 ClickHouse 集群资源,不仅体现在 Clickhouse 中数据同步系统的资源消耗,且在数据写入后,ClickHouse 内部也有 Compaction 等计算资源的消耗,在高并发查询的时候,并行读写带来更显著的资源抢占问题,可能影响其他查询任务的执行效率和集群的整体性能。
- 治理复杂:数据工程师需要投入大量人力建立 ADS 层模型,以支持 to ClickHouse 导入工作,并需要投入额外的精力维护导入任务。此外,当报表看板下线后,对应 ADS 层和 to ClickHouse 任务还在运行,造成计算和存储资源的不必要浪费,大部分情况下需要依赖人工介入判断并终止这些任务,这无疑增加了运维管理的复杂度。
此外,随着 ClickHouse 在业务中使用越来越深,查询性能出现瓶颈。排序字段、二级索引、物化视图以及哈希字段的选择和创建对 Clickhouse 查询性能有显著影响,但局限于 Clickhouse 的语言、系统适配性等因素,开发人员的学习及操作门槛都比较高,查询调优的难度比较高。
升级目标及选型
在上述问题驱使下,快手希望引入湖仓一体架构来解决上述问题,希望数仓可直接分析湖中数据,而不需要进行繁琐复杂的数据传输,避免传输及传输过程中引发的数据问题。在该目标的指引下,快手快速锁定 Apache Doris,尽管 Apache Doris 定位于实时数据仓库,但在过去版本中 Apache Doris 一直不拘于数据仓库的能力边界,在湖仓一体方向进行了诸多探索,逐步形成了湖仓一体解决方案:
- 极致分析性能、助力湖仓查询加速 : 借助强大的分布式 SQL 查询引擎,Apache Doris 对 Parquet、ORC 等开发格式进行了深度适配。凭借灵活的缓存策略和物化视图能力,用户可直接在湖仓数据之上架设 Apache Doris,从而实现高吞吐和低延迟的数据分析能力。
- 多源联邦分析、消除数据孤岛 : Apache Doris 提供丰富的数据源连接器,可以对各种异构数据源如 Hive、Iceberg、Hudi、关系型数据库进行统一的元数据管理和映射,并可通过标准 SQL 语言进行联邦分析。
- 湖仓数据无缝集成、自由流转 : 结合 Doris 异步物化视图能力和内置作业调度功能,用户可以便捷的基于 Doris 对湖仓数据进行分层加工处理,从而简化湖仓数据处理的复杂度。
- 统一数据湖的构建和计算引擎 : Apache Doris 支持主流湖仓的数据写入能力,用户可以基于 Doris 进行统一的数据写入、处理及分析,形成湖仓一体架构下的链路闭环。同时,基于 Arrow Flight 协议的高速数据通道,也进一步提升了 Doris 数据格式的开放性,用户可以在 Doris 上使用其他计算引擎支撑更丰富的计算负载。
基于 Apache Doris 的湖仓一体架构
快手基于 Apache Doris 升级为湖仓一体分析平台,新架构如图所示:
从下至上,主要分为以下几个层级:
-
数据加工层:数据源数据同步到数据湖仓(Hive/Hudi),并在湖仓系统中完成从 ODS 层到 DWS 层的加工处理, DWS 层到 ADS 层依赖自动物化服务实现(后文详细介绍)。
-
数据缓存层:ADS 层数据缓存到 Alluxio 中,以提供高性能的数据缓存访问能力。同时,元数据也缓存到元数据管理服务中,以提供统一的、稳定的元数据访问服务。
-
数据查询层:基于 Apache Doris 提供对 ADS 层数据的高性能查询服务。
另外,快手还开发了两个服务,使得整个分析平台更加智能化和自动化:
- 查询路由服务:通过分析和预估查询的数据扫描量,将超大查询自动路由到 Spark 引擎,以避免大查询占用过多的 Doris 资源。
- 自动物化服务:提供 DWS 层到 ADS 层按需自动化加工,智能选择排序字段、哈希字段以及更合理的物化,从而实现对复杂查询或高优看板数据的智能查询加速
在数据查询层中,使用 Apache Doris 替换原先的 Clickhouse,为快手带来了如下收益:
- 统一存储、简化链路: Doris 可以直接访问湖仓数据。Hive ADS 层的数据不再需要额外导入和存储在 OLAP 系统中,降低了数据维护和存储的成本,同时缩短了数据链路,提升了数据时效性。
- 查询性能提升: Apache Doris 本身作为高性能的计算引擎,查询性能已有较大的提升。同时结合 Doris 自身的物化视图透明改写能力,以及基于代价的查询优化器能力,可以满足不同数据分析场景下的查询加速需求。
- 更灵活的数据治理:结合 Doris 的物化视图改写能力和自动物化服务,可以更方便的在 ADS 层进行视图建模,对业务层屏蔽屏蔽复杂的底层实现逻辑。
接下来重点介绍整个湖仓一体架构中,缓存服务和自动物化服务方面的功能和实践经验。
缓存服务与优化
湖仓一体架构下,缓存层主要用于避免远程数据访问可能发生的网络延迟高、网络抖动、带宽不足等问题,提升数据访问的性能和稳定性。缓存的内容大致可以分为元数据缓存和数据缓存。针对这两种缓存,快手结合 Doris 的系统架构和特性做了适配和优化。
01 元数据缓存
元数据缓存的内容包括库、表、列、分区、文件等元信息。而且元数据缓存服务,除了对缓存信息的存储外,更重要的是能够及时感知元数据的变化,如 Schema 的变化、分区的增删,文件的变更等等。通过实时的变化感知,才能保证元数据的一致性和查询的时效性。
Apache Doris 内置支持了元数据的缓存能力,支持基于 Hive Metastore Event 的元数据增量同步能力,能够很方便的提供高性能、高时效性的元数据缓存服务。然而,由于快手的元数据服务需要与除 Hive Metastore 外的其他系统(如 Alluxio)进行交互,因此无法单纯依赖 Doris 内置的元数据缓存能力。为此,快手基于 Doris 的内置方案自主研发了 Meta Server,作为统一的元数据服务。
如上图所示,Meta Server 负责监听其他服务中的元数据变化,如 Hive Metastore 中的 Schema 变化,Alluxio 中的缓存变化等,并将这些信息持久化到后端的 Meta Store 中。同时,Meta Server 会将元数据定期的推送给 Doris FE 的 Catalog 中,并通过元数据校验服务来保证两边数据得一致性。
此外,针对分区信息缓存策略,快手也结合 Alluxio 的缓存能力进行了改造。
如上图所示。Meta Server 监听到分区变化后,从访问 HDFS 获取最新的文件(Split)列表,持久化存储到 Meta Store 中,并通知 Alluxio 进行缓存预热。而 Doris 在查询时,可以直接访问 Meta Store 获取文件列表。
通过 Meta Server 服务,实现了查询引擎(Doris)、元数据服务(Hive Metastore)、数据缓存服务(Alluxio) 三方联动,在提供高效的元数据访问的同时,也提升了数据缓存的时效性。
02 数据缓存
数据缓存服务支持数据内容以文件形式存储在缓存系统中,以提供本地化的数据访问性能。
Doris 支持内置和外置两种数据缓存服务。内置数据缓存不依赖第三方系统,可以提供开箱即用的缓存能力。而快手为了能够将缓存服务能够和公司内部的其他业务系统更灵活的对接,采用了基于 Alluxio 的外置数据缓存服务,其对外提供 HDFS 兼容的 API,使得 Doris 可以很方便的对接到 Alluxio 文件系统上,像访问 HDFS 一样访问 Alluxio 中的文件。
同时,为了减少数据访问的延迟,快手在上述基础上重点开发了缓存预热功能。缓存预热是非常重要的步骤,预热后的数据存储在 Alluxio 中,提高了查询响应速度。
上一节已经介绍了缓存的元信息如何存储到 Meta Store 中。Doris 会根据查询语句中的分区条件,从 Meta Store 中获取文件列表,并通过 is_cached
字段判断数据是否已经被缓存,并自动选择从 Alluxio 或者 HDFS 中读取。
03 优化效果
通过元数据缓存,与直接从 Hive Metastore 和 HDFS 中获取相比,平均耗时由 800 毫秒降低至约 50 毫秒 ,并且查询耗时并未出现较大波动。
自动物化系统
物化视图是数据仓库系统中的一项重要能力,不仅能够提供数据的分层加工功能,还可通过透明改写实现智能查询加速。快手在内部一直使用物化视图,但初期面临数据加工链路复杂和治理成本高等问题。经过深入分析,发现根本原因在于 ADS 模型的生产和消费由不同角色负责:
- ADS 模型消费:主要由运营、产品、数据分析和业务研发团队使用,这些需求与业务场景深度耦合,呈现出多样化的特点。
- ADS 模型生产:由数据工程师负责,他们需要深入理解业务需求,设计相应的 ADS 模型并进行生产加工,这给工程师带来了较大的理解成本。
因此在数据的分层加工上,快手自研了自动物化服务,实现消费驱动生产,主要流程如下:
- 数据工程师将所有数据直接交付至 DWS 层。
- 消费者访问 DWS 层数据表,并基于 DWS 层进行看板和报表的配置。
- ADS 层实现自动物化,提供实际的数据访问,物化视图的生产逻辑对数据工程师屏蔽,全部托管在计算引擎 中。
- Doris 根据查询自动选择最优的物化视图,从而实现查询加速。
物化视图包括构建、刷新和改写流程,Doris 本身支持相对完善的物化视图功能。但快手选择在外部系统(自研自动物化服务)中实现构建和刷新操作,由 Doris 支持透明改写能力,而非全部采用 Doris 能力,主要原因如下:
- 物化视图的生成需要大量计算资源。快手每天处理的数据量庞大,涉及数十万张表、数百 PB 的数据增量。如果全部由 Doris 处理,将消耗大量计算资源。因此,利用现有的计算集群资源(如 Spark)可以有效降低计算成本。
- 高优看板数据就绪需要 SLA 保障,因此物化视图的调度必须和其他系统交互,使用 Doris 内置调度方案需要对内核修改,具有较高的侵入性。
- 出于整体系统设计考虑,物化视图必须统一闭环到数据湖上,因此其数据必须保存在外表中。而 Doris 的物化视图目前是通过内表形式存储,以确保最佳查询效率。
- Doris 内置的物化视图透明改写能力在未命中物化视图时,会降级到原表查询。然而,快手的降级策略要求将这类查询转向 Spark,以避免占用过多的 Doris 计算资源,因此需要引入额外的代码逻辑。
基于以上考虑,快手结合 Doris 的物化视图透明改写能力和自研的物化服务,实现了 KwaiMTMV 自动物化系统。该方案的优势如下:
- 充分利用 Doris 优化器的视图改写能力,实现灵活的查询改写功能。
- 利用外部系统实现物化视图生产、管理等过程,使得 Doris 能够更合理的利用计算资源,灵活对接内部服务。
接下来分别从物化发现、物化生产和物化消费这三个方面,系统的介绍 Apache Doris 和物化视图的结合使用。
01 物化发现
物化发现旨在利用不同方式,为用户推荐合理的物化视图。这里采用了如下两种方式:
-
专家规则:用户主动提供一些维度、指标列的定义,或通过 SQL 样例推导指标列和维度列。
- 维度定义,如
city
,gender
等。 - 指标定义,如
sum(time)
,count(distinct uid)
等。 - SQL 样例,如
select sum(time),count(distinct uid) from db.tbl group by city, gender
;
- 维度定义,如
-
分析历史查询:基于历史查询,自动发现物化定义。
- 识别出全局高频查询算子进行物化,尽可能提高物化复用率。
- 除了聚合指标维度,还可以自动设置索引、引利分桶字段等。
通过人工和系统自动分析两种方式,可以得到合理的物化视图定义。同时也会将这些物化视图定义存储在 Meta Server 中,并和对应的基础表做关联,以便后续的物化生产和消费。
02 物化生产
基于物化发现得到的物化视图定义,将提交至作业调度平台进行构建。调度平台会根据基表数据的变化和血缘关系,自动调度物化生产任务。同时,它还会自动调整物化生产作业的优先级,确保高优先级任务按时完成。
此外,调度平台中实现了一批 Java UDF,能够将某些指标的聚合中间结果序列化为二进制格式并写入数据文件(如 Parquet/ORC)。Doris 还可以复用这些 UDF,在查询时对二进制数据进行反序列化,从而完成对指标列的上卷运算。
03 物化消费
物化消费,主要分为物化视图注册和物化视图改写阶段。
如上图所示,在物化视图注册阶段,Doris 会定期从 Meta Store 中同步生成的物化视图信息,并在 Doris 中注册类型为 KwaiMTMV
的物化视图对象。在查询改写阶段,扩展了 Doris 的物化视图改写能力,使其能够识别KwaiMTMV
类型的物化视图,并进行查询改写。
如下是对 author_id
指标进行去重的外表查询的改写结果示例。可以看到 Doris 将 count distinct
算子改写为了更高效的 Bitmap 算子,并将目的表改写到了合适的物化视图表上。
04 使用效果
引入自动物化系统后,不仅加快了数据模型的交付速度,还显著提升了查询效率。以下是实际测试 SQL 的提升效果:
上图直观展示“数据行数”和“查询耗时”这两个维度在物化前后的对比。百万到百亿级别的数据经过物化处理后,行数压缩至少达 11 倍。在查询耗时方面,针对百亿级别以下的数据,物化后均可实现毫秒级响应,查询性能相比物化前提升至少 6 倍。
湖仓数据查询优化
除缓存服务和物化视图服务外,快手在实际使用过程中总结了一些湖仓查询的优化经验:
- 外表统计信息:统计信息对查询规划尤为重要,尤其是在复杂关联查询中。然而,外表统计信息存在收集成本较高,各数据源的统计信息类型和统计口径各不相同的问题。为解决该问题,可在 Spark 进程处理湖仓数据时,同步收集统计信息并将其存储在 Meta Server 中,Doris 可以直接访问这些统计信息,从而为查询优化器提供最优的查询计划。
- 有序文件和合适的 RowGroup 大小:在构建 Parquet 数据时,按主键排序可以确保文件数据有序。这使得谓词下推时能够最大程度地过滤无用的 RowGroup。同时,指定合适的 RowGroup 大小可以有效提高 RowGroup 的过滤率。
- 使用 Bucket 表:湖中的表可以按规则生成分桶(Bucket)表。Doris 利用分桶信息生成 Colocation Agg 和 Colocation Join 等优化的分布式执行逻辑,避免大量的数据 Shuffle,从而提高查询效率。
结束语
引入 Apache Doris,使快手成功从湖仓分离架构升级到湖仓一体架构。通过 Apache Doris 替换 Clickhouse,实现了统一存储和链路简化,无需数据导入、Doris 能够直接访问湖仓数据。同时,结合 Doris 的物化视图改写能力和自动物化服务,可实现高性能的数据查询以及灵活的数据治理。后续,快手将会进一步探索 Doris 在湖仓一体下的应用实践。具体包括:
- 公司内部的看板、报表场景将逐步由 Hive to Clickhouse 替换为 Doris 湖仓一体架构,以提升数据处理效率和查询性能。
- 即席查询(AD-Hoc)场景将逐步从 Presto 迁移到 Doris,并将所有分析引擎统一为 Doris,以简化技术架构并增强数据分析能力。