ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

TPC-DS基准测试:ClickHouse与Doris OLAP性能对比实战

TPC-DS基准测试:ClickHouse与Doris OLAP性能对比实战

做OLAP数据库选型,最怕的就是厂商自己掏出来的那份压测报告——查询速度一个比一个快,资源占用一个比一个省,真到自己业务上一跑,数据量一上去,才发现根本不是那么回事。所以我一直坚持拿统一的标准来测,TPC-DS就是目前行业里公认比较能还原真实数仓分析场景的基准测试。这篇文章把我用TPC-DS分别测ClickHouse和Doris的完整过程、踩过的坑、以及最终对比结果都整理出来,给正在做数据库选型或者刚好要搭离线/实时分析平台的朋友一个参考。文章不会只贴结论,重点是讲清楚每一步为什么这么做,以及那些标准文档里不会告诉你的细节。

1. 测试方案整体设计

1.1 为什么是TPC-DS而不是TPC-H或自建场景

很多人一提起数据库性能测试,第一反应就是TPC-H,因为网上资料多、工具也顺手。但TPC-H的数据模型是一个相对简单的星形结构,表之间关联关系有限,查询模板也更偏向传统的报表型SQL,它对当下OLAP引擎的参考价值已经打了折扣。TPC-DS就不一样,它模拟的是零售行业数仓的完整业务链路,包含7张事实表、17张维度表,整体拉出一个雪花模型,查询涉及复杂的多表join、子查询嵌套、窗口函数叠加、大量CASE WHEN逻辑,还有近似查询、半结构化数据解析等场景。可以说,现实数仓中会遇到的SQL复杂度,TPC-DS基本都覆盖到了。

我最初也考虑过用业务自建场景来压测,但自建场景存在两个问题:一是SQL是业务方自己定的,很难做到客观;二是测试脚本和数据规模都不可控,复现性差。TPC-DS的好处在于数据生成工具和查询模板都是公开的,数据量可以通过参数自由伸缩,SQL预置了99条,只要把环境搭好、把数据灌进去,大家跑的就是同一套标准,改不了也逃不掉。所以拿它来衡量ClickHouse和Doris的查询能力,公平性是有保障的。

1.2 测试环境与数据规模选型

测试环境这块,我用的是一台中等配置的物理服务器,固定不变地跑两组测试,避免因为环境差异导致结果失真。具体配置如下:

配置项参数
CPU2路,每路32核心,支持超线程
内存256 GB DDR4
存储4块NVMe SSD组RAID-10,容量2 TB
操作系统64位Linux,内核5.4+
数据库版本ClickHouse 24.x / Doris 2.1.x

数据规模我选的是SF-100,也就是100GB原始数据,经过建模压缩之后实际落盘会比这个小一些。为什么不上SF-1000?因为100GB已经足够让两个引擎在分布式、列存压缩、向量化执行上拉开差距,而1TB数据对环境的压力主要集中在存储带宽和内存容量上,对于中等配置的机器反而会掩盖查询引擎本身的计算能力差异。如果条件允许,建议至少跑一遍SF-100,小于这个规模的数据量差异很容易被内存缓存抹平,测出来的性能曲线参考价值不大。

1.3 工具链与测试流程规划

需要准备的东西有:tpcds-kit(TPC官方数据生成与查询模板工具)、对应数据库的客户端、以及一套用来穿插查询的计时脚本。我用的是tpcds-kit自带的dss工具生成数据,数据格式为标准CSV,再用脚本统计每张表的行数做校验,防止生成中断导致的数据缺失。

测试流程拆成四个阶段:第一步建库建表;第二步批量导入数据并记录耗时;第三步逐条跑SQL,同时记录单查询耗时和失败率;第四步分析结果,对比资源消耗。这里有一点必须提醒:查询模板不能一股脑跑完就算完事,每次跑之前要把系统缓存清理掉,确保冷缓存下真实反映磁盘读取和计算能力,避免后面一次查询的结果被缓存撑起来、看起来漂亮但实际业务场景中根本复现不了。

2. 数据准备与导入过程

2.1 数据生成细节与校验

