从 Clickhouse 到 Apache Doris:有赞业务场景下性能测试与迁移验证
作者|有赞 基础平台数据研发工程师 李闯
商家离线后台报表:面向 B 端为商家提供 T+1 报表查询,对计算精度、查询性能及稳定性要求较高,同时会面临复杂查询场景。 人群圈选与智能营销:从私域触点、线下触点获取用户数据,结合常用社交平台中接入的用户数据,根据业务需求在客户数据平台(Customer Data Platform - 以下简称 CDP)、数据管理平台( Data Management Platform -以下简称 DMP)、客户关系管理系统(Customer Relationship Management- 以下简称 CRM) 进行不同消费者的全方位画像分析。该场景会面临大量高频的数据实时更新,同时查询体量较大、QPS 较高,时常出现复杂 SQL 查询场景。 商家实时分析报表:面向 B 端为商家提供相关实时报表分析查询,该场景特点是 QPS 比较高,商家可以选择不同的维度组合进行查询,对实时性和稳定性要求高。 天网日志分析系统:为所有业务系统提供日志采集、消费、分析、存储、索引和查询的一站式日志服务。该场景写入吞吐高,需要达到每秒百万级别的数据写入;且查询频率低,涉及天网 TopN 日志查询,因此系统要求具备实时聚合以及模糊搜索能力。
早期架构如图所示,数据主要来源于业务数据库 Binlog 与用户日志等原始数据,通过实时与离线两条链路分别对数据进行处理。其中原始数据首先导入至 Apache Kafka 与 NSQ 消息中间件,一部分会通过 Apache Flink 进行流处理计算并与存储在 HBase 中的维度明细表进行关联,另一部分数据会存储于 Apache Hive 与 HDFS 中作为离线数据,通过 Apache Spark 计算写入至 OLAP 引擎中。
有赞数据架构主要使用了以下三种 OLAP 引擎,各个组件根据业务场景的特点与需求为上游应用提供不同场景的查询与分析:
Apache Kylin:基于 Apache Kylin 搭建商家离线报表后台,为商家提供 T+1 报表查询。目前后台已经具有超 500 万家的商家数量,对于部分体量较大的商家,其单点会员数能够达到千万级别、商品 SKU 达到数十万、平台构建 Cube 数量达 400+。
Clickhouse:基于 Clickhouse 进行人群圈选与 TopN 日志查询业务,其中人群圈选主要通过实时的明细查询来辅助用户行为数据分析。
Apache Druid:针对 B 端商家实时分析报表场景,基于 Druid 构建维度查询系统,为商家提供实时指标查询服务。
然而由于该架构组件过多、架构冗余等问题导致维养、开发、业务应用等方面带来了一系列的挑战,具体如下:
数据修复难度大:当出现 Apache Flink 自身容错导致数据重复的情况,Druid 完全依赖写入侧进行幂等操作,由于自身不支持数据更新或删除,只能对数据进行全量替换,导致数据准确性低、修复难度大。 数据一致性问题:对于 Druid 而言,导入数据后需要构建完 Segment 才能响应查询结果。一旦上游 Flink 写入 Kafka 的过程中出现数据延迟,则无法按照预期时间写入 Druid 中,指标数据就会出现较大波动,数据一致性无法得到保障。 数据修复链路过长、成本过高:为了解决部份临时数据修复问题,我们首先需要花费小时级时间将 Apache Kafka 数据备份至 HDFS 上,在备份完成后还需要将数据重新导入 Druid 之后进行修复,整体修复的链路过长,投入的时间与研发成本会随之升高。
Apache Kylin 在数据处理过程中采用了预计算的方式,通过在多维 Cube 构建过程中完成聚合计算,并生成 T+1 数据报表。对部分在夜间经营的商家而言,他们需要等待一天时间才能查看前一天的报表数据,这无法满足用户对于时效性的需求。
研发成本高:业务方需要学习每种组件(Clickhouse、Druid、Apache Kylin)的使用方式、并且查询 SQL 标准各异,这会使学习成本加大,并且在后期进行研发、监控、运维、周边生态工具等开发工作过程中,需要投入大量的人力与开发接入成本,降低开发效率。
运维瓶颈:在扩缩容期间业务方需要停写进行集群调整,且单次扩容需要将所有的库表都进行迁移,不仅无法保证运维时间成本,还会增加过高的人力成本。而目前有赞存在大量的扩容需求,现有架构的运维成本则成为系统的一大痛点。
架构灵活度差:Apache Kylin 仅在维度和指标提前设定、表结构固定的场景下能够正常运行,一旦增加维度和指标则需要新建 Cube 并重刷历史数据;Clickhouse 在宽表补数时会出现需要重新全量导入数据,这些架构缺陷在业务运行过程中都会引发资源使用增加、运维成本增加、研发效能较低的问题。
技术调研与收益成本评估
对于收益而言,我们需要评估新架构引入后的性能是否如预期提升,将 Apache Doris 分别与 Clickhouse、Druid、Kylin 进行对比评估。
对于成本而言,我们首先会考虑在替换过程中,周边工具开发的成本,其中涉及监控、告警、上下游依赖平台等一系列工具的构建与研发;其次业务的迁移会涉及大量业务改造与协调,如何催动业务方进行改造、提供更丰富的改造工具、尽可能降低投入成本也是我们主要考虑的问题。
经过一系列评估后,我们发现基于 Apache Doris 进行架构迭代,其不论是在业务赋能还是成本方面,都能够有效解决当前架构的痛点,极大程度地实现降本增效的目标,整体迭代的预期收益明显高于改造代价,因此我们决定基于 Apache Doris 构建统一实时数仓,具体评估分析如下:
查询性能优异:解决了 Clickhouse 在高 QPS 查询与大表关联查询场景下的弊端,提供了优秀的并发查询能力。此外,在 Apache Doris 2.0 发布后,倒排索引功能支持任意维度快速检索、文本分词全文检索,在日志场景下的查询性能提升尤为明显。
高效的数据更新:Apache Doris 的 Unique Key 支持大批量数据更新、小批量数据实时写入,覆盖我们 90 % 业务场景,其 Duplicate Key 与 Aggregate Key 模型还能够支持部分列更新,整体数据模型丰富,帮助提升写入与查询效率。
保证数据正确性:Apache Doris 支持事务导入,Bitmap 功能支持精准去重,保证数据不丢不重;同时支持精准写入,保证数据基表与物化视图强一致性、副本数据强一致性。
简单易用、开发成本低:Apache Doris 高度兼容 MySQL,使开发简单使用门槛降低,且 Doris 的迁移与扩缩容成本较低,在横向扩容等运维操作方面特别简单。其周边组件的接入与监控的接入皆相对简单,Doris 社区提供 Flink & Doris Connector、Spark & Doris Connector 等接入工具,并且监控模版能够直接取用,无需再开发。
社区活跃度高:在过往加入的开源社区中,Apache Doris 社区活跃度非常高,社区开发者多、迭代更新快,对于社区内的问题解答也十分积极,在开发过程给予了非常多的帮助。
如上图所示,新架构将基于 Apache Doris 搭建统一实时数仓,替换原架构中的三个 OLAP 组件,解决由组件过多引起的接入成本高、资源使用增加、数据链路过长等问题,最终能够减轻业务方的负担、减少整体框架的硬件成本、实现引擎与技术栈统一等目标。
在有赞绝大多数应用场景中,原架构都存在数据重复、数据延迟需要修复的情况,引入 Apache Doris 之后,我们将利用灵活的数据模型实现高效的数据更新,保证写入效率,并且 Doris 架构具备弹性伸缩的能力,引入后能够极大程度地降低故障发生的概率以及出现故障时数据恢复的效率。
此外我们还将引入 Apache Doris 以下功能:
倒排索引: Apache Doris 2.0 版本的倒排索引功能优化天网日志分析系统,实现多维度快速检索,加速日志场景的查询分析性能。
主键模型写时合并(Merge-on-Write):Apache Doris 提供丰富的导入方式,可以将小批量数据实时导入 Doris 中,为后续上架门店业务提供实时报表查询,与原价构使用对比,Doris 能够极大程度提升导入时效性。
在确定架构迁移之后,我们首先选择将 Apache Doris 来替换 Clickhouse 组件,主要由于在业务增长时 Clickhouse 查询性能瓶颈较大、集群扩缩容操作过于复杂等痛点使运维团队的工作量大幅增加,加之大表 Join 能力差、高 QPS 查询性能差等一系列问题无法满足业务方诉求,且 Clickhouse 功能与 Apache Doris 相似,业务方更便于迁移, 因此我们优先替换 Clickhouse 组件。
接下来,我们将分享 Doris 替换 Clickhouse 的迁移方案,架构迭代的整体节奏分为 SQL 语句改写实现自动导入(包含建表语句与查询语句的改写)、查询性能测试、稳定性测试、导入性能测试与优化,在结束一系列测试后最终进行整体业务迁移。
01 SQL 建表语句与查询语句改写
字段类型映射:由于 Doris 与 Clickhouse 字段不一致,存在一些特殊要求的转换,例如 Key 值类型 String 需要转为 Varchar 以及设置对应长度、分区字段 String 需要转为 Date V2 等; 动态分区表的历史分区数确定:因为部份表存在历史分区,需要在建表时指定分区数量,否则插入数据会出现 No Partition 异常; Buckets 数量确定:虽然历史分区表可以进行统一配置,但是往往历史分区数据量不完全一致,因此我们根据历史分区的实际数据量推算出历史分区的分桶数,同时对于非分区表可以根据历史数据量设置 Properties 进行自动分桶配置; TTL 周期确定:可以设定动态分区表的转换周期,设定保留时间后再转换; Unique 模型的 Sequence 设置:在导入时可以指定 Sequence 列导入顺序,解决了导入顺序无法确定的问题,有效保证数据导入过程中的有序性。
查询表名转换:在 Clickhouse 与 Doris 建表过程中存在一定的映射规则,在进行双跑测试的过程中,我们可以直接根据映射规则直接进行转换。 函数转换:由于 Clickhouse 与 Doris 使用函数差异较大,需要根据 Doris 和 Clickhouse 的函数映射关系进行函数映射转换。其中我们遇到一些比较特殊的函数转换需要进行特别处理,例如 Clickhouse 中的 COUNTIF() 需要转换为SUM(CASE WHEN _ THEN 1 ELSE 0) 以达到相同的效果,ORDER BY 与 GROUP BY 需要利用 Doris 开窗函数进行转化,此外 Clickhouse 利用 Array Join 进行列传行,对应 Doris 则需要利用Explode 、Lateral View 来展开; 语法层面的不兼容:由于 Clickhouse 不兼容 MySQL 协议而 Doris 高度兼容,因此在子查询中需要进行别名设置。特别是在人群圈选的业务场景中存在多个子查询,因此在售后转换的时候需要把对应子查询利用 sqlparse 进行递归,检查出所有的子查询进行设置。
02 Apache Doris 与 Clickhouse 性能压测
大表 Join 查询性能测试
全关联 40 亿:在 40 亿主表完全关联查询中,Doris 查询性能均优于 Clickhouse,且随着维表数据量级增大,Doris 与 Clickhouse 查询耗时差距越大,其中 Doris 最高能够达到 5 倍性能提升; 过滤指定店铺关联 40 亿:在过滤条件关联查询中,主表按照 WHERE 条件过滤后的数据为 4100 万,相较于 Clickhouse,Doris 在维表数据量小的情况下能够达到 2-3 倍的性能提升,在维表数据量大的情况达到 10 倍以上的性能提升,其中当维度数据表超过 1 亿后,Doris 依旧可以稳定查询,而 Clickhouse 由于 OOM 情况导致查询失败。 全关联 250 亿:在 250 亿 50 字段宽表作为主表完全关联时,Doris 查询性能依旧优于 Clickhouse,Doris 在所有维表量级中均能跑出,而 Clickhouse 在超过 5000 万后出现 OOM 情况; 与过滤指定店铺关联 250 亿:在条件关联查询中,主表按照店铺 ID 过滤数据为 5.7 亿,Doris 的查询响应时间均达到了秒级,而 Clickhouse 最快响应时间也需要分钟级耗时,在数据量大的情况下更是无法跑出。 全关联与过滤指定店铺关联 960 亿:不论是主表关联查询还是条件关联查询,Doris 均可跑出且响应速度较快,Clickhouse 则在所有维表量级中无法跑出。
03 Clickhouse 线上流量回放稳定性测试
通过定时采集 Clickhouse 最近 1 分钟的查询状态为 QueryFinish 的有效查询信息。 将查询信息上报至 Kafka,接着通过 Flink 消费 Kafka Topic 获取 Clickhouse 查询 SQL 并统计结果。 在 Flink 中实现 UDF 将 Clickhouse 查询 SQL 转化为 Doris 查询 SQL,并由 JDBC 执行。 获取执行结果与统计结果,与 Clcikhouse 执行信息进行对比最终存放至 RDS。 最终通过对线上 Clickhouse 查询流量回放的统计,分析 Doris 查询性能与查询数据准确性。
04 Doris 数据导入性能测试与优化
支持通过 Spark SQL 读取外部数据,通过 Stream Load 方式写入 Apache Doris; 支持通过 Spark Load 方式,利用 Spark 集群资源将数据排序操作在 HDFS 中完成,再通过 Broker Load 将数据写入 Doris; 支持 Doris Multi-Catalog 功能直接读取外部数据源并通过 Insert Into 方式写入 Doris。
根据上方测试结果,我们进一步分析各种导入方式的优势与后续调优方案,希望以下的调优实践能够帮助到有类似需求的开发者们:
[1] 相关 PR: https://github.com/apache/doris-spark-connector/pull/117
Spark Doris Connector 支持 Bitmap 数据导入
[2] 相关 PR: https://github.com/apache/doris-spark-connector/pull/119
未来规划与展望
目前,我们正在与 Clickhouse 线上双跑对 Doris 的稳定性进一步验证,同时我们正在对 Spark Doris Connector 导入方式的的进行性能优化、开发导入周边工具以完成组件替换等落地工作。后续在逐步完成 Clickhouse 的业务迁移后,基于 Clickhouse 的迁移经验,对未迁移的存量业务逐步完成 Druid、Kylin 两个组件的迁移,最终基于 Apache Doris 构建极速分析、实时统一的数据仓库。
在此非常感谢 SelectDB 技术团队的积极响应与专业解答,加速有赞业务的迁移进程,也希望通过这篇文章为准备进行架构迁移的企业提供相关实践经验和 OLAP 选型参考。最后,我们也会持续参与社区活动,将相关成果贡献回馈社区,希望 Apache Doris 飞速发展,越来越好!
参考 GitHub PR: [1] Spark Doris Connector 支持阻塞写入 https://github.com/apache/doris-spark-connector/pull/117 [2] Spark Doris Connector CSV 格式导入优化 https://github.com/apache/doris-spark-connector/pull/119 [3] Spark Load 创建 Hive 外表支持 Hive 版本设置 https://github.com/apache/doris/pull/20622 [4] Spark Load 系统环境变量获取优化 https://github.com/apache/doris/pull/21837 [5] HIve 外表属性在 Spark Load 不生效优化 https://github.com/apache/doris/pull/21881
- END-
欢迎更多的开源技术爱好者加入 Apache Doris 社区交流群,携手成长,共建社区生态。Apache Doris 社区当前已汇集了上万名开发者和使用者,承载了 50+ 交流社群,如果你也是 Apache Doris 的爱好者,非常欢迎您的加入!