ClickHouse vs Druid vs Pinot: A Real-Time OLAP Decision Record for User-Facing Analytics in 2026
A product team decides to ship an in-app analytics dashboard. Every customer will see their own live usage numbers, filtered six ways, refreshed on every click, at a concurrency of thousands of simultaneous users. The data lands from Kafka a second ago. Someone opens the Postgres connection, runs the aggregation, watches it take four seconds, and realises the entire architecture is wrong. That moment is where clickhouse vs druid vs pinot stops being a benchmark hobby and becomes a load-bearing decision.
This choice matters more in 2026 than it did five years ago, because user-facing analytics is now a product feature customers pay for, not an internal report an analyst waits on. The three engines converged on the same problem from different origins, and each still carries the fingerprints of the company that built it. Picking wrong costs you either latency, money, or an operations team you did not budget for.
What this covers: what real-time OLAP actually demands, a reference ingest-to-serve architecture, how each engine’s storage and indexing model behaves under load, streaming upserts and their gotchas, join and concurrency limits, a weighted decision matrix, and clear “pick X when” guidance grounded in mechanisms rather than vendor claims.
Context and Background
Real-time OLAP is a narrow, demanding workload, and the word “real-time” hides two separate requirements that people conflate. The first is ingest freshness: an event written to Kafka should be queryable within a second or two, not after a nightly batch. The second is query latency: the aggregation over that data should return at p99 in the low hundreds of milliseconds, because a human is staring at a loading spinner. Systems that nail one and miss the other do not qualify.
Add the third axis that defines user-facing analytics specifically: concurrency. An internal dashboard serves a handful of analysts. A customer-facing one serves every customer at once, which means thousands of concurrent queries, each cheap individually but ruinous in aggregate if the engine cannot share work or prune data aggressively.
This is precisely why teams do not reach for Postgres or Snowflake here. A row-store like Postgres reads whole rows to answer a column aggregate and lacks the columnar compression and vectorised execution that a full-table scan over billions of rows requires. Snowflake and other cloud warehouses are columnar and fast, but they are optimised for a few large analyst queries with per-query compute spin-up and second-to-minute latency; firing ten thousand sub-second dashboard queries at a warehouse is both slow and financially catastrophic on a per-query billing model. The lakehouse query engines like Trino and Spark share the same mismatch — they are built for throughput over open table formats, not for serving a UI.
Real-time OLAP engines exist to fill exactly that gap: columnar storage, streaming ingestion, aggressive indexing, and an execution model tuned for many small selective queries rather than few large scans. ClickHouse, Apache Druid, and Apache Pinot are the three serious open-source contenders, and ClickHouse’s own 2026 guidance on choosing a real-time analytics database frames the freshness gap as sub-second or, at worst, sub-minute. That is the bar every option below is measured against.
This real-time OLAP ADR treats clickhouse vs druid vs pinot as a genuine architecture decision record: context first, then the drivers, then each option’s trade-offs, then a recommendation you can defend in a design review rather than a leaderboard you memorise.
The decision drivers and a reference architecture
The clickhouse vs druid vs pinot decision should be settled by four measurable drivers, not by brand loyalty: ingest freshness and upsert semantics, query p99 under real concurrency, join and SQL richness, and the operational headcount you can afford. Every real-time OLAP database implements the same logical pipeline — ingest from a stream, seal it into immutable columnar segments, back it with cheap deep storage, and answer queries through a scatter-gather broker — so the differences live in how each stage is realised, not in the shape of the pipeline itself.

