ClickHouse vs Doris vs StarRocks: OLAP ADR 2026

ClickHouse vs Doris vs StarRocks: OLAP ADR 2026

ClickHouse vs Doris vs StarRocks: OLAP ADR 2026

Most teams pick a real-time analytics engine on a benchmark blog post and regret it within a quarter, when the second JOIN or the first upsert workload arrives. The choice of ClickHouse vs Doris vs StarRocks is no longer a single-table speed contest; it is a decision about join strategy, upsert semantics, concurrency, and operational footprint that compounds for years. It matters now because the three engines have converged on overlapping capabilities — separation of storage and compute, materialized views, lakehouse federation — while diverging sharply on the workloads where each one breaks. This post is a working architecture decision record: you leave with decision drivers, a weighted matrix, a decision tree, and the honest failure modes that vendor pages omit.

What this covers: the architecture of each engine, single-table scan versus multi-table JOIN behavior, real-time upserts, concurrency for user-facing analytics, Iceberg federation, licensing, a weighted decision matrix, and a decision tree you can adapt.

Context and Background

Real-time OLAP sits between the transactional database that records events and the dashboard or API that queries them. The incumbents in 2026 are three column stores: ClickHouse, the single-node-fast engine that grew distributed; Apache Doris, an Apache Software Foundation top-level project built around an MPP frontend/backend split; and StarRocks, a Linux Foundation project that forked from an earlier Doris lineage and rebuilt the query engine. All three are columnar, vectorized, and Apache-2.0 licensed at the core, with commercial clouds layered on top.

The reason this is a genuine decision rather than a coin flip is that the three engines optimize different points. ClickHouse historically wins flat, high-cardinality single-table scans and ingestion throughput, but treated JOINs and updates as second-class for years. Doris and StarRocks were designed around a cost-based optimizer and distributed JOINs, which makes them strong on star-schema and snowflake queries where ClickHouse struggles, at the cost of a heavier multi-node footprint.

What changed by 2026 is convergence at the edges. ClickHouse narrowed its join gap with a new analyzer and more join algorithms; Doris and StarRocks both added storage-compute separation on object storage and matured their lakehouse catalogs. That convergence is exactly why a clean decision is harder than it was three years ago: the feature checklists now overlap, so the real differences hide in the workloads where each engine degrades. An ADR is the right tool because it forces you to write down the consequences you are accepting, not just the features you are buying. This document treats every score and recommendation as conditional on your workload, and labels qualitative judgments as such rather than dressing them up as benchmark fact.

If your workload is closer to streaming ingestion of one wide event table, the trade-offs resemble those in our Apache Pinot vs Druid real-time OLAP ADR more than a warehouse comparison. For the precise behavior of MergeTree, the canonical reference is the ClickHouse documentation. This ADR assumes you have ruled out a general-purpose warehouse and need sub-second analytical queries at scale.

Decision drivers

An ADR is only as honest as its drivers. Five forces shaped this comparison, in rough priority order. First, query shape: the ratio of single-table scans to multi-table JOINs in your real workload, because it splits the field more cleanly than any other factor. Second, write pattern: append-only event streams versus mutable rows that need correct, low-latency upserts. Third, concurrency: a handful of analyst queries versus thousands of small user-facing requests per second. Fourth, lakehouse posture: whether data already lives in Iceberg or Hive and must be queried in place rather than copied. Fifth, operational budget: how many node roles and tuning axes your team can responsibly own. Cost and licensing act as tie-breakers rather than primary drivers, because all three engines are economical and Apache-2.0 at the core. Naming these drivers up front keeps the rest of the document from drifting into feature-list comparison, which is where most engine evaluations go wrong.

Reference Architecture: How Each Engine Moves Data

The fastest way to understand the trade-offs is to trace ingestion and query through each engine. The architectures rhyme — columnar storage, vectorized execution — but the control planes differ in ways that determine join performance and operational cost.

