Skip to content

CostBench: Putting real-time analytics to the test

tom schreiber headshotlio headshot singapore
Sep 8, 2026 · 10 minutes read

TL;DR

Real-time performance per dollar depends on both the cost of keeping incoming data query-ready and how much work that preparation leaves for the query engine.

  • CostBench measures both effects as one continuous workload under sustained load: fresh rows are ingested and made query-ready while aggregate and drill-down queries continue to run.
  • We tested ClickHouse Cloud, Snowflake, Databricks, BigQuery, and Redshift Serverless using each vendor’s recommended real-time ingest path, streaming more than 100 billion stock-market quotes at a target rate of 1 million rows per second.
  • ClickHouse Cloud had the lowest fresh-data-path cost, query-serving cost, and accumulated runtime. The tested alternatives delivered 412–1,996× worse end-to-end performance per dollar.
  • This post launches a series of 1:1 analyses tracing the architectures and billing behind those results.

This is Part 1 of our CostBench series.
Other parts: Snowflake · Databricks · BigQuery · Redshift
Coming next: Why ClickHouse Cloud won

The path from fresh data to fast queries shapes performance per dollar

A real-time analytics system serves queries over a dataset that is constantly changing. New rows keep arriving while users, applications, and agents query data that may already span billions or trillions of rows. Each new row must become query-ready while those queries continue to run.

CostBench measures the end-to-end performance and cost of real-time analytics across cloud data warehouses. Its first results show that query engines are only part of what separates these systems:

Systems vary dramatically in the cost and efficiency of carrying incoming data from arrival to query readiness.

Under sustained load, that work can shape performance per dollar as much as query execution itself: it carries its own direct cost, and the state it produces determines how much work remains for the query engine.

This round tested ClickHouse Cloud, Snowflake, Databricks, BigQuery, and Redshift Serverless using push-based ingestion. Fresh quotes kept arriving while aggregate and drill-down queries ran, so each system had to prepare new data and answer queries at the same time.

We’ll first show what makes data query-ready, then put the systems under continuous load. The results reveal a gap in both runtime and cost. From there, we’ll explain how CostBench combines those dimensions, and why the path from an incoming row to a current answer matters so much.

What makes data query-ready

Consider a common analytical question about stock quotes: How many AAPL quote updates arrived each day?

The query filters for AAPL, groups its quotes by day, and counts the rows in each group:

SELECT day, COUNT(*)
FROM quotes
WHERE sym = 'AAPL'
GROUP BY day;

This is exactly the kind of query analytical systems are built to run fast.

Query-ready data is organized and prepared so the query engine reads less data and does less work at query time.

For our “AAPL a day” query, three steps help the engine read less data and do less work.

Store data by column

Our query needs sym to find AAPL’s quotes and day to group them. It does not need any other column.

Columnar storage lets the engine read only those two columns and skip the rest. In the example below, that means reading 2 columns instead of 7.

RESEARCH BACKGROUND: COLUMNAR STORAGE (click to expand)

Column-oriented storage has a long research history. A Decomposition Storage Model (Copeland and Khoshafian, SIGMOD 1985) examined storing attributes separately and the trade-offs involved. The Monet paper Database Architecture Optimized for the New Bottleneck: Memory Access (Boncz, Manegold and Kersten, VLDB 1999) explored how database layouts and algorithms could use the memory hierarchy more efficiently.

These papers provide background for why the arrangement of data matters before query execution begins.

Organize data for vectorized execution and chunk pruning

Columnar layouts also help the engine to process the data it reads efficiently. With a column’s values together in memory, the engine can apply an operation to a chunk of values in a simple loop. This vectorized execution reduces the overhead of handling rows individually. Suitable operations can also use SIMD instructions, which process several values with one instruction.

Before reading a chunk of stored data, the engine checks its metadata and skips it when the metadata shows it cannot contain values matching the query’s filter. This is chunk pruning.

