不靠性能赢的选型:一次约束优先的决策,怎么讲给上面听

英文版:English

这篇想说什么

案例研究讲的是这次迁移怎么工程化的——内存、compaction、路由。这一篇讲的是它上面那一层:这个引擎当初是怎么被选出来的,以及当屋子里最显眼的一个数字指向反方向时,这个选择是怎么被讲给上面听、还站得住的。

陷阱:「哪个更快」从一开始就问错了问题

任何一次引擎选型的讨论都会自然滑向 benchmark 对刷,而这个对刷自带一个陷阱:不管你亮出哪个数字,总有人能为另一种查询形状亮出更好看的数字。我们照样跑了这个对刷,用自己的一手数据,结果 ClickHouse 在平表扫描上领先 1.2–2.5 倍。如果这个决策的框架是「哪个引擎更快」,这个数字就能把讨论结束掉——而且结束得是错的。

修法是拒绝这个框架本身。真正的问题从来不是「哪个引擎扫描更快」,而是「我们的负载到底需要什么」——而我们的负载根本不是一个扫描负载。它的绝大多数——远超九成,这是挖了两周生产查询日志得出的结论,而不是相信大家早就默认的那套 benchmark 套件——是毫秒级点查,旁边挂着一小股不可预测、且越来越多由 agent 产生的重分析查询。一条排在 bulk insert 后面的查询,墙钟等了 61 秒、实际只用了 60 毫秒 CPU,这个案例让问题的形状变得无可辩驳。负载的形状一旦是这样,「哪个引擎扫描更快」就不再是那个承重的问题。

三条硬约束,不是一张功能清单

下一个陷阱,是把一个负载问题悄悄换成一个功能问题——比功能清单而不是推导需求。我们把决策收敛成三条不可谈判的约束,按顺序:

# 约束 为什么不可谈判
1 serving 和重分析必须硬隔离 一条坏查询不能被允许从一个实时反欺诈决策里偷走延迟
2 计算必须能独立于存储扩缩 重负载是突发且大部分时间空闲的;为它常驻付全款,是在为错误的形状付钱
3 架构必须开源、可自建 把承重架构建立在某一家云厂商的专属托管服务上,是架构风险,不是便利

任何一个引擎,只要在约束 1 或 2 上不合格,不管 benchmark 数字多好看都出局。这个顺序——约束在前、benchmark 在后——才是「架构决策」和「打扮成决策的技术偏好」之间真正的区别。

先交出那个不好看的数字

这次向上论证里杠杆最大的一个动作,是顺序:ClickHouse 的 benchmark 优势被放在任何 Doris 优点之前说出来,不是压在附录里,也不是等被追问才不情愿地承认。「我们不是为了性能选它——在平表扫描上,现有方案依然更快」,这句话为后面所有的话买到了可信度。一份只展示有利数字的材料读起来像推销;一份先亮出对自己不利的数字的材料读起来像分析。领导层会信第二种,而且应该信——正是这份自我约束,让后面的论证值得被听下去。

在对方提出之前,先把最明显的反驳重新框定

可预见的反驳是「ClickHouse 现在不也有 workload isolation 了吗」。这是真的,而且最好立刻承认而不是争辩:新版 ClickHouse 确实上线了 CPU 权重、查询槽位并发、内存预算。关键在于,这仍然是进程内的软性 QoS——单台 server 内部的令牌桶式准入层——而 ClickHouse 自己的指引也这么写:独立算力依然是最强的资源隔离形式。这个反驳并没有错,它只是回答了一个错的问题。隔离是一个层级问题,不是一个「有没有」的是非题;提前把这个区分讲清楚,能把一次质疑变成一条脚注,而不是一场辩论。

比第一个决策更重要的第二个决策

选引擎是一个决策。另一个决策,是在 fork 已经跑出真实代码之后才做的,它对「这是判断力还是执行力」这件事的分量更重:领导层起初希望把查询路由分类器和估算机制一起贡献上游。我用第一次贡献已经立住的同一条原则反过来论证:引擎应该吐出数据,调用方决定策略。这个分类器携带着我们校准过的阈值、和一个刻意 recall 优先的姿态——那是策略,不是机制,而策略应该留在内部,即使包裹着它的代码本身完全开源。主动想清楚不该往上游交什么,和交一个 PR 上去,是两种不同的信号——那是「给一个项目做贡献」和「对一个架构负责」之间的区别。

最后才说出口的那笔诚实交易

这次论证该怎么收尾就怎么收尾:这不是「Doris 全面赢了 ClickHouse」。我们放弃的是平表扫描上的一部分速度,换来的是可预测的 serving 延迟、workload 隔离、独立弹性扩缩,以及和一套自建开源技术栈的一致性。把放弃了什么大声说出来,放在论证的最后而不是永远不提——这才是让一次选型变成一个「别人不用重新推导一遍就能信任」的决策的关键。