A real-time OLAP database stores data in column-oriented files, skips irrelevant data using sparse indexes or partitioning, and executes filters and aggregates in vectorized batches across many cores. ClickHouse does this from a single binary; Doris and StarRocks split the work across frontend planner nodes and backend execution nodes. That structural difference is the root of most downstream decisions.

ClickHouse vs Doris vs StarRocks ingestion and query path architecture diagram

Figure 1: ClickHouse MergeTree ingestion and vectorized query path.

Figure 1 shows the ClickHouse model. Inserts land as immutable MergeTree parts, partitioned by a date or key expression. Background merges compact small parts into larger sorted ones, which is when the primary index becomes effective. On query, ClickHouse uses the sparse primary index to skip granules, then scans the needed columns vectorized. There is no central coordinator node in the Doris sense; a distributed ClickHouse cluster is sharded tables plus replicas, and JOINs historically broadcast or pulled the right table into memory rather than shuffling intelligently.

ClickHouse: MergeTree, sharding, and vectorized scans

ClickHouse’s strength is mechanical. The MergeTree family — including ReplacingMergeTree, AggregatingMergeTree, and SummingMergeTree — encodes upsert-like and rollup behavior into the merge process itself. The table’s ORDER BY clause doubles as a sparse primary index: ClickHouse stores one index entry per granule (a configurable block of rows, 8192 by default) rather than per row, so the index stays tiny even at trillions of rows and lets the engine skip whole granules that cannot match a predicate. Compression is codec-driven and column-aware — Delta, DoubleDelta, Gorilla, and ZSTD are chosen per column — which is why time-series and metric columns compress so aggressively that scans become I/O-cheap.

Sharding distributes data by a key across nodes; ReplicatedMergeTree plus a coordination service (historically ZooKeeper, increasingly the built-in ClickHouse Keeper) handles replication and deduplicated inserts. A Distributed table is a thin routing layer over the shards. Vectorized execution processes data in cache-friendly column batches across cores, which is why single-table scans over billions of rows feel interactive.

The historic weakness is the planner. JOINs lacked a mature cost-based optimizer, so multi-table queries depended heavily on the engineer ordering tables correctly and the right-hand table fitting in memory. As of 2026, ClickHouse has invested in join algorithms — hash, partial-merge, grace-hash, and full-sorting-merge variants — and a new analyzer that improves planning, but star-schema JOINs with several large dimension tables remain its softer flank relative to the purpose-built MPP engines.

Doris and StarRocks: FE/BE, the CBO, and storage models

Figure 2 shows the shared shape of Doris and StarRocks. Frontend (FE) nodes parse SQL, hold metadata, and run a cost-based optimizer that can rewrite queries to use materialized views. Backend (BE) nodes scan tablets, shuffle data over the network, and execute hash or broadcast JOINs. Both engines expose explicit table models: a primary-key model for real-time upserts, a unique model, an aggregate model for pre-rollups, and a duplicate model for raw append. StarRocks adds a pipeline execution engine and a query cache aimed at high-concurrency, user-facing analytics; Doris ships a comparable vectorized engine and its own materialized-view machinery.

Doris and StarRocks MPP frontend backend architecture with cost based optimizer and storage models

Figure 2: The Doris/StarRocks FE/BE MPP model, CBO, materialized views, and table storage models.

Figure 2 traces a JOIN: the FE plans and optimizes, possibly rewriting to a materialized view, then BE nodes scan tablets, exchange data by join key, and run the JOIN locally. The four storage models are not cosmetic. The duplicate model keeps every raw row and is the fastest to ingest. The aggregate model applies pre-defined aggregations at load time, shrinking storage and accelerating rollup queries at the cost of losing row-level detail. The unique model deduplicates by key with merge-on-read semantics. The primary-key model is the one that changed the game: it supports true upserts and deletes with read performance close to append-only tables, because it maintains a persistent index and applies delete vectors at read time rather than forcing a full rewrite.