Figure 1: The canonical real-time OLAP serving path shared by all three engines. Events flow from application and IoT sources into a partitioned Kafka topic, are consumed by a streaming ingestion layer with exactly-once semantics, held briefly as in-memory rows that are immediately queryable, then sealed into compressed columnar segments that are backed up to object storage and served — alongside the still-hot in-memory rows — by a query broker that scatters each dashboard request across nodes and gathers the partial results.
The ingest stage decides your freshness and your upsert story
Ingestion is where the three engines diverge first and hardest. Druid and Pinot both run native Kafka indexing services: a set of tasks each own a slice of the topic’s partitions, pull records continuously, and build real-time segments in memory that answer queries the moment a row lands. This gives genuine sub-second freshness and, importantly, decouples ingestion from your query nodes so a spike in writes does not stall reads.
ClickHouse takes a different path. Its Kafka table engine — or the newer ClickPipes in ClickHouse Cloud — consumes the topic and inserts into a MergeTree table, where a materialized view can fan the rows into aggregation targets on the way in. It works well, but ingestion and query share the same server process, so you size hardware for the sum of both.
The upsert question separates them further. If your source emits corrections — an order status flips, a session metric revises — you need the store to reflect the latest version per key. This is native in Apache Pinot’s upsert tables, which key records on a primary key and serve the newest version at query time. ClickHouse approximates it with ReplacingMergeTree, which deduplicates on background merge, meaning stale rows can be visible until a merge runs unless you pay for FINAL. Druid’s streaming upsert story remains the weakest of the three and typically pushes you toward best-effort rollup or reindexing.
The store stage is columnar segments plus deep storage
Once rows are sealed, all three write immutable, compressed, columnar segments and register them in a metadata catalog. Druid and Pinot push those segments to deep storage — S3, GCS, or HDFS — as the durable source of truth, then load copies onto historical or server nodes for querying. This separation lets you scale storage and compute independently and recover a node by re-pulling segments, at the cost of a cold-start penalty when a node has to fetch before it can serve.
ClickHouse historically kept data on local disks attached to the query nodes, favouring raw scan speed over storage-compute separation, though its newer shared-storage and cloud tiers now offer an object-store backend too. The trade-off is the classic one: local disk is faster per query but couples storage cost to compute cost.
The serve stage is scatter-gather with a concurrency budget
Every query is a scatter-gather: a broker (Druid, Pinot) or a distributed query coordinator (ClickHouse) fans the request across the nodes that hold relevant segments, each node computes a partial aggregate over its slice, and the broker merges the partials. The performance lever that matters for user-facing work is pruning — how much data the engine can skip before it scans. A dashboard query filtered to one tenant and one week should touch a tiny fraction of segments; the indexing model of each engine determines how tight that pruning is, and that is what the next section dissects.
The concurrency budget is where user-facing analytics lives or dies. If a single dashboard query occupies a CPU core for 20 milliseconds and you must serve 5,000 queries per second at peak, you need 100 core-seconds of query work per second — roughly 100 busy cores before headroom, replication, and p99 tail padding. Halve the per-query cost through tighter pruning or pre-aggregation and you halve the fleet. That linear relationship is why the star-tree and materialized-view mechanisms below are not cosmetic: they change the size of the cluster you pay for.
A worked capacity and cost sketch
Put rough numbers on it. Suppose 200,000 events per second stream in from Kafka, each event about 400 bytes, retained hot for 30 days. That is roughly 17 billion rows per day and, before compression, on the order of 6–7 TB per day of raw data; columnar compression of 8–12x typically brings the hot footprint down to the mid-hundreds of gigabytes per day, or low tens of terabytes across the retention window. These are illustrative planning figures, not benchmarks — your compression depends entirely on cardinality and column types.
The architectural choice then reshapes cost. On ClickHouse, that hot data sits on local NVMe attached to query nodes, so you are buying compute-heavy instances sized for storage you rarely scan in full — fast, but you pay for the coupling. On Druid or Pinot, the durable copy lives in object storage at a fraction of the price, and you size historical nodes only for the working set you keep resident, accepting a cold-fetch penalty for the rest. When people frame clickhouse vs druid vs pinot as purely a latency race, this storage-economics fork is the variable that actually moves the monthly bill.
Ingest-time rollup and pre-aggregation change the arithmetic again. If a Druid rollup collapses 17 billion raw rows into 200 million pre-aggregated tuples because your dashboards only ever group by a handful of dimensions, both storage and query cost fall by nearly two orders of magnitude — but you lose the raw events and cannot answer a question the rollup did not anticipate. That is the real-time OLAP database bargain in one line: pre-aggregate for cheap-and-fast, keep raw for flexible-and-expensive, and know which one your product needs before you pick an engine.
The three engines compared
For a like-for-like read, the weighted decision matrix below scores each engine on the drivers that matter for user-facing analytics. Weights reflect a typical customer-facing dashboard workload; reweight them for your own case. Scores are 1 to 5, directional and based on documented architecture rather than a single benchmark — because, as every honest comparison notes, real-time OLAP benchmarks are intensely workload-dependent and easy to rig.
| Driver | Weight | ClickHouse | Apache Druid | Apache Pinot |
|---|---|---|---|---|
| Streaming ingest freshness | 0.15 | 4 | 5 | 5 |
| Streaming upserts | 0.12 | 3 | 2 | 5 |
| Query p99 at high concurrency | 0.20 | 4 | 4 | 5 |
| High-cardinality aggregation | 0.15 | 5 | 4 | 4 |
| Joins and full SQL | 0.15 | 5 | 3 | 3 |
| Operational simplicity | 0.13 | 5 | 2 | 2 |
| Managed cloud maturity | 0.10 | 5 | 4 | 4 |
| Weighted total | 1.00 | 4.38 | 3.44 | 4.13 |
Read the totals as a starting posture, not a verdict: ClickHouse leads on simplicity and SQL, Pinot leads on concurrency and upserts, Druid rarely wins a column outright but is a proven, well-understood middle. The per-engine analysis below explains why those scores fall where they do.

