核心内容摘要
《长门和小南的奇幻萝卜大战:一场超越时空的漫画冒险》
本文整理自快手高级计算引擎研发工程师 周思闽 在 Doris Summit 2025 中的演讲内容并以演讲者第一视角进行叙述。
快手是国内日活过亿的短视频平台其广告投放平台是商业化外部广告主与快手电商商家进行广告投放的主要阵地支持客户在平台上进行广告物料搭建、物料管理、策略变更、数据查看等操作这对底层数据系统的存储、计算与查询性能提出了极高要求。
要支撑如此大规模的广告投放与实时分析底层数据架构面临巨大挑战。
当前快手的广告数据包括由投放系统产生的物料数据以及用于数据分析的效果数据这些数据呈现出三个显著特征数据存量巨大广告物料累计已达千亿级别且随业务发展正向万亿规模迈进存储体量位居公司前列对架构扩展性提出极高要求。
数据增长迅猛仅 2025 年第一季度日均新增广告物料数据同比激增
5 倍要求底层引擎具备强大的实时写入与弹性扩展能力。
数据模型复杂整个数据体系涵盖约 700 个核心字段涉及物料、投放、用户、效果等多个维度同时为应对多样化分析场景沉淀的查询模板已超 4000 个对查询引擎的兼容性与性能均是严峻考验。
架构演进从分散存储到统一分析01 早期架构及挑战早期存储架构中物料数据由 MySQL、Elasticsearch 协同存储效果数据主要存储与 Clickhouse 中。
数据分析时将分散在 MySQL、Elasticsearch 中的物料数据与 ClickHouse 中的效果数据进行高效关联查询从而为广告主提供完整、及时的投放效果洞察。
在如上所说的 ClickHouse on ES 架构中用户提交的查询通常包含 Elasticsearch 外表a与 ClickHouse 内表b。
ClickHouse 会解析查询中外表部分将其转换为 Elasticsearch 查询语句通过 HTTP 请求获取数据并封装为 Block最后在引擎内部完成与内表的关联计算。
然而随着 Elasticsearch 中数据量持续增长该架构逐渐暴露诸多问题查询性能恶化慢查询率上升至 35%平均查询耗时达到
4 秒存储瓶颈Elasticsearch 单分片难以支撑 10 亿级以上数据量扩容与数据重分布成本高运维复杂度高数据链路依赖组件多运维与监控成本显著上升问题定位困难缺少 ClickHouse 与 Elasticsearch 之间的全链路可观测手段出现查询延迟、数据不一致等问题时需跨系统排查耗时较长。
02 选型目标及调研基于上述问题及挑战我们为新架构设定了明确目标慢查询率低于 5%运维排查耗时降低至分钟级支持单表万亿级别数据存储保障数据实时性延迟低于 5 分钟。
基于以上目标我们对 Apache Doris、ClickHouse、Elasticsearch 等主流 OLAP 引擎进行了全面的调研与性能压测。
测试涵盖了写入吞吐、查询延迟、存储压缩率、全文检索性能等关键维度。
在这过程中ClickHouse 首先被排除因其不支持唯一键模型而广告物料数据存在大量更新场景要求引擎具备主键更新能力。
因此重点在 Elasticsearch 与 Apache Doris 之间进行对比。
综合测试结果Apache Doris 在写入性能、查询效率、存储成本及运维复杂度等方面均表现优异不仅能够满足既定架构目标还在多个场景下显著优于 Elasticsearch。
因此我们最终选定 Apache Doris 作为下一代广告数据分析引擎。
03 基于 Apache Doris 的统一分析引擎在实际应用中我们引入 Apache Doris计算引擎 替换了原先架构中的 Elasticsearch、ClickHouse设计了统一分析引擎 Bleem。
通过在外部表模块中引入数据缓存层与元数据服务层有效提升了跨源查询效率使数据湖外表的查询性能接近内表水平实现了关键的性能突破。
具体来看Bleem 架构自下而上分为 5 层存储层数据湖中的 Hive/Hudi 数据存储于 HDFS存算分离模式下的内表数据存放于对象存储 BlobStore存算一体模式下的内表数据则存储于本地磁盘。
缓存层将 Hive/Hudi 外部表数据缓存至 Alluxio保障 I/O 稳定性提升数据读取效率。
计算层Apache Doris 为核心引擎。
不同项目组对应不同的 Doris 集群以实现计算资源物理隔离用户可按需申请计算资源。
依托于 Doris 湖仓查询能力可直接对 Doris 内表与外部 Hive/Hudi 数据查询。
同时Doris 也支持存算一体与存算分离两种部署方式可根据实际需求灵活选择。
服务层元数据缓存服务实时监听 Hive 元数据变更并同步至缓存中以提升湖仓外部表的查询效率。
接入层将 OneSQL 作为统一查询接入网关提供集群路由、查询改写、物化改写、查询鉴权、限流与阻断等功能。
依托 Doris 强大的 OLAP 计算与湖仓一体能力将此前分散的数据湖分析、实时 OLAP 查询、在线报表及全文检索等多种场景统一整合至同一套引擎架构中实现了技术栈的收敛与提效。
该架构在实际落地中已带来显著收益性能大幅提升慢查询率低于 5%整体查询性能提升了20%90%存储扩展高效支持万亿级别数据存储水平扩容效率较 Elasticsearch 提升10 倍以上运维大幅简化一套引擎覆盖全部查询场景系统依赖组件少运维复杂度显著降低可观测性全面加强Doris 支持全链路追踪与全面监控平均问题排查时间降低 80%。
迁移实践及调优经验整个迁移过程分为三个阶段稳步推进以确保业务平稳过渡第一阶段试点验证选取关键词推广场景进行试点跑通全量与增量数据导入流程搭建双链路并行验证数据一致性与查询正确性。
第二阶段主体迁移迁移原 ClickHouse on ES 查询链路将 Elasticsearch 中全量物料数据导入 Doris完成业务切换后下线 Elasticsearch 集群。
第三阶段收尾统一迁移剩余纯 ClickHouse 场景将无需关联 Elasticsearch 的查询任务及其数据全部迁移至 Doris完成整体架构统一。
在架构升级及迁移过程中我们收获了许多实践及优化经验在此逐一分享。
01 解决极端场景下数据一致性问题在数据导入层面我们基于 SeaTunnel 实现流式数据同步该方式支持批处理场景下的 Overwrite 语义所有导入均采用两阶段提交机制以确保数据同步的最终一致性。
而在基于 SeaTunnel 和 Spark 的数据同步过程中我们遇到了极端场景下的数据重复问题。
主要有两种情况Spark 推测执行时两个 Task 同时写入同一份数据并均完成 Doris 两阶段提交尽管 Driver 只认定一个 Task 成功但数据已重复。
Spark Task 完成 Doris 提交后在向 Driver 汇报前因抢占或异常退出Driver 重启 Task 并重新写入数据。
为解决该问题我们在 Doris 的两阶段事务提交环节引入了 ZooKeeper 分布式锁机制通过记录并校验事务状态来保证批同步的一致性。
具体流程如下准备提交阶段先获取 ZooKeeper 临时锁确保同一时间只有一个事务进入提交流程获取锁后将 Prepare 状态写入 ZooKeeper 临时节点并记录当前事务 ID查询上一个事务的状态若不存在直接提交当前事务若上一事务处于 Prepare 状态则先回滚上一事务再提交当前事务若上一事务已 Commit则直接回滚当前事务最终将 Commit 状态写入 ZooKeeper 持久节点完成本次提交。
02 Stream Load 机制优化为应对高并发数据导入我们对 Apache Doris 的 Stream Load 机制进行了调优。
通过合理配置任务优先级与合并Compaction参数显著提升了写入吞吐与稳定性。
Doris 内部通过Load Channel进行任务调度以区分高优与普通优先级通道。
调优的核心在于合理配置相关参数例如当 Stream Load 任务指定的timeout时间小于 300 秒时系统会将其判定为高优任务并分配至高优通道。
参数优化如下load_task_high_priority_threshold_second300 compaction_task_num_per_fast_disk16 max_base_compaction_threads8 max_cumu_compaction_threads803 差异化的建表策略OLAP 引擎的查询性能很大程度上取决于表结构设计。
因此我们针对不同业务场景制定了差异化的建表策略物料表高频更新与大规模检索该表数据量极大且需支持实时更新。
业务查询主要基于account_id进行过滤而非原 MySQL 的自增 ID。
为充分发挥 Doris 前缀索引与排序键的优势在保证业务逻辑等价的前提下我们将account_id与id组合为联合主键并将account_id设为首个排序键及分桶字段大幅提升查询过滤效率。
同时配置倒排索引以支持多维检索并选用ZSTD 压缩算法平衡存储与 IO 性能。
-- 建表语句参考 CREATE TABLE ad_core_winfo (account_id BIGINT NOT NULL, id BIGINT NOT NULL, word STRING, INDEX idx_word (word) USING INVERTED...) UNIQUE KEY(account_id,id) DISTRIBUTED BY HASH(account_id) BUCKETS 1000;效果表多维聚合分析 相较于物料表效果表侧重于数仓指标的累加与聚合。
因此我们直接采用聚合模型并按照“天”或“小时”粒度设置分区。
-- 建表语句参考 CREATE TABLE ad_dsp_report (__time DATETIME, account_id BIGINT, ... ad_dsp_cost BIGINT SUM, ...) AGG KEY(__time,account_id,...) AUTO PARTITION BY RANGE(date_trunc(__time,hour))() DISTRIBUTED BY HASH(account_id) BUCKETS 2;04 大账户数据倾斜治理在数据压测中我们发现不同 Account ID 对应的数据量差异极大小至个位数、大至百万级别导致 BE 节点 CPU 负载严重不均。
通过SHOW DATA SKEW命令进一步确认Tablet 存储分布明显倾斜大 Tablet 占用空间达 3–4 GB小 Tablet 仅
MB且大账户查询延迟较高。
为此我们实施了以下两点优化A按账户范围进行分区经分析Account ID 为 5–8 位数字且未来不会超过 10 位。
因此使用FROM_UNIXTIME函数将 Account ID 转换为 Datetime 类型按月对历史数据进行分区共划分出 33 个历史分区。
每个分区可容纳 2,592,000 个 Account ID后续每新增约 200 多万个 Account ID 才会新增一个月份分区。
同时针对历史分区根据数据存量进行手动分桶新分区则默认设置为 256 个分桶。
该方案通过分区裁剪有效过滤了大量无关数据同时为未来数据膨胀预留了扩展空间物料表日均增量约 3 亿显著降低分区增长对查询性能的影响。
B对 Account ID 进行二次哈希为缓解单个 Account ID 数据量差异过大导致的分布不均我们选取与 Account ID 无关的ID字段通过ID MOD 7计算得到一个取值在 06 之间的mod字段。
将原本仅基于account_id的哈希分桶键调整为(account_id, mod)联合键从而将同一 Account ID 的数据分散到 7 个 BE 节点上。
优化后各 Tablet 大小基本均衡稳定在 1GB 左右数据存储与查询负载得以在多个 BE 间均匀分布有效解决了 此前 CPU 负载不均的问题。
05 万级分区下的查询优化当分区数量达到万级别时简单点查 SQL 的耗时达到 250 毫秒远超 100 毫秒的预期。
通过分析耗时主要集中在 Plan 阶段原因是 Doris
1 版本在分区裁剪时会遍历所有分区进行匹配万级分区的顺序遍历开销巨大。
为此我们将顺序遍历改为二分查找对万级分区先进行排序再利用二分查找快速定位目标分区将时间复杂度从 O(n) 降至 O(log n)。
优化后该查询耗时从 250 毫秒降至 12 毫秒性能提升超过 20 倍。
目前二分查找已在 Doris
1 版本中实现。
06 并发调优在查询优化过程中我们发现多数查询经过条件过滤后实际命中的数据量并不大即便在大账户场景下命中数据量也仅在百万级别。
然而Profile 显示这类查询的 Total Instance 数高达 800 个其默认并发数为 32存在明显的过度并发。
为此我们调整以下参数降低并发开销set global parallel_exchange_instance_num5; set global parallel_pipeline_task_num2;调整后同一查询的 Total Instance 数量降至 17 个查询耗时也显著缩短。
这说明在小数据量点查场景下适当降低并发可有效减少 RPC 开销从而降低延迟220ms 降至 147ms。
同时这一优化也提升了系统的整体 QPS 承载能力。
收益及规划经过上述架构迁移与深度优化我们在三个核心维度取得了显著收益查询性能大幅提升关键词推广页平均查询延迟下降 64%创意推广页延迟下降超过 90%整体查询体验实现跨越式提升。
写入能力显著增强单节点写入承载能力提升 3 倍以上单表实时导入峰值突破300 万行/秒。
存储效率优化明显通过分区策略与 ZSTD 压缩算法存储效率较 Elasticsearch 提升约 60%并可轻松支撑万亿级数据存储。
未来我们将深度探索 Apache Doris 重点围绕两方面展开增强全文检索与分词能力引入社区在 Doris
0 版本中推出的 BM25 打分功能以及 IK 分词器等更多分词组件实现按业务场景灵活选用最优分词方案。
增强向量索引基于 Doris