数据生成使用的是tpcds-kit工具,它的核心指令比较简单,指定缩放因子和目录就能生成整套CSV文件。实际耗时取决于单机并行度,SF-100在32线程下大概跑了40分钟左右。生成过程中有两个容易忽略的点:

第一,生成数据时建议用一致性参数把数据分布固定下来,保证两张库读到的是同一套数据,而不是各自随机生成一番,那样对比就没有意义了。第二,落到磁盘上的CSV文件不需要做清洗,保持原始格式,因为TPC-DS的schema设计里本身就包含空值和不规则字段,这些数据正好用来测试两个引擎对脏数据的容忍度。

数据生成完之后一定要做行数校验,不能想当然认为工具跑完就一定完整。我当时的做法是把每张表CSV的行数统计出来,和官方文档里的row count对照表核对。实际踩过一个坑:中途磁盘空间满了,工具自动退出,但没报错,小表生成了一部分就停了,导入时才发现数据缺了一大块。这种问题在导入后排查很痛苦,所以前置校验这个步骤不能省。

2.2 两张库的建表差异

ClickHouse和Doris虽然都是分析型数据库,但建表思维完全不在一个频道上。ClickHouse走的是MergeTree家族,建表时需要显式指定排序键和分区键,数据按照排序键物理有序地落盘;Doris走的是Unique/ Aggregate / Duplicate三种模型,需要根据表的更新特征选择合适的模型,再指定分桶键和分桶数。

举个例子,TPC-DS里最大的事实表store_sales,在ClickHouse中我建表时排序键选了业务日期和主键的组合,分区键选了月份字段,这样按日期过滤的查询只会扫描对应月分区的数据。在Doris中,同样一张表我用Duplicate模型,分桶键选主键,分桶数按总数据量和单分桶建议的1-2GB来估算。这里必须提醒,分桶数不是越大越好,我一开始图省事直接设成100个分桶,结果小查询调度开销比计算开销还大,后面调回合理区间才正常。

另一个值得注意的点是字段类型映射。TPC-DS原始schema里不少字段是decimal和char变长类型,ClickHouse对decimal支持没有问题,但char类型需要转成FixedString或者String来规避尾部空格填充问题;Doris对char类型支持较好,但超大宽表的char类型会产生存储放大,实际建表时我会把这部分也统计进去。表结构设计直接影响导入和查询性能,这块建议在导入前就精心打磨,而不是导入后再反复调整。

2.3 数据导入方案与耗时对比

ClickHouse的导入方式很多,常规CSV导入走INSERT SELECT或者clickhouse-client本地文件方式,我使用的是clickhouse-client配合FORMAT CSV,按表分批导入。这里性能瓶颈集中在磁盘I/O和MergeTree的写放大,导入过程会有后台merge,CPU占用率会有一个明显的波峰。SF-100整体导入耗时,在三个小时内能完成主表,整体规模和并发压力下会有波动,但比传统行存导入快很多。

Doris这边我用的是Stream Load机制,这是它主推的导入方式,支持HTTP协议提交文件任务。Stream Load的优点是能边导入边做聚合和索引构建,但需要控制导入任务的文件大小,我习惯把一张大表拆成多个文件并行提交,不然单任务内存压力会比较高。实测下来导入耗时两者相差不大,但在并发和负载均衡上,Doris的调度更平滑,而ClickHouse的导入对CPU有阶段性冲击。导入耗时本身不是重点,重点是导入后表的segment大小、分桶分布是否均匀,这些直接决定了后续查询性能,我会在导入完成后用系统表检查一下数据分布,有严重不均匀就手动进行一轮table optimize。

3. 查询适配与改造

3.1 99条SQL的兼容性硬伤

TPC-DS的99条查询模板是标准SQL规范下的产物,但实际跑起来,没有哪个OLAP引擎能宣称100%兼容。我这次测试把99条SQL先在标准MySQL或PostgreSQL方言下过一遍,再分别往ClickHouse和Doris里跑,记录每个查询是直接通过、改SQL后通过、还是不通过。