Figure 2: ClickHouse MergeTree write path — sparse primary index, materialized-view rollups, and merge-time deduplication. An insert becomes a new sorted data part; the part registers granules in the sparse primary index so queries skip whole blocks, feeds a materialized-view trigger that maintains an AggregatingMergeTree rollup, and is later combined by background merges where ReplacingMergeTree collapses duplicate keys, all three paths feeding the query engine that prunes on the sparse index.
ClickHouse: MergeTree, sparse indexing, and real SQL
ClickHouse’s core is the MergeTree family. Data is stored in parts sorted by a primary key, and the primary index is sparse — it stores one mark per granule of ~8,192 rows rather than one entry per row, so the index is tiny and lives in memory, letting the engine binary-search to the right granules and skip the rest. This is a superb fit for high-cardinality aggregation and for scans that filter on the sort key, and it is why ClickHouse real-time analytics feels fast on wide tables.
Two features carry the real-time load. Materialized views act as insert-time triggers that maintain pre-aggregated AggregatingMergeTree tables, so a dashboard reads a small rollup instead of re-scanning raw events. And ClickHouse has genuine, mature JOIN support — distributed joins, dictionary lookups, and window functions — which neither competitor matches. The cost is the upsert weakness already noted and the fact that pruning depends heavily on choosing the sort key correctly.
That sort-key sensitivity is the ClickHouse gotcha most teams learn late. Because the primary index is sparse and the data is physically ordered by it, a query that filters on the leading sort-key columns prunes to a handful of granules, while a query that filters on a column absent from the sort key degrades to a near-full scan. Skipping indexes and projections mitigate this, but the honest framing is that ClickHouse rewards a schema designed around your actual query patterns and punishes ad-hoc filtering — which is fine for a fixed dashboard and awkward for exploratory analytics.
Druid: segments, bitmap indexes, and rollup
Druid was built at Metamarkets for ad-tech time-series and still shows it. Data is partitioned by time into segments, every dimension column gets an inverted bitmap index, and ingestion can apply rollup — pre-aggregating rows that share a dimension tuple within a time bucket, which can shrink time-series data dramatically before it ever hits disk. Bitmap indexes make multi-dimensional filtering on low-to-medium cardinality dimensions extremely fast, which is Druid’s sweet spot.
The weaknesses are joins and upserts. Historically Druid handled only broadcast/lookup joins; its newer multi-stage query engine widens SQL coverage but joins remain a second-class citizen compared to ClickHouse. Streaming upserts are effectively unsupported, so mutable data is awkward.
Druid’s rollup deserves a closer look because it is the feature that most changes the economics. During ingestion Druid can be told to aggregate every row that shares the same dimension tuple within a time granularity, so a million raw ad impressions bucketed to the minute might collapse to a few thousand summary rows carrying counts and sums. Best-effort rollup applies to streaming ingest and perfect rollup to batch, and the compression is real — but it is a one-way door. You are trading the ability to answer an unanticipated question for a smaller, faster dataset, and that trade is exactly why Druid shines for known time-series dashboards and struggles the moment product wants raw-event drill-down.
Pinot: segments, the star-tree index, and upserts
Pinot, born at LinkedIn for member-facing analytics, is the concurrency specialist. Its segments support a rich menu of pluggable indexes — inverted, sorted, range, JSON, text, and the distinctive star-tree index, a configurable pre-aggregation tree that materialises partial aggregates so high-concurrency group-by queries hit pre-computed nodes instead of scanning. That is the mechanism behind Pinot’s reputation for tens of thousands of QPS at single-digit-millisecond latency.
Pinot also has the best native upsert support of the three and, via its v2 multi-stage query engine, is closing the gap on joins and window functions — the Apache Pinot docs describe the multi-stage engine as the path toward fuller ANSI SQL including JOIN, OVER windows, and MATCH_RECOGNIZE. The price is operational complexity, which the next figure makes concrete.
The star-tree index is worth understanding mechanically because it is Pinot’s real differentiator in the clickhouse vs druid vs pinot field. Rather than indexing rows for lookup, it builds a tree whose nodes hold pre-computed aggregates for combinations of dimension values up to a configured cardinality threshold; above that threshold it falls back to scanning, so you tune the space-versus-speed trade directly. For a dashboard that repeatedly asks “sum this metric grouped by these three dimensions”, the query resolves to a tree traversal that touches pre-aggregated nodes instead of scanning millions of rows — which is how Pinot sustains high QPS at single-digit-millisecond latency where a naive columnar scan would fold under the concurrency.