Our query already reads only sym and day columns. But in the unsorted example, AAPL quotes appear in all three chunks, so the engine must read all three.

Sorting by sym keeps each stock’s quotes together. For WHERE sym = 'AAPL', the engine now reads one AAPL chunk and skips the MSFT and NVDA chunks. That leaves three AAPL quotes to group by day and count.

RESEARCH BACKGROUND: VECTORIZED EXECUTION (click to expand)

MonetDB/X100: Hyper-Pipelining Query Execution (Boncz, Zukowski and Nes, CIDR 2005) explains how processing chunks of values reduces tuple-at-a-time interpretation overhead while avoiding the large intermediate results of whole-column execution.

This helps explain the distinction above: pruning reduces how much data the engine processes; vectorized execution makes processing the remaining data more efficient.

Pre-aggregate to reduce query-time work

At billions or trillions of rows, scanning and aggregating raw data can be too slow for interactive analytics. When those queries run repeatedly, as they often do in dashboards, the compute cost can also add up.

Pre-aggregations move that work out of each query and into data preparation. They maintain a much smaller set of summary rows, reducing both the data read and the grouping and aggregation performed at query time. The summaries retain the grouping dimensions and aggregate results the queries need, allowing the engine to combine prepared results instead of calculating them from individual rows.

In our example, pruning leaves three AAPL quotes to group by day and count. Pre-aggregation prepares those counts in advance: it groups quotes by sym and day and calculates COUNT(*) for each group. This turns nine quotes into four summary rows, each containing a count for one stock on one day.

Because queries may still filter the summary rows, pre-aggregated data also benefits from a pruning-friendly physical layout. Our summary rows are ordered by sym. For WHERE sym = 'AAPL', the engine reads only the two AAPL summary rows and skips the MSFT and NVDA rows. Those two rows already contain the daily counts - two quotes on D1 and one on D2 - so the engine no longer needs to group and count the individual AAPL quotes on every execution.

For time-sensitive use cases such as monitoring market activity, these summaries must stay current with the underlying quotes, ideally updating as new quotes become queryable.

For our AAPL query, columnar storage lets the engine skip unused fields, sorting by sym lets it skip unrelated stocks, and current pre-aggregations let it skip grouping and counting individual quotes. Together, these preparations make data query-ready for analytical queries like this one: the query engine reads less data and performs fewer calculations.

Up to 1,996× better real-time performance per dollar

That’s what ClickHouse Cloud delivered in CostBench under continuous load. See what it can do for your data - sign up today.

Try ClickHouse Cloud

Real-time systems must make fresh data query-ready as it arrives

With a fixed dataset, that preparation can happen before queries begin. In a real-time system, new rows arrive continuously while queries keep running.

“A real-time system should have access to real-time data so it's viable for making real-time decisions based on the freshest data.” — Instacart Engineering

We call the continuous work that carries each new row from arrival to query readiness the fresh-data path. It comprises three responsibilities that must remain active in parallel:

① Ingest fresh data into columnar storage as it arrives.

② Maintain pruning-friendly physical layouts for event-level and pre-aggregated data through sorting, clustering, partitioning, or equivalent structures.

③ Maintain current pre-aggregations as new data arrives, keeping common grouping and aggregation work out of query time.

These are logical responsibilities, not a strict execution sequence. A system may fuse them in a single write path - for example, sorting incoming rows, computing pre-aggregations, and sorting those results before either representation is written to storage. Other systems complete some of this work asynchronously after ingestion.

Indexes, metadata, compaction, and other provider-specific structures also affect the cost and efficiency of this work.

Alongside the fresh-data path, the query engine serves two typical analytical paths. Both depend on the preparation described above:

  • Pre-aggregated path: Interactive aggregate queries depend on ③ to keep repeated grouping and aggregation out of query time. If they filter the summarized rows, they also depend on ② to prune irrelevant chunks.

  • Event-level path: Selective drill-down queries read event-level data directly and therefore depend on ② to avoid scanning most of the data. Even when these queries take seconds, pruning is what makes them feasible at scale; they may still group or aggregate the remaining rows.