实测下来,Doris的SQL兼容性明显更贴近传统数据库,尤其是对多表join、子查询、窗口函数的支持比较完整,99条SQL里大部分可以直接跑通,需要修改的集中在函数细节和join语法上。ClickHouse这边情况就复杂一些,它的SQL方言有自己的优化方式,很多标准SQL写法它能跑但效率不高,需要用带方言色彩的改写让它走进良好的执行计划。比如ClickHouse对某些大表join的限制比较严格,需要显式指定join算法,否则会退化成低效的hash join,甚至直接报内存溢出。

我在测试时碰到最头疼的一类问题是隐式类型转换。TPC-DS查询模板里大量存在int和decimal之间的比较、字符串和日期之间的隐式转换,这两个引擎对这类转换的处理逻辑不一样,Doris会按传统数据库的规则自动转换,而ClickHouse更倾向于报类型错误或者用意外的规则截断。类似问题在跑数仓查询时经常出现,所以适配阶段要专门花时间把这些隐式转换手动改成显式CAST。

3.2 典型的SQL适配问题实录

具体适配过程中,我遇到了几个高频率问题,列出来供参考:

  • 日期函数差异:TPC-DS模板里大量使用日期加减和区间判断。ClickHouse的日期加减语法是INTERVAL子句,而模板里很多地方写的是DATE_ADD这种传统函数,需要在改写时统一。Doris对传统函数支持更好,改动少一些。

  • 窗口函数写法:TPC-DS第7条、第14条这种带多位排序和累积窗口的查询,在Doris上基本能直接跑,ClickHouse则对窗口的PARTITION BY和ORDER BY条件要求比较严格,需要拆成子查询才能稳定执行。

  • 子查询物化语义:TPC-DS里面很多派生表(derived table)的场景,ClickHouse对派生表关联的优化相对保守,有时需要手动把子查询结果集通过临时表或with子句先行物化。这个改动看似不改变业务语义,但实际执行计划差异很大,测试前必须仔细核对结果数据一致性。

3.3 统一测试口径的方法

为了让对比结果公平,我设定了一个原则:能用原生SQL跑就用原生SQL,确实因方言限制需要改写的,尽量用等价的改写而不是绕过核心逻辑,并且在博文里记录每次查询属于“原生可用”还是“适配后可用”。这样出来的性能对比才有说服力。

我也准备了三个查询场景集合:第一组是简单聚合加过滤,模拟常规BI报表;第二组是复杂多表关联加嵌套子查询,模拟深度分析;第三组是窗口函数和排名类查询,模拟业务上的动态排序和留存分析。每个场景挑出有代表性的TPC-DS查询,两类库各跑三遍取中位数,最大程度降低偶发波动的影响。

4. 测试结果与性能差异分析

4.1 数据加载与压缩对比

指标ClickHouseDoris
SF-100导入总耗时约130分钟约145分钟
落盘数据大小约55GB约72GB
压缩比约1.8x约1.4x

ClickHouse在压缩率上优势明显,这源于它默认的高压缩算法和列存特性。Doris落盘稍大,但它提供了可选的压缩方式和降基词典,针对短字符串字段可以进一步优化。这里要补充一句,落盘大小差异不是越快越好,还要看压缩率是否影响查询效率,压缩率高通常意味着I/O减少,但如果解压开销过大反而会拖慢CPU敏感型查询。

4.2 查询耗时分类对比

我将99条查询按响应速度分成了三档:快速查询(2秒以内)、中等查询(2到10秒)、慢查询(超过10秒)。从整体分布看:

  • ClickHouse在纯列存扫描、单表聚合、大范围过滤场景中表现非常突出,很多典型的报表查询都能在1秒出头返回。优势来自向量化执行引擎和非常激进的并行扫描策略。

  • Doris在单表聚合性能上略落后一点,但落后幅度有限,真正拉开差距的场景是多表join和复杂子查询。TPC-DS里面凡是涉及三张以上表关联的,Doris的执行器优化更好,多数查询能稳定在10秒以内,而ClickHouse有些查询会因为join策略选择不佳冲到20秒开外,甚至需要使用改写才能收敛。