Figure 3: The multi-component topology shared in spirit by Druid and Pinot. A controller owns metadata and segment assignment, real-time servers ingest fresh rows from Kafka while historical/offline servers serve sealed segments pulled from deep storage, ZooKeeper holds cluster state, a broker scatter-gathers each dashboard query across real-time and historical servers, and a minion or middle-manager tier runs background compaction — four or more distinct processes to run, monitor, and scale versus ClickHouse’s single binary.
Trade-offs, Gotchas, and What Goes Wrong
The operational complexity gap is the trade-off teams most often underestimate. ClickHouse ships as effectively one binary you can run on a single node and scale into a cluster; a small team can operate it. Druid and Pinot are distributed systems with separate controller, broker, server, and minion roles plus a ZooKeeper dependency and deep storage, as Figure 3 shows. That topology buys elastic storage-compute separation and independent scaling, but it demands real platform-engineering capacity — the multi-component cluster is a standing operational cost, not a one-time setup.

Figure 4: A requirement-driven decision path. Starting from the need for a real-time OLAP engine, the flow branches on whether you need complex joins and full SQL (ClickHouse), then streaming primary-key upserts (Pinot), then extreme user-facing concurrency (Pinot or Druid), then time-series rollup-heavy workloads (Druid), defaulting to ClickHouse when none of the sharper constraints bind.
The upsert gotcha bites hardest in practice. Teams adopt ReplacingMergeTree on ClickHouse expecting a key-value upsert and are surprised that duplicates remain visible until a background merge fires — correctness now depends on FINAL or careful query patterns, and FINAL is not free. Pinot’s upsert is cleaner but pins the affected partitions’ state in memory, so upsert tables carry a memory cost that grows with your primary-key cardinality; size for it or the servers OOM.
Joins are the second recurring surprise. Druid and Pinot historically pushed you toward denormalising everything at ingest — flattening dimensions into the fact table — because their join support was lookup-only. The newer multi-stage engines improve this, but a star-schema join that ClickHouse runs comfortably can still be slower or memory-heavy on Druid and Pinot; do not assume ANSI-SQL parity from a feature checkbox. If your data lives in open lakehouse table formats like Iceberg or Delta, plan the denormalisation boundary explicitly.
Cost surprises come from the storage model. ClickHouse’s local-disk speed couples storage to expensive compute nodes, so cold historical data is costly to keep hot. Druid and Pinot decouple to deep storage but pay a cold-start latency when a node fetches segments — a p99 spike right after a scale-up or a node restart that catches teams off guard during incidents.
The last thing that goes wrong is treating the clickhouse vs druid vs pinot choice as permanent. All three are moving fast: ClickHouse keeps improving its object-store tiers and lightweight deletes, Pinot’s multi-stage engine keeps adding SQL surface, and Druid’s query engine keeps widening join support. A weakness you design around today may be gone in a year, so anchor the decision to the constraint that is structural for your product — operational headcount, upsert semantics, join richness — rather than to a transient feature gap that a release will close.
Practical Recommendations — pick X when…
The honest summary of the clickhouse vs druid vs pinot question is that all three are excellent at the core job and diverge on secondary constraints, so let the constraints decide. Match the sharpest requirement you have to the engine that owns it, and default to the one your team can actually operate.
Pick ClickHouse when you need real SQL and joins, want the simplest operations, run a small platform team, and your mutation needs are modest. It is the strongest general-purpose real-time OLAP database and the safest default when no single exotic constraint dominates.
Pick Apache Pinot when the workload is genuinely user-facing at extreme concurrency, you need first-class streaming upserts, and you can staff a distributed cluster. It is the concurrency and freshness specialist, and its managed option (StarTree) removes much of the operational tax.
Pick Apache Druid when you are time-series-heavy, benefit enormously from ingest-time rollup, filter on many low-cardinality dimensions, and do not need mutable data or rich joins. Its managed path is Imply.
Checklist before you commit:
- [ ] Measured p99 target and expected peak concurrent queries written down.
- [ ] Ingest freshness requirement stated in seconds, and an upsert-or-append decision made.
- [ ] Join and SQL needs listed — will you denormalise at ingest or query star-schema at runtime?
- [ ] Storage-compute separation vs local-disk cost model chosen deliberately.
- [ ] Operations honestly scoped: single binary vs multi-component cluster with ZooKeeper and deep storage.
- [ ] Managed vs self-hosted decided (ClickHouse Cloud, Imply, StarTree) with a cost model.
- [ ] A representative benchmark run on your data — never trust a vendor’s.
Frequently Asked Questions
Is ClickHouse or Pinot better for user-facing analytics?
In the clickhouse vs druid vs pinot comparison for pure user-facing analytics at very high concurrency with sub-10-millisecond latency, Apache Pinot’s star-tree pre-aggregation and native upserts usually edge out ClickHouse. ClickHouse wins when you also need joins, complex SQL, and a small operations footprint. Many teams run ClickHouse first because it is far simpler to operate, and move specific extreme-concurrency dashboards to Pinot only when ClickHouse’s concurrency ceiling actually becomes the bottleneck under measured load.
What is the difference between Apache Pinot vs Druid?
Apache Pinot vs Druid is a comparison of two closely related segment-based systems. Both ingest from Kafka into immutable columnar segments backed by deep storage and served through brokers. Pinot adds the star-tree index and first-class upserts, targeting the highest concurrency and mutable data. Druid emphasises ingest-time rollup and bitmap-indexed time-series, and has a longer operational track record. Pinot is generally stronger for upserts and raw QPS; Druid is a mature, well-understood choice for rollup-heavy time-series.
Why not just use Postgres or Snowflake for real-time dashboards?
Postgres is a row store that reads whole rows for column aggregates and lacks the columnar compression and vectorised execution needed to scan billions of rows in milliseconds. Snowflake and cloud warehouses are columnar and fast but tuned for a few large analyst queries with second-to-minute latency and per-query compute costs. Firing thousands of concurrent sub-second dashboard queries at either is both slow and financially unsustainable, which is exactly the gap real-time OLAP engines fill.
Do Druid and Pinot support SQL joins now?
Partially. Both historically supported only broadcast or lookup-style joins and pushed teams to denormalise data at ingestion. Both now ship multi-stage query engines that broaden SQL coverage — Pinot’s v2 engine targets JOIN, window functions, and closer to full ANSI SQL. In practice, complex star-schema joins still run more comfortably on ClickHouse, so treat multi-stage joins as improving but not yet at parity, and benchmark your specific join shapes before relying on them.
How do streaming upserts work across the three engines?
Pinot has native upsert tables that key on a primary key and serve the newest version at query time, at the cost of holding partition state in memory. ClickHouse uses ReplacingMergeTree, which deduplicates only on background merge, so stale rows can remain visible until a merge runs or you query with FINAL. Druid’s streaming upsert support is the weakest; mutable data typically requires best-effort rollup or reindexing rather than true per-key upserts.
Which real-time OLAP database is cheapest to run?
It depends on data volume and how hot the data must stay. ClickHouse’s local-disk model is cheapest for smaller, always-hot datasets and simplest to operate, but coupling storage to compute gets expensive as cold data grows. Druid and Pinot separate storage to cheap object stores, which lowers storage cost at large scale but adds cold-start latency and a heavier operational footprint. Managed offerings — ClickHouse Cloud, Imply, StarTree — trade higher list price for lower operational headcount.
Further Reading
- Trino vs Presto vs Apache Spark: lakehouse query engines in 2026 — the batch/interactive counterpart to these serving engines.
- Iceberg vs Delta vs Hudi: lakehouse table formats ADR — where your source-of-truth tables live before they reach a real-time OLAP store.
- Kafka vs Redpanda vs WarpStream: edge telemetry ADR — choosing the streaming backbone that feeds ingestion.
- ClickHouse: choosing a real-time analytics database in 2026 — vendor guidance, read with the usual caveats.
- Apache Druid design overview and Apache Pinot multi-stage query engine — the primary docs for each engine’s architecture.
By Riju — about
