Choosing Doris Without a Speed Win: A Constraint-First Case, Argued Upward
Also in Chinese: 中文版
What this is about
The case study is about how the migration was engineered — memory, compaction, routing. This is about the decision one layer above it: how we picked the engine in the first place, and how that pick got defended to leadership when the most obvious number in the room pointed the other way.
The trap: “which one is faster” is the wrong first question
Every engine-selection conversation gravitates toward a benchmark shootout, and the shootout has a trap built into it: whichever number you bring, someone can bring a better one for a different query shape. We ran the shootout anyway, on our own primary-source data, and ClickHouse won flat-table scans by 1.2–2.5×. If the frame of the decision is “which engine is faster,” that number ends the conversation and it ends it wrong.
The fix was to refuse the frame. The real question was never “which engine wins a scan” — it was “what does our workload actually require,” and our workload was not a scan workload. The overwhelming majority of it — well over 90%, confirmed by mining two weeks of production query logs rather than trusting the benchmark suite everyone had already agreed on — was millisecond point lookups sitting next to a small, unpredictable, and increasingly agent-generated stream of heavy ad-hoc analytics. A 61-second wall-clock wait behind 60ms of actual CPU, queued behind a bulk insert, was the case that made the shape of the problem undeniable. Once the workload is that shape, “which engine scans faster” stops being the load-bearing question.
Three hard constraints, not a feature checklist
The next trap is reframing a workload question into a features question — comparing checklists instead of deriving requirements. We collapsed the decision to three non-negotiables, in order:
| # | Constraint | Why it’s non-negotiable |
|---|---|---|
| 1 | Serving and heavy analytics must be hard-isolated | one bad query cannot be allowed to steal latency from a live fraud decision |
| 2 | Compute must be able to scale independently of storage | the heavy workload is bursty and largely idle; paying for always-on capacity to serve it is paying for the wrong shape |
| 3 | The architecture must be open-source and self-hostable | a load-bearing dependency on one cloud vendor’s proprietary managed service is an architecture risk, not a convenience |
Any engine that failed constraint 1 or 2 was out regardless of benchmark numbers. That ordering — constraints first, benchmark second — is the actual difference between an architecture decision and a fan preference dressed up as one.
Give up the unflattering number first
The single highest-leverage move in how this got argued upward was sequencing: the ClickHouse benchmark loss was stated before any Doris advantage, not buried in an appendix or volunteered only under questioning. “We are not choosing this for speed — on flat scans, the incumbent is still faster” is the sentence that buys credibility for everything said after it. A pitch that only shows favorable numbers reads as a pitch; a pitch that leads with the number against itself reads as an analysis. Leadership trusts the second one, and should — the discipline is what makes the rest of the case worth listening to.
Reframe the obvious counter-argument before it’s raised
The predictable pushback was “ClickHouse has workload isolation too now.” True, and worth conceding immediately rather than disputing: recent ClickHouse versions do ship CPU weighting, query-slot concurrency, and memory budgets. What matters is that this is in-process, soft QoS — a token-bucket admission layer inside a single server — and ClickHouse’s own guidance says as much: separate compute remains the strongest form of resource isolation. The counter-argument isn’t wrong, it’s answering the wrong question. Isolation is a tier question, not a yes/or/no question, and stating that distinction up front turns an objection into a footnote instead of a debate.
The second decision that mattered more than the first
Picking the engine was one decision. A second one, made after the fork was already producing working code, mattered more for how the work read as judgment rather than execution: leadership wanted the query-routing classifier upstreamed alongside the estimation mechanism. I argued against it, using the same principle the first contribution had already established — the engine should emit data; the caller decides policy. The classifier carried our calibrated thresholds and a deliberately recall-biased posture; that’s policy, not mechanism, and policy is the part that should stay internal even when the code that surrounds it is genuinely open. Knowing what not to hand upstream, on your own initiative, is a different signal than shipping a PR — it’s the difference between contributing to a project and being accountable for an architecture.
The honest trade-off, stated last
The pitch closes the way it should: this was not “Doris beats ClickHouse.” It was giving up some flat-scan speed to buy predictable serving latency, workload isolation, independent elastic scaling, and consistency with a self-hosted, open-source stack. Naming what was given up, out loud, at the end of the pitch rather than never — that’s what turns a selection into a decision someone else can trust without re-deriving it themselves.