整体看,测试期间两个引擎都无法做到99条全部低延迟通过,但Doris的稳定性明显更好——它几乎没有出现单条查询因资源限制而中断的情况,ClickHouse则在几条高复杂度join查询上出现过内存保护触发的问题。

4.3 并发与资源消耗表现

我额外测试了20并发下的查询吞吐。ClickHouse的高并发能力很强,纯分析型查询在并发下CPU利用率能稳定跑到80%以上,但并发一高,I/O调度会出现排队,个别查询延迟会被放大。Doris在并发控制上有自己的资源组机制,常用队列模型限制大查询,所以整体延迟曲线更平滑,尤其是复杂join查询在并发场景下的失败率比ClickHouse低很多。

内存占用方面,ClickHouse的大join查询峰值内存较高,遇到数据膨胀时容易触碰内存限制;Doris对join内存控制更精细,小表广播、大表分桶执行的策略更稳妥。如果你的场景是团队内多业务同时跑复杂分析,Doris的资源隔离优势会比较明显;如果场景是高并发简单报表,ClickHouse的延迟优势更能打。

4.4 硬件与调优空间分析

两类数据库在新硬件上都支持较多的核心数,但调优方向不同。ClickHouse主要通过调整线程数和内存限制参数来提升单查询性能,它的并行度几乎可以线性扩展。Doris则需要重点调分桶数、副本数和join内存策略,调优得当的Doris能在大宽表场景中表现出色,但调优路径更长,得理解它的MPP执行模型才能调准。

5. 常见问题与排查技巧实录

5.1 SQL改写的边界问题

在测试过程中,我经常需要在“保持语义不变”和“让引擎跑得快”之间折中。一位数据开发曾经跟我说过,压测的时候别为了性能毫无边界地改写SQL,比如有些团队会把连接谓词直接抽到临时表里,减少join参与表的数据量,这在业务上线时往往不可维护。我的建议是:压测时用于调优的SQL改写可以大胆,但最终交付的业务SQL必须保留原始语义,并且把改写方案沉淀成一份规范,方便后续排查。

5.2 内存溢出与查询异常

高频出现的内存溢出场景集中在两个地方:一个是不等值join,另一个是全局排序。简单表关联可以在Doris里通过分桶裁剪解决,但遇到极端数据倾斜(某个键值占比特别高)时,单分桶数据会超过内存阈值。我的排查路径是通过explain看分桶扫描行数,再对倾斜键做单独处理。

ClickHouse侧则容易在子查询返回结果集过大时出现问题,解法是用全局字典或者构造视图来减少中间结果集写入内存。这类问题在标准压测里表现为查询失败率,但在真实业务中就是线上事故,必须提前摸底。

5.3 数据一致性与结果校验

跑性能测试不能只看耗时,还得看结果对不对。TPC-DS官方提供了结果一致性校验思路,但实际操作中我们更常用的是把关键查询的结果集算一个校验值,在两个库中分别执行后对比。我在测试中遇到过一次自己发现的严重问题:某条带窗口函数的查询在改写后性能不错,但结果数和标准模板差了几行,原因是ClickHouse对排序边界值的处理和一个隐藏的NULL排序规则差异有关,后来显式指定了NULL的排序方向才一致。

6. 最后再分享一些个人经验

测试做完整整一遍,我的感受是:没有绝对的“谁更强”,只有“谁更适合你的业务”。ClickHouse更适合海量明细数据的高频过滤和聚合统计,它的吞吐量在同价位硬件上很有竞争力,但复杂join和子查询需要花时间做SQL适配;Doris在复杂分析、多表关联、实时更新场景下更省心,对SQL开发人员也更友好,团队上手成本低。如果在选型时拿不准,强烈建议把TPC-DS跑通一遍,并且不要只看SQL执行时间,把导入时间、压缩率、并发稳定性、内存峰值这些指标都放到一张表里综合评估。我这套测试流程和数据分析模板,完全可以作为你选型评估的起点,即使你面对的不是ClickHouse和Doris,换成其他分析型数据库也一样适用。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进