Both engines also offer a shared-data or storage-compute-separation mode that places primary data on object storage (S3-compatible buckets) with BE-local caching, decoupling storage cost from compute scaling. In the classic shared-nothing mode, data lives on local disks attached to BE nodes, which gives the lowest latency but couples storage and compute. Shared-data mode lets you scale a compute cluster up for a heavy reporting window and back down afterward without moving terabytes. That mode trades a little cold-query latency — the first read of a cold tablet must fetch from object storage — for elastic, cheaper storage, the same lakehouse-leaning shift discussed in our Apache Iceberg lakehouse deep dive.

Ingestion: the part that decides real-time

The “real-time” in real-time OLAP is mostly an ingestion-latency claim, and the three engines reach it differently. ClickHouse favors large batched inserts written straight to MergeTree parts, with async inserts and Kafka table engines smoothing streaming sources into block-sized writes; freshness is excellent once a part lands, typically sub-second. Doris and StarRocks support stream loads, routine loads from Kafka, and primary-key upserts, applying a write to a memtable and an in-memory delete index so the row is queryable almost immediately, then flushing and compacting in the background. The practical takeaway in the ClickHouse vs Doris vs StarRocks decision: all three deliver seconds-fresh data, but ClickHouse pushes you toward batching and the MPP engines toward managing compaction pressure. Neither is “more real-time” in a way that survives a real workload — both are tuning problems, not free lunches.

Where the architectures diverge on cost

ClickHouse minimizes moving parts: one process type, predictable scaling, low operational surface for a single-table workload. A three-node ClickHouse cluster is genuinely operable by one engineer. Doris and StarRocks require you to run and balance FE and BE roles, manage tablet distribution, size buckets, and reason about replica placement — more capable on JOINs, heavier to operate. FE nodes form a small Raft-style quorum for metadata; BE nodes hold data and do the heavy lifting, and you scale them independently. That separation is the source of both the MPP strength and the operational weight.

Options considered and rejected

This ADR deliberately scopes to three distributed column stores, but it is worth recording what was excluded and why. A general-purpose cloud warehouse (Snowflake, BigQuery, Redshift) was rejected because the requirement is single-digit-second latency at high concurrency for user-facing surfaces, where per-query warehouse overhead and cost do not fit. Pinot and Druid were considered and are strong for append-only event streams, but they are weaker on ad hoc JOINs and SQL ergonomics than the MPP engines here; they get their own treatment in the linked Pinot-vs-Druid ADR. Embedded engines like DuckDB were ruled out by the scale and concurrency requirement, since they target single-node and per-process analytics. Postgres with columnar extensions was rejected on raw scan throughput. Naming the rejected options keeps the comparison honest: the three finalists are not the only OLAP engines, they are the ones that survive a distributed, high-concurrency, mixed scan-and-JOIN requirement.

Deeper Analysis: Joins, Upserts, and Concurrency

The ClickHouse vs Doris vs StarRocks decision hinges on four behaviors that benchmarks rarely isolate. The matrix below scores each engine on the axes that actually break production systems. Scores are the author’s qualitative assessment on a 1 (weak) to 5 (strong) scale as of 2026, not measured benchmark output; validate against your own workload.

Decision axis Weight ClickHouse Apache Doris StarRocks
High-cardinality single-table scan 5 5 4 4
Multi-table JOIN performance 5 3 4 5
Real-time upserts (primary-key) 4 3 4 5
Concurrency / QPS (user-facing) 4 3 4 5
Lakehouse / Iceberg federation 3 3 4 4
Ingestion throughput 4 5 4 4
Operational simplicity 3 4 3 3
Ecosystem and managed cloud 3 5 4 4
Weighted total (of 155) 122 122 132

Methodology: weights reflect a typical user-facing real-time analytics product; multiply each score by its weight and sum. Re-weight for your context — a single-table observability store would weight scan and ingestion far higher and likely tip the total toward ClickHouse.