An efficient fresh-data path lowers its own cost - and the work left for queries

A system can sustain ingestion while its query readiness falls behind.

Keeping up with incoming data does not guarantee that the system is keeping up with everything needed to query it efficiently.

That gap can affect answer freshness: new stock quotes may be ingested promptly, yet a risk query can still return results based on an outdated view of the market.

It can also affect query efficiency, even when queries can see the latest data. If ordering or pre-aggregation falls behind, the query engine may need to scan more data, reconcile newer rows, or repeat grouping and aggregation at query time.

The interactive diagram below shows this second effect while data keeps flowing. Watch the loop or drag the slider to see how the query engine’s scanning and aggregation work shrinks as ordering and pre-aggregation catch up - and grows when preparation falls behind.

That extra query work consumes read-side compute, making queries more expensive and answers slower. An efficient fresh-data path therefore pays off in three ways: lower preparation cost, lower query cost, and faster answers.

CostBench measures preparation cost, query cost, and runtime under continuous load

CostBench follows the same continuously growing dataset from ingestion through row visibility, event-level layout maintenance, and pre-aggregation freshness, while aggregate and drill-down queries run on a fixed schedule. The query definitions stay fixed, but their answers must continually account for newly arriving data. This makes both sides visible: the direct cost of keeping data query-ready and the performance and cost of serving queries from the state the system actually achieved.

The first results show that some systems sustained ingestion while pre-aggregation lag accumulated or query latency rose - moving more work onto the read path and driving up query-serving cost.

How CostBench keeps the comparison fair

This round used push-based ingestion across ClickHouse Cloud, Snowflake, Databricks, BigQuery, and Redshift Serverless: one shared client sent the stream directly through each system’s recommended low-latency ingest path. Four controls kept the comparison like-for-like while respecting each system’s architecture:

  • Same workload: Each system received the same 113.2-billion-row National Best Bid and Offer (NBBO) stock-market quotes dataset, schema, layout intent, target ingestion rate of 1 million rows per second, queries, and cadence.

  • Comparable resources: We aligned read-side compute where a meaningful comparison was possible and used the smallest tested configurable fresh-data-path compute that sustained the target during calibration.

  • Complete platform paths: Each system used its recommended real-time architecture with a low latency push-based ingestion path. Managed and serverless services retained their native scaling, and their metered work remained in the benchmark’s cost.

  • One shared harness: The same source files, decoding logic, client host, rate controller, scheduler, timing, and recording logic drove every system; only the destination adapter changed.

We configured matching sorting or clustering keys across systems for both raw data and daily pre-aggregations. These layouts support the workload’s stock-symbol filters. The pre-aggregations group quotes by stock symbol and day, maintaining the counts, sums, and price minima and maxima that the aggregate queries use. Each system therefore had to keep the same preparation current for the same query workload as new rows arrived.

Later rounds may also test pull-based variants using one shared external stream, to give a more complete picture of real-time performance per dollar.

The shared harness holds the workload constant. CostBench measures how each system handles it.

BENCHMARK METHODOLOGY AND FAIRNESS CONTROLS (click to expand)

INGESTION MODEL

The client read NBBO data from Parquet files, decoded it into raw rows, and pushed the stream directly into each platform’s target tables through its vendor-recommended low-latency real-time ingest path. Later rounds will test pull-based variants using one shared external stream.

WORKLOAD MODEL

In a real-time analytics system, new events are generated continuously at the source at an application-driven rate. Future rows do not yet exist, so the database is never handed a complete backlog to load as quickly as possible. CostBench reproduces that operating condition by using the Parquet files as a deterministic source, decoding their rows, and releasing them as a paced continuous stream while each platform keeps the arriving data query-ready and serves scheduled queries.

DATASET AND ACTIVE WINDOW

Every system received the same complete 113.2-billion-row dataset of narrow, 12-column National Best Bid and Offer (NBBO) stock-market quotes.

All systems used the following sorting or clustering keys:

  • Event-level data: (sym, t) — stock symbol and event timestamp.
  • Daily pre-aggregations: (sym, day) — stock symbol and day.

The harness replayed the entire source as one uninterrupted stream toward a target rate of 1 million rows per second and continued until the final source row had been ingested. At exactly the target rate, that active-ingestion window would last about 31.4 hours; observed durations varied by system.

SHARED INGESTION CLIENT

The same source files, schema, row-decoding logic, and workload-generation code drove ClickHouse Cloud, Snowflake, Databricks, BigQuery, and Redshift Serverless. For every system, the client ran on the same AWS EC2 m6i.8xlarge instance, used the same rate controller, and adjusted batch size and worker parallelism to pace the destination toward the target.

BEST-PRACTICE PLATFORM CONFIGURATION

Only the destination-specific delivery adapter changed. Inside each platform, we used its vendor-recommended low-latency push-based real-time ingest path and documented best practices for keeping incoming data query-ready. Differences between those paths are part of the benchmark.

RESOURCE-SIZING POLICY

We aligned read-side compute by estimated CPU capacity wherever the platforms allowed a meaningful comparison. For configurable fresh-data-path compute, we used the smallest tested configuration that sustained the target rate of 1 million rows per second during calibration while keeping incoming data query-ready. Managed and serverless components used their native scaling, and their metered work remained part of the benchmark’s cost. Observed end-to-end throughput is reported separately from the configured target.

SHARED QUERY DRIVER

From the beginning of ingestion through the final source row, the same four interactive aggregate queries ran every 10 minutes and the same two selective drill-down queries every hour, using identical fixed-rate scheduling, timing, and result-recording logic.

QUERY WORKLOAD

The workload is deliberately designed to stress test the efficiency of the fresh-data path: queries run throughout ingestion, and their answers must account for newly arriving data rather than a fixed historical slice. Each system must therefore keep preparing fresh data for those queries while continuing to serve them.

The query labels describe the data path, not whether the SQL itself contains aggregation: aggregate queries read maintained pre-aggregations; drill-down queries calculate directly from event-level rows.

How fresh arrivals enter the queries

All six queries cover the history ingested so far, without a date or time cutoff:

  • Queries filtered by symbol (A1, A2, D1, D2): The selected symbols stay the same, but their matching data keeps growing as more quotes arrive. For example, sym = 'AAPL' includes newly ingested Apple quotes alongside its earlier quotes. The eight-symbol watchlist works the same way.
  • Queries without symbol filters (A3, A4): These cover all symbols through the maintained daily pre-aggregations. Newly ingested quotes contribute to the summaries queried for historical price ranges and daily market activity.

For aggregate queries, those incoming quotes must be incorporated into the pre-aggregations. For drill-down queries, the new event-level rows must be available in a layout that supports efficient filtering.

What each query does

Interactive aggregate queries (A1–A4) read the continuously maintained daily pre-aggregation. A1 returns an all-time summary for one symbol; A2 summarizes an eight-symbol watchlist; A3 finds the largest historical price ranges by symbol; and A4 returns market-wide activity by day. A1 and A2 use the leading sym layout key, while A3 and A4 read the complete rollup.

Selective drill-down queries (D1–D2) read event-level data directly for one symbol across its complete history. D1 builds hourly OHLCV bars with VWAP, volatility, spread, and quote count. D2 returns a single-row risk-and-liquidity profile covering mid-price volatility, spread distribution and tail percentiles, order-book imbalance, and spread-versus-depth correlation.

How preparation matches the queries

The workload exercises both forms of preparation. D1/D2 filter the raw data by sym, the leading column of (sym, t). A1/A2 filter the daily summaries by that same leading column in (sym, day); A3/A4 read summaries across all symbols.

The daily pre-aggregation groups by (sym, day) and maintains the counts, sums, minima, and maxima used by A1–A4. For example, total quotes comes from summing prepared quote counts, while average spread comes from dividing the accumulated spread sum by the accumulated quote count.

CACHE POLICY

Query-result caching was disabled on every system so measured runtimes reflect query execution rather than retrieval of a previously computed answer. This also matches the workload’s freshness requirement: newly arriving data must contribute to the answers, so an earlier cached result may no longer be current.

Ordinary underlying data caches were allowed to remain warm, reflecting continuous operation. These caches can reduce the cost of reading data, but they do not replace query execution: the engine must still account for new arrivals and perform the filtering, reconciliation, or aggregation required by each query.

The full benchmark definition, accepted runs, results, cost summaries, and reproduction steps are open in the CostBench repository.

The gap opens while the data is still arriving

As the dataset grew toward 113.2 billion rows, the systems kept preparing fresh quotes and answering queries. The three charts below show what accumulated along the way: fresh-data-path cost, normalized query-serving cost, and query runtime.

ClickHouse Cloud stays near the bottom in all three. Across the accepted comparisons, it spent less keeping incoming data query-ready, less serving the measured queries, and less time answering them. Its advantage was already visible while ingestion was running.

Those three results are connected. Preparing data efficiently carries a direct cost, but it also changes the work left for the query engine. CostBench combines preparation cost, query-serving cost, and query runtime into one score to show their overall effect.

Turning runtime and cost into performance per dollar

A fast query is useful; delivering it economically while new data keeps arriving is the full challenge. CostBench combines the three inputs shown above:

  1. Fresh-data-path cost: ingestion, layout maintenance, and pre-aggregation during the complete ingest.
  2. Normalized query-serving cost: the measured query workload priced at the applicable read-side compute rates.
  3. Accumulated query runtime: the time spent answering accepted queries while ingestion continued.

The score multiplies combined cost by accumulated query runtime. Lower cost improves the score; faster queries improve it too. Each provider’s result is compared with its matched ClickHouse baseline.

The score rewards systems that keep incoming data query-ready economically and serve the workload quickly. Lower is better.

Database storage costs are excluded from the score because their impact is small over the benchmark’s duration; see the calculation note below.

SCORE CALCULATION AND METERING NORMALIZATION (click to expand)

WHAT THE FULL-PATH SCORE MEANS

The score answers one question:
Where do you get the most full-path real-time performance per dollar spent?

It combines the cost of keeping continuously arriving data query-ready with the cost and total runtime of serving the same query schedule. A lower score means a better combination of lower end-to-end cost and faster query serving under continuous ingestion.

CALCULATION

End-to-end score = (fresh-data-path cost + normalized query-serving cost) × total query runtime. Lower is better.

FRESH-DATA-PATH COST

The fresh-data-path component uses the complete-ingest cost of accepting the full 113.2-billion-row stream and keeping event-level and pre-aggregated data query-ready. Configured compute is priced for its observed active duration; managed and serverless services use their metered work.

NORMALIZED QUERY-SERVING COST

We normalize query cost as accumulated end-to-end runtime × read-side compute price, as if compute were billed per second. This compares how much query work each system completes for the same amount of paid compute time. Per-second normalization removes differences in idle timeouts and minimum billing windows.

For this benchmark, we apply that calculation to equal numbers of query executions while ingestion is running, matched by dataset progress. Each execution’s full end-to-end runtime is multiplied by the applicable read-side compute rate, and the costs are summed. The result is a normalized comparison cost rather than a reconstruction of actual bills.

WHAT ABOUT STORAGE COSTS?

Database storage is excluded from the score. Earlier CostBench experiments with approximately 113.2 billion rows illustrate its limited impact over a short ingest run: the measured raw-table and pre-aggregation footprints corresponded to approximately $9.83 per month for ClickHouse Cloud and $16.07 for Snowflake, using the benchmark’s recorded ClickHouse and Snowflake storage rates.

Charging those complete footprints for an entire 37-hour run would add approximately $0.50 and $0.81, respectively, using a 730-hour month. This deliberately charges the final footprint throughout, even though the dataset grows during ingestion. For context, the current comparison’s fresh-data-path costs are $28.69 and $79.42, respectively. These storage estimates illustrate scale; they are not measured storage bills for the current runs. Current cost results.

Storage costs become more important over longer retention periods. This exclusion concerns database storage; required ingestion infrastructure, including Redshift’s MSK broker storage, remains included in the fresh-data-path cost.

BOUNDARY AND EXCLUSIONS

Fresh-data-path cost covers the complete 113.2-billion-row ingest. Query-serving cost and total runtime cover matched, accepted queries during active ingestion. Storage, free tiers, discounts, idle capacity, minimum billing, post-ingestion queries, failed attempts, and standing fallback capacity are excluded. Provider-specific fallback allocations in the accepted query-cost model remain included.

RANKING

Each provider’s end-to-end score is divided by the score of its matched ClickHouse comparison. ClickHouse is the 1× baseline in each comparison.

RELATED METHODOLOGY

This extends CostBench’s earlier metering-granularity and ranking methodology from query-side tests to the complete fresh-data path.

The fresh-data path widens the performance-per-dollar gap

We began with a thesis:

Under sustained load, keeping data query-ready can shape performance per dollar as much as query execution itself.

The results now show just how large that impact can be.

Query-side performance: a 23–101× advantage

The earlier query-side comparison began with an already-loaded dataset and measured query-serving cost and runtime. ClickHouse led that comparison: Databricks, Snowflake, Redshift Serverless, and BigQuery Capacity had query-side scores 23–101× higher.

This round asks a broader question: what happens to performance per dollar when fresh data keeps arriving and each system must prepare it while serving queries?

End-to-end performance: a 412–1,996× advantage

In the continuous-ingestion benchmark, Snowflake’s end-to-end score was 412× higher than its matched ClickHouse baseline; BigQuery Capacity’s was 438×, Databricks’ 752×, and Redshift Serverless’ 1,996×. Lower is better.

The larger range comes from a broader benchmark scope: data preparation and query serving running together. It shows how much the complete path can matter to performance per dollar.

Why the complete path matters

The final chart brings runtime and cost together. ClickHouse Cloud combines the lowest accumulated query runtime with the lowest combined preparation and normalized query-serving cost in the accepted comparisons.

Its fresh-data path performs ordering and pre-aggregation as incoming blocks are inserted. Raw data and summaries advance together, so queries can use current, prepared data without waiting for a separate summary-refresh cycle.

RESEARCH BACKGROUND: CLICKHOUSE’S ARCHITECTURE (click to expand)

ClickHouse — Lightning Fast Analytics for Everyone (Schulze et al., PVLDB 2024) describes the engine mechanisms behind these examples. Sections 3.1–3.3 cover storage parts, pruning and merge-time transformations, including the discussion of incremental materialized views. Section 4 explains query processing and vectorized execution.

The paper provides architectural background; the performance-per-dollar results in this article come from CostBench’s measurements.

That connects the result back to the AAPL example. Reading fewer columns, pruning irrelevant data, and reusing prepared aggregates all reduce query work.

A real-time system must keep those advantages as the dataset changes. ClickHouse Cloud did so at the lowest measured cost in these comparisons.

The provider deep dives examine where each system spends that preparation effort and what happens when it falls behind. The ClickHouse Cloud–Snowflake comparison follows the 412× result through ingestion, materialized-view refresh, query compilation, and billing.


Share this post

  • Y Combinator icon
  • X icon
  • Bluesky icon
  • Facebook icon
  • LinkedIn icon

Subscribe to our newsletter

Stay informed on feature releases, product roadmap, support, and cloud offerings!

Recent posts

Alex Soffronow Pagonidis · Oct 16, 2026
Andrey Chudnovskiy · Oct 15, 2026

Follow us

XBlueskySlackGithubTelegramMeetupRSS