Upcoming Webinar:

Getting Started with OpenObserve

July 30, 2026
11:00 AM ET
Register

OpenObserve vs ClickHouse: Which Is Better for Logs?

Hengfei Yang
Huaijin Hao
Gorakhnath Yadav
Hengfei Yang,Huaijin Hao,Gorakhnath Yadav
July 22, 2026
22 min read
Don't forget to share!
TwitterLinkedInFacebook

Ready to get started?

Try OpenObserve Cloud today for more efficient and performant observability.

Table of Contents
OpenObserve vs ClickHouse pod-level log search benchmark on 1 billion rows

TLDR

  • If you want a database engine, ClickHouse can win that contest. If you want an observability platform, start with OpenObserve. At 1 billion rows, the integrated platform gave up no query performance. ClickHouse's warm wins were tens of milliseconds. OpenObserve's were multiples: 4.3x on the common-token search and 13.9x on the pod-name lookup (71 ms against 989 ms). Cold on gp3, OpenObserve's slowest query took 1.60 seconds; ClickHouse's took 14.2 seconds.
  • This is a reproducible OpenObserve vs ClickHouse benchmark for logs: 1 billion Kubernetes-style records (2.04 TiB of raw JSON) streamed identically to both engines, run twice, once on local NVMe and once on gp3 EBS, with every query published.
  • Searching by pod name, the query every Kubernetes engineer runs, OpenObserve answered in 71 ms against ClickHouse's 989 ms warm. Cold, it was 82 ms against 5.05 seconds on NVMe, and 141 ms against 14.2 seconds on gp3, roughly 100x.
  • Warm, the engines are competitive: ClickHouse was faster on 15 of 19 queries, usually by tens of milliseconds (aggregations, narrow needle lookups, row fetches). OpenObserve won the heaviest search patterns by multiples: 4.3x on a common-token search, 13.9x on the pod-name lookup.
  • Cold is where the architectures diverge. OpenObserve was faster on 10 of 19 cold queries on NVMe and 13 of 19 on gp3. Moving from NVMe to gp3 made ClickHouse's heaviest cold queries 2.3x to 2.8x slower, while OpenObserve's cold profile stayed nearly flat.
  • On disk, OpenObserve used 621.0 GiB against ClickHouse's 937.2 GiB, each including its own search indexes, but our ClickHouse table ran default compression, so that gap is not a fair storage verdict. This run also did not measure query performance or total cost on object storage.

This is not only a database-versus-database verdict. It compares two ways to build a cloud-native observability system: assemble observability capabilities around a high-performance analytical database, or start with an integrated platform designed around object storage. The benchmark addresses the question that remains after cost, architecture, and operations enter the decision: does the integrated approach require giving up query performance?

We wanted a straight, reproducible answer, so we ran OpenObserve and ClickHouse on matched hardware, with the same data and the same queries, at a billion rows, and we ran it twice, on two different disk tiers. OpenObserve can run as a single Rust binary and stores logs, metrics, and traces as Parquet, with object storage as the durable data layer in its cloud-native architecture. ClickHouse is a column-oriented analytical database that many teams, and products such as SigNoz and ClickStack, use as an observability store.

Why compare OpenObserve and ClickHouse for logs

ClickHouse earned its place in observability honestly. ClickHouse Inc. stores 43 PB of OpenTelemetry data in its own internal logging system, and companies like Last9 push 480 million log lines per minute through it (ClickHouse blog, April 2025). As a column store with strong compression, it is a good place to keep log data.

The difference is what surrounds the store. Bare ClickHouse gives you a fast engine and SQL; you design the schema, indexes, ingestion and retention policies, and add an observability-specific query service and UI. Products such as SigNoz and ClickStack package those surrounding layers. OpenObserve takes the other path: it integrates ingestion, indexing, retention, search, dashboards, and alerting in one product, with defaults tuned for logs. Its single-node mode can run as one binary; its HA architecture adds the infrastructure required for durability and scale while keeping the observability experience integrated. If you are weighing self-hostable options, this is the same decision you face when you read our roundup of open source observability tools or compare Elasticsearch alternatives, where ClickHouse also comes up as a logs backend.

Cost keeps this comparison honest. In Grafana's 2025 observability survey, 74 percent of respondents said cost is a top factor when picking observability tools, and the average organization runs eight observability technologies (Grafana 2025 survey). Log volume is the reason: Chronosphere's 2024 practitioner survey found log data growing 250 percent year over year on average (Chronosphere, October 2024). Storage and query efficiency are not academic here. They are the bill.

OpenObserve as an integrated observability product versus an assembled ClickHouse-based stack

The diagram compares the observability application layers supplied by each approach. Both still need a collector for Kubernetes logs, and production HA deployments need supporting infrastructure.

How we ran it

The methodology matters more than any single number, so here it is in full. The driver, the schemas, and every query live in the benchmark repository, so you can reproduce or dispute anything below.

Hardware and versions

Each engine had its own node, and we benchmarked them one at a time with the driver running on the engine's node. Neither could contend with the other for CPU or page cache. A third identical node ran the data generator.

  • Headline run: three AWS c7gd.8xlarge nodes (32-core Graviton3, 61.6 GiB usable RAM, 1900 GB local NVMe instance store), Ubuntu 24.04.
  • Disk-tier run: the same layout on c7g.8xlarge nodes, each engine on a 2000 GiB gp3 EBS volume provisioned at 16,000 IOPS and 1000 MB/s, which is gp3 at its maximum throughput, so the comparison is against gp3 at its best rather than a throttled default.
  • ClickHouse 26.7.1 (single node, native binary) and OpenObserve v0.92.0-rc2 (single node, open source), the exact versions pinned by the repository's install scripts, so both runs used identical builds. Two version notes for precision: the OpenObserve build is a release candidate from the 0.92 line rather than a stable tag, and ClickHouse published its 26.7.1 stable tag on July 22, 2026, days after these runs.

Note the ratio: 61.6 GiB of RAM against 937 GiB of ClickHouse data and 621 GiB of OpenObserve data on disk. The working set cannot fit wholly in page cache, and the cold run drops the OS page cache before the query to exercise the storage path. Neither run measures either engine querying from S3.

The dataset

We generated 1 billion synthetic Kubernetes log records with a deterministic Rust generator (fixed random seed) that streams every batch to both backends simultaneously, so they received an identical 1,000,000,000-row stream. The stream is the generator's 100M-record output ingested ten times; we verified row counts against the expected total after every 100M batch, and both engines finished at exactly 1,000,000,000 records with zero failures. The raw JSON totaled 2.04 TiB. Each record carries realistic fields: pod and container names, namespaces, HTTP method and status, trace and span ids, and a log message built from real-world templates. The needles the queries search for hit real data: the benchmark's trace id matches 60 rows out of the billion, verified directly against ClickHouse after ingest.

The queries

We ran three families of queries, each published verbatim in the repository, and measured server-side latency five times per query. The first run comes right after dropping the OS page cache, so run one is cold; the warm number is the fastest of the next two runs, matching how the published report page computes it. We report both.

  • Needle counts (Q0 to Q7): find rows by an exact trace id, span id, pod name, or a token inside the message.
  • Aggregations (Q8 to Q10): an events-over-time histogram, a top-N by namespace, and a filtered histogram.
  • Row fetches (Q11 to Q18): the same filters, but SELECT * ... ORDER BY _timestamp DESC LIMIT 100, which is what a log viewer actually does.

Fairness controls

Two systems are only comparable if they answer the same questions the same way, so both engines ran the same generated data, the same needles, and the same five-run cold-then-warm protocol, with each engine's own result cache disabled per request. Every measurement is a real search, not a cached answer.

And one disclosure that belongs in every vendor benchmark: we make OpenObserve. Treat the numbers accordingly: the repository exists so you can rerun everything on your own account and tell us where we are wrong.

Config disclosure

The most common way a vendor benchmark goes wrong is tuning your own tool and running the competitor near defaults, so here is exactly what each side ran. Read this section before you read the numbers. Both engines had full search indexes on the same fields; neither side ran unindexed.

OpenObserve indexes are set with environment variables before ingest.

ZO_ENABLE_INVERTED_INDEX=true
ZO_FEATURE_FULLTEXT_EXTRA_FIELDS=message
ZO_FEATURE_INDEX_EXTRA_FIELDS=trace_id,span_id,kubernetes_pod_name

That is the entire index configuration. OpenObserve builds a Tantivy full-text index on message and secondary indexes on the marked columns automatically, described in the full-text search functions docs.

ClickHouse needs the same fields indexed through its own native mechanisms: three bloom_filter skip indexes on the high-cardinality columns, and one full-text text() index on message. These indexes cover the same fields, but their data structures and pruning behavior are not identical.

CREATE TABLE k8s_logs
(
    `_timestamp`          UInt64,
    `message`             String,
    `trace_id`            String,
    `span_id`             String,
    `kubernetes_pod_name` String,
    -- 22 more columns, LowCardinality on repetitive string dimensions
    INDEX idx_trace_id   trace_id            TYPE bloom_filter(0.01) GRANULARITY 1,
    INDEX idx_span_id    span_id             TYPE bloom_filter(0.01) GRANULARITY 1,
    INDEX idx_pod_name   kubernetes_pod_name TYPE bloom_filter(0.01) GRANULARITY 1,
    INDEX idx_message_text message TYPE text(tokenizer = splitByNonAlpha, preprocessor = lower(message))
)
ENGINE = MergeTree
ORDER BY (_timestamp)
SETTINGS index_granularity = 8192;

One gap you should know about: this table carries no explicit compression codecs, so it used ClickHouse's default compression. ClickHouse's own observability guidance recommends ZSTD(1) on columns and Delta plus ZSTD(1) on a monotonic timestamp, which compresses log data considerably better than the default. We did not apply them. That affects the storage numbers below, and to a lesser degree the cold reads, so treat this run as ClickHouse at default compression rather than ClickHouse tuned for storage.

Two more choices a ClickHouse expert would examine. The sort key is the timestamp (ORDER BY _timestamp), the layout a general-purpose log table ships with; a sort key designed around these specific predicates (leading with the pod or namespace column, say) would trade that general layout for wins on exactly the queries ClickHouse lost. And ingest used large batched inserts from the generator, not per-row inserts, so ClickHouse was not handicapped by the small-insert pattern its docs warn about.

Results

Ingest

Because the generator writes the same batches to both systems at once, ingest throughput is a single number for the pair, and it was generator-bound rather than engine-bound on both disk tiers: 71,865 records per second (153.35 MB/s) on NVMe and 71,311 records per second (152.17 MB/s) on gp3. Loading 1 billion rows took about 3.9 hours per run: 14,023 seconds measured wall clock on gp3, and 13,915 seconds on NVMe, reconstructed from the sustained rate because that run's wall-clock log was not preserved. Both engines kept up with the same stream and neither dropped a record: zero failures across all twenty 100M-row batches, and both engines counted exactly 1 billion rows at the end of each run.

Storage

Storage settled to effectively identical totals on both disk tiers: the gp3 run measured 620.9 GiB for OpenObserve and the same 937.2 GiB for ClickHouse. The table shows the NVMe run; each engine's number includes its own search indexes.

Engine Data Search index Total on disk Compression (raw to disk)
OpenObserve 372.4 GiB 248.6 GiB (Tantivy) 621.0 GiB 3.36x
ClickHouse 677.1 GiB 260.1 GiB (text + bloom filters) 937.2 GiB 2.22x

On-disk storage at 1 billion rows: OpenObserve 621.0 GiB, ClickHouse 937.2 GiB, and the 2083.9 GiB of raw JSON both ingested

The report's storage view. The multipliers are relative to the smallest footprint, so raw JSON shows 3.36x OpenObserve's total; the compression column in the table divides the other way.

OpenObserve wrote about a third less to disk, and the index cost is nearly the same on both sides. The difference is in the data itself, where OpenObserve's Parquet columns are smaller than ClickHouse's default-codec columns. The honest reading is the caveat from the config section: ClickHouse ran default compression, not the ZSTD(1) plus Delta codecs its own documentation recommends for log data (ClickHouse blog, where they document 14x to 33x compression depending on the ingestion path). A codec-tuned ClickHouse would close much of this gap, so we are not claiming a storage win here. If storage is your deciding factor, set the codecs and measure it yourself. The more important system question is whether object storage is the default durable data layer or an additional storage tier and cache policy to design. This benchmark used local disks for both engines, so it does not turn that architectural difference into a cost result. See our log management tools comparison for how footprint plays out across other backends.

Query latency

Times below are from the NVMe run: the cold (cache-dropped) run and the warm number, lower is better. This table shows representative queries; the benchmark repository contains all 19.

Query OpenObserve cold OpenObserve warm ClickHouse cold ClickHouse warm
trace_id count 714 ms 58 ms 677 ms 51 ms
span_id count 278 ms 58 ms 496 ms 49 ms
rare token count 151 ms 59 ms 122 ms 26 ms
"failed" count (common token) 217 ms 129 ms 1.19 s 553 ms
pod_name count 82 ms 71 ms 5.05 s 989 ms
histogram, 1h buckets 673 ms 661 ms 1.15 s 464 ms
top-N namespaces 1.05 s 555 ms 1.39 s 485 ms
filtered histogram 715 ms 670 ms 1.19 s 668 ms
trace_id SELECT * 514 ms 191 ms 732 ms 163 ms
"failed" SELECT * 196 ms 78 ms 80 ms 39 ms
pod_name SELECT * 943 ms 339 ms 508 ms 130 ms

✅ marks the faster engine in each cold pair and each warm pair. Across all 19 queries on NVMe, ClickHouse was faster on 15 of the warm runs and OpenObserve on 4; cold, OpenObserve was faster on 10 and ClickHouse on 9. The warm filtered histogram is effectively a tie at 668 ms against 670 ms.

Those win counts need reading together, because they describe different shapes of victory. ClickHouse's warm wins are mostly tens of milliseconds: 51 ms against 58 ms on the trace id, 49 ms against 58 ms on the span id, margins nobody feels in a log viewer. OpenObserve's wins are measured in multiples, and they land on the two query shapes that hurt most in production. A common token, "failed", defeats ClickHouse's text-index pruning. The rare request id lives in a handful of data blocks, so the index skips nearly everything; a token that recurs throughout a billion rows sits in essentially every block, leaving nothing to skip, and ClickHouse pays scan-level cost: 553 ms warm against OpenObserve's 129 ms, a 4.3x gap. A pod name whose matches are scattered across the full time range does the same to the bloom filter: 989 ms warm against 71 ms, 13.9x, and 5.05 seconds against 82 ms cold. A bloom filter tells ClickHouse which granules might contain a value, and with matches spread across a billion rows that still means touching a lot of granules. OpenObserve's inverted index points at the rows directly, so its latency depends on the result, not on how the matches are scattered. Our query performance tuning docs go deeper on why the index paths behave this way.

OpenObserve versus ClickHouse warm query latency across all benchmark queries at 1 billion rows, lower is better

One note on reading that chart: the multipliers in it follow the ClickBench convention of adding 10 ms to both sides before dividing, which damps the ratios on very fast queries. That is why it labels the warm pod-name gap 12.33x while the raw numbers (989 ms against 71 ms) divide out to 13.9x. We quote the raw ratio in this post and left the chart on the standard convention.

Does the disk tier matter?

We ran the identical benchmark a second time on gp3 EBS, provisioned at gp3's maximum 16,000 IOPS and 1000 MB/s, so this is gp3 at its best. Warm latencies barely changed for either engine: warm queries are answered from RAM and indexes, and the warm win count stayed 15 to 4 for ClickHouse. Cold is a different story.

Query (cold run) ClickHouse on NVMe ClickHouse on gp3 ClickHouse change OpenObserve on NVMe OpenObserve on gp3
pod_name count 5.05 s 14.16 s 2.8x slower 82 ms 141 ms
"failed" count 1.19 s 2.94 s 2.5x slower 217 ms 209 ms
histogram, 1h buckets 1.15 s 2.82 s 2.4x slower 673 ms 809 ms
top-N namespaces 1.39 s 3.62 s 2.6x slower 1.05 s 1.20 s
filtered histogram 1.19 s 2.95 s 2.5x slower 715 ms 654 ms
span_id SELECT * 552 ms 1.26 s 2.3x slower 175 ms 219 ms
compound SELECT * 651 ms 1.49 s 2.3x slower 253 ms 232 ms

The seven cold queries in the table got 2.3x to 2.8x slower moving from NVMe to gp3, because their cold path reads enough data to be bound by the disk. Averaged across all 19 queries (geometric mean), the slower disk cost ClickHouse 1.8x on cold runs and cost OpenObserve 3 percent. OpenObserve's cold latencies changed little in either direction, because its index reads touch little data, so the disk tier barely shows up: several improved, and its one sizable regression was the pod-name row fetch, 943 ms to 1.60 s. The extreme is the pod-name count: 141 ms against 14.16 seconds, roughly a 100x gap on the exact query an engineer runs first during an incident. Cold overall on gp3, OpenObserve was faster on 13 of 19 queries; ClickHouse kept 6: two token counts, three token row fetches, and the pod-name row fetch (936 ms against OpenObserve's 1.60 s, OpenObserve's worst cold query on gp3).

This matters because incident investigation often reaches data that is not already cached. The first query against last night's logs is a cold query, and on commodity cloud disks the two engines give you very different first answers: on gp3, OpenObserve's cold latencies stayed between 114 ms and 1.60 s across all 19 queries, while ClickHouse's ranged from 34 ms to 14.16 s.

Cold-run query latency on gp3 at 1 billion rows: ClickHouse cold latencies grow with the slower disk while OpenObserve's barely move

Full-text log search

In OpenObserve, searching text is one function on data that was indexed automatically.

-- OpenObserve: find a request id anywhere in the message
SELECT count(*) FROM k8s_logs WHERE match_all('474dbfe4c65a971176ae239869a6ba47');

In ClickHouse the same search works, but only because we added the text() index in the schema above, and you query it with the matching function.

-- ClickHouse: same needle, through the text() index
SELECT count() FROM k8s_logs
WHERE hasAnyTokens(message, ['474dbfe4c65a971176ae239869a6ba47']);

Both found the needle in a billion rows fast, and warm, ClickHouse was faster: 26 ms against 59 ms. The difference is setup, not capability: ClickHouse's full-text indexing is something you choose, size, and maintain, while on OpenObserve it is on by default for the fields you mark. And the same text index that wins on a rare token is the one that falls behind by 4.3x when the token is common: indexed search in ClickHouse is sensitive to how often the term appears, where OpenObserve's stays flat.

Where ClickHouse wins

An honest benchmark reports its losses, and ClickHouse beat OpenObserve in real ways here.

  • Warm, it won 15 of 19 queries. Most margins are small, 51 ms against 58 ms on a trace id, but they are consistent, and on the token-needle counts the gap is 2.3x to 3.3x (26 ms against 59 ms on the rare token, 25 ms against 82 ms on the double token).
  • It won the aggregations warm: 464 ms against 661 ms on the hourly histogram and 485 ms against 555 ms on top-N. Full-range scans are the work its engine was built for.
  • Row fetches lean its way warm, 6 of the 8, and it takes the heaviest one on both disk tiers: pulling the last 100 lines for a busy pod took 130 ms against 339 ms warm, and it stayed ahead on that fetch cold on both tiers (508 ms against 943 ms on NVMe, 936 ms against 1.60 s on gp3).
  • Then there is everything else a full analytical database gives you: window functions, joins across datasets, and reuse of a cluster you already run. That is why ClickHouse-backed stacks like SigNoz and ClickStack exist, as we note in our open source APM tools roundup.

Where OpenObserve wins

  • The searches that hurt. The two query shapes that defeat index pruning, a common token and a high-cardinality equality with scattered matches, are exactly where OpenObserve pulls away by multiples: 4.3x and 13.9x warm, 100x cold on gp3.
  • Cold behavior. It was faster on 10 of 19 cold queries on NVMe and 13 of 19 on gp3, and its cold latencies are predictable: 82 ms to 1.05 s across all 19 on NVMe, 114 ms to 1.60 s on gp3. ClickHouse's cold range on the same runs was 27 ms to 5.05 s, and 34 ms to 14.16 s.
  • Disk independence. Moving from NVMe to gp3 multiplied ClickHouse's heaviest cold latencies by 2.3x to 2.8x and left OpenObserve's nearly unchanged. That is worth real money, because it means the cheaper disk tier does not cost you first-query latency.
  • Operational surface. Three environment variables replaced a hand-authored 27-column table definition, three skip indexes, and a full-text index. OpenObserve inferred the incoming fields and their types rather than requiring us to define them before ingest.
  • Text search and a query interface ship in the binary, with no separate index to size or UI layer to run.

When to pick which

Query performance is the last piece of this decision, not the whole decision. This benchmark asked a narrow question: if a team chooses OpenObserve for its object-storage-first architecture, automatic schema evolution, and integrated observability experience, does it have to accept weak query performance? At a billion rows, the answer was no, with an honest asymmetry. Warm, ClickHouse is faster on most queries by tens of milliseconds; OpenObserve is faster on the heaviest search patterns by multiples. Cold, and especially cold on commodity disks, OpenObserve is the more predictable engine.

The setup was deliberately conservative for that broader claim. ClickHouse ran native MergeTree on local disk, while OpenObserve also ran on local disk even though its cloud-native architecture uses object storage as the durable system of record. That makes the latency comparison reproducible, but it leaves OpenObserve's production storage economics and elasticity out of the result. This is not an S3 performance or total-cost benchmark; it shows that OpenObserve remains competitive even without counting one of the main reasons teams choose it.

The operating model is different too. The ClickHouse table in this test explicitly defined 27 columns and four indexes. Maintaining that optimized schema and index layout as logs gain fields or change shape is part of operating the system. OpenObserve discovers fields, infers their types, and evolves the stream schema during ingestion. ClickHouse gives you a powerful analytical database; OpenObserve gives you an observability product that integrates ingestion, search, dashboards, alerts, and retention across logs, metrics, and traces. A single-node OpenObserve deployment can run as one binary, and the product remains integrated when the infrastructure scales out.

So the practical answer is not "use ClickHouse for histograms and OpenObserve for needle searches." The split is what you are choosing. If you are picking a database engine, ClickHouse can win that contest: analytical SQL, consistently fast warm queries, and a cluster you may already run well. If you are starting an observability platform, the small warm margins matter less than what surrounds them: storage architecture, cost, schema evolution, and the work of building and operating everything around the engine.

For most teams looking for a complete cloud-native observability system, start with OpenObserve. It does not win every query; ClickHouse's warm wins and its row-fetch performance in this benchmark are real. But performance is no longer a reason to give up low-cost object storage, schemas that adapt as logs change, and one integrated product instead of a database around which you still have to assemble the experience.

Try OpenObserve

If automatic schema evolution, built-in indexed search, and an integrated observability experience appeal to you, the fastest way to feel the difference is to point some real logs at it. OpenObserve Cloud gives you ingestion, full-text search, dashboards, and alerting without running the binary yourself, and the same engine is open source if you prefer to self-host. Every schema, query, and install script from this benchmark is in the benchmark repository if you want to rerun it on your own account.

Frequently Asked Questions

About the Authors

Hengfei Yang

Hengfei Yang

TwitterLinkedIn

Hengfei Yang is the founding engineer at OpenObserve. He has extensive experience in distributed system development. He is passionate about open source and has interests in traveling, music and photography.

Huaijin Hao

Huaijin Hao

LinkedIn

Huaijin Hao is a backend engineer at OpenObserve, focused on building scalable, high-performance distributed systems. He is passionate about cloud-native technologies and open source.

Gorakhnath Yadav

Gorakhnath Yadav

TwitterLinkedIn

Gorakhnath is a passionate developer advocate, working on bridging the gap between developers and the tools they use. He focuses on building communities and creating content that empowers developers to build better software.

Latest From Our Blogs

View all posts