The JOIN row is the crux. Figure 3 shows the strategy a CBO chooses. With a small dimension table, the engine broadcasts it to every BE node; with two large tables it shuffles both by the join key. If the tables are colocated by that key, the JOIN runs locally with no network exchange — a major StarRocks and Doris advantage for star schemas. Runtime filters then prune the probe-side scan. The failure mode lives at the bottom: if the build side exceeds memory, the engine must spill to disk or fail the query outright.

Join strategy decision tree comparing broadcast shuffle and colocate joins across OLAP engines

Figure 3: Distributed JOIN strategy — broadcast, shuffle, colocate, runtime filters, and the memory-pressure cliff.

On real-time upserts, the primary-key model in StarRocks and Doris lets you UPDATE and DELETE rows by key with read performance close to append-only tables, because the delete vector is applied at read time rather than forcing a full merge. ClickHouse achieves similar ends through ReplacingMergeTree and lightweight updates, but the semantics are eventually-consistent until a merge runs — you may read stale duplicates between merges unless you add FINAL or aggregation, which costs query time.

On concurrency, user-facing analytics means hundreds or thousands of small, fast queries per second, not a few huge scans. StarRocks’s query cache and pipeline engine target exactly this: the pipeline engine schedules execution as fine-grained pipeline drivers across cores instead of one thread per fragment, which keeps tail latency stable as concurrency climbs, and the query cache reuses partial aggregation results across similar queries. Doris is competitive with its own vectorized engine and partition-level caching. ClickHouse can serve high QPS, but the common pattern is to engineer around it — pre-aggregate with AggregatingMergeTree or projection tables, narrow the schema, and cap concurrent heavy scans so a few large queries do not starve the many small ones. For embedded or single-node analytical needs the calculus changes entirely, as covered in our DuckDB vs ClickHouse embedded analytics ADR.

Materialized views are where the MPP engines quietly pull ahead for dashboards. In Doris and StarRocks, the cost-based optimizer can transparently rewrite an incoming query to read a materialized view it never named, so a dashboard query against a raw fact table silently hits a pre-aggregated rollup. ClickHouse materialized views are powerful but operate as insert-time triggers populating a target table; they do not transparently rewrite arbitrary queries the way a CBO-driven rewrite does. The practical effect: on the MPP engines you can add a rollup later and have existing queries accelerate without touching application SQL, whereas in ClickHouse you design the materialized pipeline up front and route queries to it explicitly.

A minimal StarRocks primary-key table makes the upsert model concrete:

CREATE TABLE events_pk (
    event_id   BIGINT       NOT NULL,
    user_id    BIGINT       NOT NULL,
    status     VARCHAR(32)  NOT NULL,
    updated_at DATETIME     NOT NULL
)
PRIMARY KEY (event_id)
DISTRIBUTED BY HASH(event_id) BUCKETS 16
PROPERTIES ("enable_persistent_index" = "true");

-- Upsert: re-inserting an existing event_id overwrites the prior row
INSERT INTO events_pk VALUES (1001, 42, 'shipped', '2026-06-20 09:30:00');

The equivalent intent in ClickHouse uses ReplacingMergeTree, but the latest row only wins after a background merge, so point reads need FINAL for correctness:

CREATE TABLE events_rmt (
    event_id   UInt64,
    user_id    UInt64,
    status     String,
    updated_at DateTime
)
ENGINE = ReplacingMergeTree(updated_at)
ORDER BY event_id;

-- Correct latest-row read before merges complete:
SELECT * FROM events_rmt FINAL WHERE event_id = 1001;

That single FINAL keyword is the upsert tax: it forces extra work to deduplicate at query time, and it is the reason teams with heavy update workloads frequently land on a primary-key MPP engine.

On lakehouse federation, the comparison of ClickHouse vs Doris vs StarRocks turns on how each treats external data. Doris and StarRocks expose external catalogs that mount an entire Iceberg, Hiv

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *