---
title: "Why Snowflake can’t match ClickHouse Cloud for real-time analytics"
date: "2026-10-16T09:35:06.474Z"
author: "Tom Schreiber and Lionel Palacin"
category: "Engineering"
excerpt: "ClickHouse Cloud delivered 412× better performance per dollar than Snowflake in CostBench. We trace the gap from fresh data arriving to fast answers coming back."
---

# Why Snowflake can’t match ClickHouse Cloud for real-time analytics

<style>
details.note-box {
  margin: 28px 0;
  background: #2B2B2B;
  color: #E2E8F0;
  border-left: 5px solid rgba(255,255,255,.1);
  border-radius: 12px;
}

details.note-box > summary {
  position: relative;
  padding: 18px 48px 18px 24px;
  cursor: pointer;
  list-style: none;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.5;
  text-transform: uppercase;
  border-radius: 10px;
}

details.note-box > summary::-webkit-details-marker {
  display: none;
}

details.note-box > summary:hover {
  background: rgba(255,255,255,.05);
}

details.note-box > summary::after {
  content: "▶";
  position: absolute;
  right: 20px;
  color: #A0AEC0;
  font-size: 12px;
  transition: transform .2s;
}

details.note-box[open] > summary::after {
  transform: rotate(90deg);
}

details.note-box > .note-body {
  padding: 8px 24px 28px;
}

/* Comfortable line spacing */
details.note-box > .note-body > p,
details.note-box > .note-body li {
  font-size: 15px;
  line-height: 1.7;
}

/* Space between paragraphs */
details.note-box > .note-body > p {
  margin: 0 0 14px;
  padding: 0;
}

/* Larger breaks before main sections */
details.note-box > .note-body > p.note-heading {
  margin: 28px 0 8px;
  font-size: 14px;
  line-height: 1.5;
  letter-spacing: .025em;
  color: #E2E8F0;
}

/* Smaller breaks before subsections */
details.note-box > .note-body > p.note-subheading {
  margin: 22px 0 8px;
  line-height: 1.5;
  color: #E2E8F0;
}

details.note-box > .note-body > .note-heading:first-child {
  margin-top: 0;
}

/* Consistent list spacing */
details.note-box > .note-body > ul {
  margin: 0 0 16px;
  padding: 0 0 0 22px;
}

details.note-box > .note-body li {
  margin: 0;
  padding: 0;
}

details.note-box > .note-body li + li {
  margin-top: 8px;
}

details.note-box > .note-body > :last-child {
  margin-bottom: 0;
}
</style>

<style id="article-heading-typography">
/* Article headings: retain semantic levels and generated anchors. */
article:has(style[id$="article-heading-typography"]) .rich-text > h3[id] {
  font-size: 28px;
  line-height: 1.25;
}
article:has(style[id$="article-heading-typography"]) .rich-text > h4[id] {
  font-size: 22px;
  line-height: 1.25;
}
@media (max-width: 767px) {
  article:has(style[id$="article-heading-typography"]) .rich-text > h3[id] {
    font-size: 24px;
  }
  article:has(style[id$="article-heading-typography"]) .rich-text > h4[id] {
    font-size: 20px;
    line-height: 1.3;
  }
}
</style>

## TL;DR

Real-time systems vary dramatically in the cost and efficiency of making incoming data query-ready.
**In this series, we trace the path from data arrival to current answers, showing how it shapes preparation cost, query cost, and query runtime.**

Here, we follow **ClickHouse Cloud** and Snowflake using their recommended real-time stacks while ingesting 113.2 billion quotes and running queries. **ClickHouse Cloud delivered 412× better end-to-end performance per dollar**.

**This is Part 2 of our CostBench series.**<br/>
Other parts: [Methodology](https://clickhouse.com/blog/costbench-real-time-performance-per-dollar) · [Databricks](https://clickhouse.com/blog/clickhouse-vs-databricks-real-time-performance-per-dollar) · [BigQuery](https://clickhouse.com/blog/clickhouse-vs-bigquery-real-time-performance-per-dollar) · [Redshift](https://clickhouse.com/blog/clickhouse-vs-redshift-serverless-real-time-performance-per-dollar)<br/>
Coming next: Why ClickHouse Cloud won

## From fresh data to fast answers

[Real-time analytics](https://clickhouse.com/blog/selecting-a-real-time-analytical-database) has to stay fast while fresh data keeps arriving. **[CostBench](https://clickhouse.com/blog/costbench-data-warehouse-cost-performance) tests this directly: queries run throughout continuous ingestion, and their answers must include newly arrived data.** Efficient preparation lowers the cost of keeping that data [query-ready](https://clickhouse.com/blog/costbench-real-time-performance-per-dollar#what-makes-data-query-ready) and leaves less work for queries, reducing their cost and runtime.

Columnar storage lets queries skip unused fields; ordering lets drill-downs skip unrelated quotes; current pre-aggregations let interactive aggregations combine prepared summaries. The animation below shows how this [fresh-data path ](https://clickhouse.com/blog/costbench-real-time-performance-per-dollar#real-time-systems-must-make-fresh-data-query-ready-as-it-arrives)changes the scanning and aggregation left for queries as preparation catches up or falls behind. 


<iframe src="/uploads/costbench_p02_01_fresh_data_path_query_work_balance_refresh_20261016_110e785d8f.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>

CostBench measures ① preparation cost, ② query cost, and ③ runtime, then combines them into one [end-to-end performance-per-dollar score](https://clickhouse.com/blog/costbench-real-time-performance-per-dollar#turning-runtime-and-cost-into-performance-per-dollar).

This article follows those three effects through the ClickHouse–Snowflake comparison, from preparing incoming data to serving current answers.

## The workload and the result

> ClickHouse Cloud delivered 412× better end-to-end performance per dollar than Snowflake in this continuous-ingestion workload.

Both systems used their recommended real-time stack and low-latency ingestion path. CostBench streamed 113.2 billion stock-market quotes at a target of **1 million rows per second**, using **matching sorting or clustering keys** and daily **pre-aggregations** by stock symbol. **Throughout ingestion**, **four interactive aggregate queries** ran every ten minutes; **two drill-down queries** over raw quotes ran every hour. Every query covered the history ingested so far, including newly arrived quotes. **Read-side compute was matched at approximately 16 CPUs**, with query-result caching disabled.  
 
CostBench calculates its **end-to-end performance-per-dollar score** using the formula shown in the diagram below: **Score = (① preparation cost + ② normalized query cost) × ③ accumulated query runtime.** 

<iframe src="/uploads/costbench_p02_02_costbench_end_to_end_score_refresh_20261016_37f38fca0f.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>

Preparation cost covers the complete fresh-data path. Normalized query cost prices measured query runtimes at the applicable read-side compute rates. Accumulated query runtime is the sum of query durations. **Lower scores are better.**


<details class="note-box">
  <summary>HOW DID WE MEASURE ① ② ③? (click to expand)</summary>
  <div class="note-body">
<iframe src="/uploads/costbench_p02_03_methodology_bounds_20261016_ec2beed103.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>
<iframe src="/uploads/costbench_p02_04_methodology_bounds_20261016_c624a15bda.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>
    <p class="note-heading"><strong>SHARED HARNESS</strong></p>
    <p>
      Both systems used the same source files, schema, row decoder, workload generator, AWS EC2 m6i.8xlarge client host, rate controller, scheduler, timing, and result recorder. Only the destination-specific delivery adapter changed.
      <a href="https://clickhouse.com/blog/costbench-real-time-performance-per-dollar#how-costbench-keeps-the-comparison-fair">Part 1 describes the shared methodology.</a>
    </p>
    <p class="note-heading"><strong>DATASET AND PREPARATION</strong></p>
    <p>
      The complete <a href="https://github.com/ClickHouse/CostBench/tree/main/full-path-realtime/quotes#dataset">NBBO dataset</a> contains 113.2 billion narrow, 12-column rows. Raw tables used <code>(sym, t)</code>, stock symbol and event timestamp; daily summaries used <code>(sym, day)</code>, stock symbol and day. The summaries maintained the counts, sums, and price minima and maxima used by the aggregate queries.
    </p>
    <p class="note-heading"><strong>CONTINUOUS WORKLOAD</strong></p>
    <p>
      The client paced fresh rows toward 1 million rows per second while four interactive aggregate queries ran every ten minutes and two drill-downs every hour. Every query covered the history ingested so far, including the newest quotes. This simulates fresh data being generated continuously and becoming query-ready as it arrives. It measures the cost and performance of sustaining that workload, rather than the speed of bulk-loading or backfilling an existing dataset.
    </p>
    <p class="note-heading"><strong>RECOMMENDED STACKS AND SIZING</strong></p>
    <p>
      Both systems used vendor-recommended low-latency components and native implementations of the same preparation requirements. ClickHouse Cloud used one 16-CPU, 64-GiB read node; Snowflake used a Small Interactive Warehouse and a Gen2 Small fallback, each treated as approximately 16 CPUs because Snowflake does not publish CPU counts. This sizing draws on
      <a href="https://select.dev/posts/snowflake-warehouse-sizing">SELECT.dev</a> and an
      <a href="https://medium.com/snowflake-engineering/deep-dive-inside-snowflakes-new-gen2-standard-warehouses-powered-by-aws-graviton3-6aacca73ae2d">independent Gen2 inspection</a>.
    </p>
    <p class="note-heading"><strong>① PREPARATION COST</strong></p>
    <p>
      The complete 113.2-billion-row ingest includes all metered ingestion and preparation work. ClickHouse’s cost includes both 2-CPU nodes in its smallest calibrated high-availability ingest service. Snowflake’s separately metered Snowpipe Streaming and serverless MV refresh are both included.
    </p>
    <p class="note-heading"><strong>② QUERY COST AND ③ RUNTIME</strong></p>
    <p>
      Query cost prices each accepted query’s full client-visible duration at the applicable read-side rate. Runtime sums those durations over the same active-ingestion workload, aligned by row count. These normalized query costs are separate from complete warehouse invoices. The query-comparison and pricing notes below give the accepted counts, rates, and exclusions.
    </p>
    <p class="note-heading"><strong>TARGET VERSUS OBSERVED RATE</strong></p>
    <p>
      The target implies about 28 hours for 100 billion rows. Observed throughput averaged approximately 0.86 million rows/s for ClickHouse and 0.90 million for Snowflake. They reached 100 billion rows after roughly 32 and 31 hours, and completed 113.2 billion rows after roughly 37 and 35 hours, respectively. Client pacing, parallel streams, and batching targeted an exact rate but could not guarantee one.
    </p>
    <p class="note-heading"><strong>REPORTING WINDOW</strong></p>
    <p>
      Per-query latency charts stop at 100 billion rows. Preparation costs cover the complete ingest; accumulated query runtime and cost use the full accepted active-ingestion observations aligned by row count. Displayed totals are rounded.
    </p>
  </div>
</details>

ClickHouse Cloud led in all three measured components:


**① Preparation cost:** $28.69 versus $79.42.<br/>
**② Normalized query cost:** about $0.05 versus $15.99.<br/>
**③ Accumulated query runtime:** 1.03 minutes versus 2.13 hours.

The charts below track how those differences accumulated while data arrived.


<iframe src="/uploads/costbench_p02_05_accumulated_cost_and_runtime_refresh_20261016_bb85dd644d.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>

The score breakdown below brings those components together: ClickHouse Cloud’s **3.32× lower combined preparation and normalized query cost** and **124× lower accumulated query runtime** multiply to produce its overall performance-per-dollar advantage.

<iframe src="/uploads/costbench_p02_06_final_ranking_score_refresh_20261016_a3a8e12b1b.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>

<details class="note-box">
  <summary>PRICING, FORMULAS, AND THE 412× SCORE (click to expand)</summary>
  <div class="note-body">
    <p class="note-heading"><strong>PRICING BASIS</strong></p>
    <p>The tested Enterprise pricing in AWS us-east was $3 per Snowflake credit and $0.3903 per ClickHouse Cloud compute unit (CU) per hour. Snowflake’s serverless charges use its metering history. Displayed values below are rounded; the <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/results/t2/charts/run14/full_path_cost_performance_clickhouse_vs_snowflake_wide_summary.json">accepted summary</a> uses full-precision inputs.</p>
    <p class="note-heading"><strong>① COMPLETE PREPARATION COST</strong></p>
    <p>This covers ingestion and preparation of all 113.2 billion rows.</p>
    <p><strong>ClickHouse Cloud:</strong> <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/clickhouse-cloud/costs/out_t2/ingest.json">2 CUs</a> × 36.75 hours × $0.3903/CU-hour = $28.68705. This includes both nodes of the HA ingest service.</p>
    <p><strong>Snowflake ingestion:</strong> <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/costs/out/t2/run14/snowpipe_streaming.json">Snowpipe Streaming processed 4.6762 TiB</a>, metered at 17.71783 credits under the <a href="https://www.snowflake.com/legal-files/CreditConsumptionTable.pdf">0.0037-credits-per-uncompressed-GB rate</a>. Multiplying the unrounded credits by $3 gives $53.15348. Ingest-time clustering is included; no ingest warehouse was used.</p>
    <p><strong>Snowflake refresh:</strong> <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/costs/out/t2/run14/mv_refresh.json">8.75553 metered credits</a> × $3/credit = $26.26659. Total preparation cost: $53.15348 + $26.26659 = $79.42007.</p>
    <p class="note-heading"><strong>② NORMALIZED QUERY COST</strong></p>
    <p>Each accepted query is priced as its full client-visible duration × the applicable per-second read-side rate.</p>
    <p><strong>ClickHouse Cloud:</strong> 8 CUs × $0.3903/CU-hour × (<a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/clickhouse-cloud/costs/out_t2/matched/snowflake_run14/dashboard.json">11.361s aggregate</a> + <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/clickhouse-cloud/costs/out_t2/matched/snowflake_run14/drilldown.json">50.466s drill-down</a>) ÷ 3,600 = $0.05362.</p>
    <p><strong>Snowflake Interactive-priced work:</strong> (<a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/costs/out/t2/run14/dashboard.json">946.509s aggregate</a> + <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/costs/out/t2/run14/drilldown.json">74.815s drill-down</a>) × 1.2 credits/hour × $3/credit ÷ 3,600 = $1.02132.</p>
    <p><strong>Snowflake fallback-priced work:</strong> <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/costs/out/t2/run14/dashboard.json">6,653.660s aggregate</a> × 2.7 credits/hour × $3/credit ÷ 3,600 = $14.97074. Total normalized query cost: $1.02132 + $14.97074 = $15.99207.</p>
    <p><strong>Fallback boundary:</strong> The elapsed-time proxy assigns jobs above five seconds to Gen2 Small for their full duration and all others to Interactive Small. It excludes standing fallback capacity, idle capacity, minimum billing, and an additional Interactive charge for fallback-priced jobs. The allocation is a cost model, not a reconstruction of which warehouse physically executed each job.</p>
    <p class="note-heading"><strong>③ ACCUMULATED QUERY RUNTIME</strong></p>
    <p><strong>ClickHouse Cloud:</strong> 11.361s aggregate + 50.466s drill-down = 61.827s.</p>
    <p><strong>Snowflake:</strong> 7,600.169s aggregate + 74.815s drill-down = 7,674.984s.</p>
    <p class="note-heading"><strong>COMPARISON BOUNDARY</strong></p>
    <p>① covers the complete ingest. ② and ③ use the accepted active-ingestion sets aligned by row count: 209 × 4 aggregate queries and 35 × 2 drill-downs per system. Later post-ingestion observations and database storage are excluded. Query cost is normalized to accepted query work; it is not the complete warehouse invoice.</p>
    <p class="note-heading"><strong>FINAL SCORE</strong></p>
    <p><strong>ClickHouse Cloud:</strong> ($28.68705 + $0.05362) × 61.827 = 1,776.9494.</p>
    <p><strong>Snowflake:</strong> ($79.42007 + $15.99207) × 7,674.984 = 732,286.6479.</p>
    <p><strong>Relative score:</strong> 732,286.6479 ÷ 1,776.9494 = 412.103× using the full-precision inputs.</p>
  </div>
</details>




---

## 412× better real-time performance per dollar

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

[Try ClickHouse Cloud](https://console.clickhouse.cloud/signUp?loc=blog-cta-2713-412-better-real-time-performance-per-dollar-try-clickhouse-cloud&utm_blogctaid=2713)

---

All benchmark code and results are available in the [CostBench repository](https://github.com/ClickHouse/CostBench/tree/main/full-path-realtime/quotes). The stock-quotes dataset requires a [separate data license](https://github.com/ClickHouse/CostBench/tree/main/full-path-realtime/quotes#data-source-and-licensing), so the data itself cannot be redistributed.


To explain the result, we will follow the same path as the data: preparation first, then query serving.

## How ClickHouse Cloud prepares incoming data

The benchmark used a dedicated ClickHouse Cloud ingest service with two 2-CPU nodes for high availability - the smallest HA configuration that sustained the target during calibration. The shared client sent rows through [asynchronous inserts](https://clickhouse.com/blog/asynchronous-data-inserts-in-clickhouse) built into each node.

As shown below, those same two nodes handled ① columnar storage, ② ordering raw data, and ③ ordered pre-aggregation, plus background merges, on four CPU cores in total.


<iframe src="/uploads/costbench_p02_07_clickhouse_fresh_data_path_refresh_20261016_22f49dae13.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>




**① Store in columns and ② order data:** When an ingest node flushes its asynchronous-insert buffer, it sorts the raw quotes by the [MergeTree](https://github.com/ClickHouse/CostBench/blob/6fb94fd628b100db69bc2bf9f8d6f149eec54601/full-path-realtime/quotes/clickhouse-cloud/create.sql#L7) table’s (sym, t) key - stock symbol and event timestamp - and writes an ordered [data part](https://clickhouse.com/docs/concepts/core-concepts/parts).

**③ Order and pre-aggregate data:** In parallel, the same node runs the [incremental MV](https://github.com/ClickHouse/CostBench/blob/6fb94fd628b100db69bc2bf9f8d6f149eec54601/full-path-realtime/quotes/clickhouse-cloud/create.sql#L53)’s query over the incoming data block in memory, computes aggregate states, and sorts the results by the [AggregatingMergeTree table](https://github.com/ClickHouse/CostBench/blob/6fb94fd628b100db69bc2bf9f8d6f149eec54601/full-path-realtime/quotes/clickhouse-cloud/create.sql#L53)’s (sym, day) key - stock symbol and day - before writing an ordered data part containing the pre-aggregated data.

[Background merges](https://clickhouse.com/docs/concepts/core-concepts/merges) consolidate parts in both tables.

> The result: raw data and pre-aggregations [advance together](https://clickhouse.com/docs/resources/support-center/knowledge-base/materialized-views/are-materialized-views-inserted-asynchronously) from the same flushed insert block, without a separate refresh cycle.

<details class="note-box"><summary>HOW MUCH WORK DID THE FOUR-CORE INGEST SERVICE HANDLE? (click to expand)</summary><div class="note-body"><p>The <a href="https://github.com/ClickHouse/CostBench/tree/main/full-path-realtime/quotes/clickhouse-cloud/results_t2/utilization/writer">writer-utilization results</a> show the two-node service sustaining ingestion alongside background maintenance. CPU usage remained around 3.1 of its 4 available cores, tracked memory stayed within capacity, <a href="https://clickhouse.com/docs/merges">background merges</a> processed several million rows per second, and the maximum active-part count per partition stayed around 100.</p><p>Both nodes were retained for high availability, so the preparation cost includes the complete two-node deployment.</p></div></details>


We now follow the same preparation tasks through Snowflake, where ingestion and materialized-view refresh run in separate services.


## How Snowflake prepares incoming data

The benchmark used [Snowpipe Streaming’s high-performance architecture](https://docs.snowflake.com/en/user-guide/snowpipe-streaming/data-load-snowpipe-streaming-overview), which places a [managed server-side ingestion buffer](https://www.snowflake.com/en/blog/engineering/next-gen-snowpipe-streaming-architecture/) between application writes and the Interactive Table.

As shown below, Snowpipe Streaming handles ① columnar storage and ② ordering raw data, while a separate serverless refresh service handles ③ ordered pre-aggregation.

<iframe src="/uploads/costbench_p02_08_snowflake_fresh_data_path_refresh_20261016_0965a6cb7a.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>


**① Store in columns and ② order data:** Snowpipe Streaming writes rows [directly](https://www.snowflake.com/en/developers/guides/interactive-tables-snowpipe-streaming-arcade-lab/) into an [Interactive Table](https://github.com/ClickHouse/CostBench/blob/254400222ce969083b67c61e51cab05a2d707df1/full-path-realtime/quotes/snowflake/t2/setup_streaming.sql#L27). With [CLUSTER_AT_INGEST_TIME=TRUE](https://docs.snowflake.com/en/user-guide/snowpipe-streaming/snowpipe-streaming-pipe-object#pre-clustering-data-during-ingestion), it pre-clusters incoming rows on the shared (sym, t) key before writing them into [micro-partitions](https://docs.snowflake.com/en/user-guide/tables-clustering-micropartitions#what-are-micro-partitions) - Snowflake’s columnar storage units.

**③ Order and pre-aggregate data:** A separate Snowflake-managed [serverless service](https://docs.snowflake.com/en/user-guide/views-materialized#materialized-views-cost) refreshes the [Interactive MV](https://github.com/ClickHouse/CostBench/blob/254400222ce969083b67c61e51cab05a2d707df1/full-path-realtime/quotes/snowflake/t2/setup_streaming.sql#L71) asynchronously from that table. We configured the view with a (sym, day) clustering key, matching the sorting key of ClickHouse’s pre-aggregation table. Snowflake controls when refreshes run and how much compute they use; users cannot configure the refresh schedule or size the refresh compute.

> The result: the separate refresh cycle can leave Snowflake’s pre-aggregations behind its raw data.

<details class="note-box"><summary>WHY THESE INGESTION PATHS, AND HOW WERE THEY BUFFERED? (click to expand)</summary><div class="note-body"><p class="note-heading"><strong>THE SAME INGESTION PROBLEM</strong></p><p>Applications produce small, frequent writes that must be combined into efficient storage writes. This benchmark measures direct application-to-table ingestion, using server-side buffering in both <a href="https://docs.snowflake.com/en/user-guide/snowpipe-streaming/data-load-snowpipe-streaming-overview">Snowpipe Streaming</a> and <a href="https://clickhouse.com/blog/asynchronous-data-inserts-in-clickhouse">ClickHouse asynchronous inserts</a>.</p><p class="note-heading"><strong>SNOWFLAKE’S MANAGED SERVICE</strong></p><p>Clients open channels against a <a href="https://docs.snowflake.com/en/user-guide/snowpipe-streaming/snowpipe-streaming-pipe-object">PIPE object</a>. The high-performance ingestion service handles buffering, validation, configured transformations, and optional pre-clustering before committing rows. Snowflake manages the ingestion compute and bills it separately by uncompressed volume.</p><p class="note-heading"><strong>CLICKHOUSE’S NATIVE PATH</strong></p><p>Ordinary INSERT requests with <a href="https://clickhouse.com/blog/asynchronous-data-inserts-in-clickhouse"><code>async_insert</code></a> enabled are buffered on each receiving node. An <a href="https://clickhouse.com/blog/clickhouse-release-24-02#adaptive-asynchronous-inserts">adaptive flush timeout</a> responds to incoming traffic. Ordering, pre-aggregation, and <a href="https://github.com/ClickHouse/CostBench/tree/main/docs/clickhouse-native-ingestion">insert-path transformations</a> run on those nodes; a dedicated ClickHouse Cloud ingest service can <a href="https://clickhouse.com/docs/products/cloud/features/infrastructure/warehouses">scale independently</a> from reads while sharing the same stored data.</p><p class="note-heading"><strong>SHARED PACING</strong></p><p>Both destinations used the same Parquet source, row decoder, client compute, rate controller, and eight-worker parallelism. Destination-specific batching adapted delivery to each API.</p><p class="note-heading"><strong>CLICKHOUSE BATCHES</strong></p><p>The client sent <a href="https://github.com/ClickHouse/CostBench/blob/254400222ce969083b67c61e51cab05a2d707df1/full-path-realtime/quotes/clickhouse-cloud/_commands.txt#L60">3,000-row batches</a> through asynchronous inserts. With the run’s <a href="https://clickhouse.com/blog/asynchronous-data-inserts-in-clickhouse">default buffering settings</a>, a node flushed at the first of three thresholds: 100 MiB buffered, an adaptive timeout between 50 milliseconds and 1 second, or 450 queued insert queries.</p><p class="note-heading"><strong>SNOWFLAKE BATCHES</strong></p><p>Each worker called <code>append_row</code> through its own channel. Earlier <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/results/t2/stream_preview.log">stream-preview evidence</a> for this setup shows the SDK choosing <a href="https://www.snowflake.com/en/blog/engineering/next-gen-snowpipe-streaming-architecture/">file mode</a> and grouping calls into NDJSON batches averaging approximately 61,500 rows. Across eight channels, that produced about 14.6 file uploads per second before further server-side buffering.</p><p class="note-heading"><strong>BATCH-SIZE BOUNDARY</strong></p><p>The 3,000-row and approximately 61,500-row figures describe client delivery batches. Both systems buffered them again server-side, so these figures do not specify final storage-write sizes.</p><p class="note-heading"><strong>RETRIES AND RECOVERY</strong></p><p>Snowpipe Streaming’s <a href="https://docs.snowflake.com/en/user-guide/snowpipe-streaming/snowpipe-streaming-channels">offset tokens</a> record committed channel progress. A custom application retrieves the last token, maps it to its source, and resumes from the next record; Snowflake’s Kafka connector handles that mapping for Kafka partitions. ClickHouse supports <a href="https://clickhouse.com/docs/concepts/features/operations/insert/deduplicating-inserts-on-retries">insert deduplication</a> for retries, including <a href="https://clickhouse.com/blog/clickhouse-release-26-01#deduplication-of-asynchronous-inserts-with-materialized-views">dependent materialized views</a>.</p><p class="note-heading"><strong>WHERE <a href="https://clickhouse.com/docs/integrations/clickpipes/home">CLICKPIPES</a> FITS</strong></p><p>Direct application writes are compared here. For Kafka, the corresponding integrations are Snowflake’s Kafka connector and Kafka ClickPipes; for object-storage files, <a href="https://docs.snowflake.com/en/user-guide/snowpipe-streaming/data-load-snowpipe-streaming-overview#snowpipe-streaming-versus-snowpipe">classic Snowpipe</a> and Object Storage ClickPipes. Those paths include an external-source integration beyond the direct-write boundary measured here.</p><p class="note-heading"><strong>STORAGE CONTEXT</strong></p><p>In the <a href="https://clickhouse.com/blog/real-time-analytics-cost-performance-snowflake-vs-clickhouse">earlier configuration</a>, at roughly 113 billion rows, Snowflake’s raw Interactive Table occupied 2.9 TiB, versus 701 GiB for its standard table and 362 GiB for ClickHouse’s raw table, roughly four and eight times as much. These are earlier-study footprints, and database storage costs are excluded from this score.</p></div></details>

## What this means for freshness and preparation cost


### Pre-aggregation lag

ClickHouse updates summaries during inserts; Snowflake refreshes them asynchronously. The chart below shows the resulting raw-to-summary lag: zero for ClickHouse in this setup; Snowflake’s plotted lag averages about 1.4 minutes and reaches about 2 minutes.

<iframe src="/uploads/costbench_p02_09_pre_aggregation_lag_refresh_20261016_0406b78861.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>

This measures summary freshness: Snowflake still returns current answers by including newer rows at query time. 

<details class="note-box">
  <summary>HOW WAS PRE-AGGREGATION LAG MEASURED? (click to expand)</summary>
  <div class="note-body">
    <p class="note-heading"><strong>SNOWFLAKE SERIES</strong></p>
    <p>The <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/results/t2/charts/run14/mv_lag_clickhouse_vs_snowflake_wide_summary.json">MV-lag series</a> uses 2,072 active one-minute <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/results/t2/mv_latency_20260811T111856Z.jsonl"><code>behind_by</code> samples</a>. Poll timestamps were aligned to raw row count by linear interpolation between 208 active dashboard observations.</p>
    <p class="note-heading"><strong>SMOOTHING AND READOUTS</strong></p>
    <p>The reported average and maximum come from a centered 61-sample rolling mean: 1.372 and 2.033 minutes, rounded to 1.4 and 2.0 minutes. The displayed trend adds a centered 11-sample rolling mean and shape-preserving interpolation. Its interactive readout follows that displayed trend at the selected row count.</p>
    <p class="note-heading"><strong>CLICKHOUSE BASELINE</strong></p>
    <p>The zero line is a benchmark-semantic baseline rather than a separately polled provider metric. The raw table and <a href="https://clickhouse.com/docs/resources/support-center/knowledge-base/materialized-views/are-materialized-views-inserted-asynchronously">incremental MV</a> consume the same flushed insert block, with no independent refresh interval between them.</p>
  </div>
</details>

> ClickHouse kept pre-aggregations current during ingestion; Snowflake’s refresh lag shifted catch-up work onto queries.

We examine that extra work in the serving comparison below; first, the next chart accounts for preparation cost.


### Cost of keeping incoming data query-ready

ClickHouse handles ① columnar storage, ② ordering raw data, and ③ ordered pre-aggregation natively within its engine. Snowflake splits the same work across separately charged services. The chart below compares their accumulated preparation costs.

<iframe src="/uploads/costbench_p02_10_fresh_data_path_cost_refresh_20261016_51036f598b.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>

ClickHouse’s complete ingest service cost $28.69. Snowflake’s preparation totaled $79.42: $53.15 for Snowpipe Streaming’s columnar storage and ordering (① ②), plus $26.27 for ordered pre-aggregation through materialized-view refresh (③), about 2.8× as much. 
 
> ClickHouse handled columnar storage, ordering, and pre-aggregation in one ingest service; Snowflake’s separately charged services cost 2.8× as much. 
 
Now we follow the prepared raw data and summaries into query serving.


##  How ClickHouse Cloud serves queries

Ordered raw data lets drill-downs skip unrelated rows; pre-aggregations let interactive aggregations combine prepared results instead of recalculating them from individual quotes. The diagram below shows both paths through ClickHouse Cloud’s read service: one node with 16 CPUs and 64 GiB of memory, matching the estimated CPU count of Snowflake’s Small warehouse.

<iframe src="/uploads/costbench_p02_11_clickhouse_query_serving_refresh_20261016_78768bf2b6.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>

**Drill-downs:** Queries prune ordered raw data in the [event-level MergeTree table](https://github.com/ClickHouse/CostBench/blob/6fb94fd628b100db69bc2bf9f8d6f149eec54601/full-path-realtime/quotes/clickhouse-cloud/create.sql#L7). Filtering by symbol, the leading column of its (sym, t) sorting key, lets the read service skip unrelated quotes.

**Interactive aggregations:** Queries read [current pre-aggregations](https://clickhouse.com/docs/resources/support-center/knowledge-base/materialized-views/are-materialized-views-inserted-asynchronously) from the [AggregatingMergeTree table](https://github.com/ClickHouse/CostBench/blob/6fb94fd628b100db69bc2bf9f8d6f149eec54601/full-path-realtime/quotes/clickhouse-cloud/create.sql#L53), combining prepared counts, sums, minima, and maxima from a much smaller set of daily summary rows. No refresh catch-up work is needed. 
 
> The result: drill-downs prune ordered raw data; interactive aggregations read summaries kept current during ingestion.

Snowflake uses the same two query paths, with additional work when its summaries lag.


## How Snowflake serves queries

The diagram below shows Snowflake’s Small [Interactive Warehouse](https://docs.snowflake.com/en/user-guide/interactive) serving both workloads, with an estimated 16 CPUs and [approximately 600 GB](https://docs.snowflake.com/en/user-guide/interactive#choosing-a-size-for-an-interactive-warehouse) of [local SSD](https://medium.com/snowflake/cost-effective-performance-with-snowflake-interactive-warehouses-3892ede05f4c) cache.

<iframe src="/uploads/costbench_p02_12_snowflake_query_serving_refresh_20261016_a8c16e2132.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>

**Drill-downs:** Queries prune ordered raw data in the [Interactive Table](https://github.com/ClickHouse/CostBench/blob/254400222ce969083b67c61e51cab05a2d707df1/full-path-realtime/quotes/snowflake/t2/setup_streaming.sql#L27) directly. Its (sym, t) clustering, applied during ingestion, lets them skip unrelated quotes when filtering by symbol.

**Interactive aggregations:** Queries target the [Interactive MV](https://github.com/ClickHouse/CostBench/blob/254400222ce969083b67c61e51cab05a2d707df1/full-path-realtime/quotes/snowflake/t2/setup_streaming.sql#L71) and catch up on read by combining its summaries with rows added to the [Interactive Table](https://github.com/ClickHouse/CostBench/blob/254400222ce969083b67c61e51cab05a2d707df1/full-path-realtime/quotes/snowflake/t2/setup_streaming.sql#L27) since the last refresh. The answer therefore includes current data.

**Timeout and fallback:** Interactive Warehouses [cancel queries that exceed five seconds](https://docs.snowflake.com/en/user-guide/interactive#automatically-handling-statement-timeouts); the limit can only be lowered. We configured a separate Gen2 Small standard warehouse, also estimated at 16 CPUs, to receive eligible timed-out queries through Snowflake’s [automatic fallback mechanism](https://docs.snowflake.com/en/user-guide/interactive#automatically-handling-statement-timeouts).

<details class="note-box">
  <summary>HOW ARE SNOWFLAKE’S CATCH-UP AND FALLBACK PATHS INTERPRETED? (click to expand)</summary>
  <div class="note-body">
    <p class="note-heading"><strong>CURRENT-ANSWER CONTRACT</strong></p>
    <p>Queries against an Interactive MV include the rows added to the base table since its last refresh. The amount of newer data grows between refreshes and shrinks when a refresh catches up, explaining the rising-and-reset pattern in the aggregate curves.</p>
    <p class="note-heading"><strong>FALLBACK CONFIGURATION</strong></p>
    <p>The tested Interactive Warehouse used a five-second limit and a separate Gen2 Small fallback warehouse for eligible queries. The benchmark’s normalized-cost model uses elapsed time as a pricing proxy; it does not establish which warehouse physically executed each query.</p>
    <p class="note-heading"><strong>ATTRIBUTION RULE</strong></p>
    <p>Each accepted query with a full client-visible duration strictly greater than five seconds is priced for its entire duration at the Gen2 Small rate. Queries at or below five seconds use the Interactive Small rate. Exactly five seconds remains Interactive-priced. Each query belongs to one bucket, without an additional Interactive charge for fallback-priced work.</p>
    <p class="note-heading"><strong>OBSERVED ALLOCATION</strong></p>
    <p>Of 836 aggregate executions, <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/costs/out/t2/run14/dashboard.json">477 (57.1%)</a> were above five seconds; <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/costs/out/t2/run14/drilldown.json">all 70 drill-downs</a> were at or below it. A query at or below the threshold could still have used fallback in reality, so the proxy can undercount fallback.</p>
    <p class="note-heading"><strong>COST EXCLUSIONS</strong></p>
    <p>Standing fallback capacity, minimum billing, idle capacity, and an additional primary-warehouse charge for fallback-priced queries are excluded. Full-duration timing is retained; no query phase is removed from latency or normalized cost. The pricing note below provides the rates and calculation.</p>
  </div>
</details>

> The result: drill-downs prune ordered raw data; interactive aggregations catch up on read when summaries lag.


## What this means for query performance and cost


### Drill-downs over ordered raw data

Both systems order raw data as it arrives, so drill-downs can prune unrelated data. [D1 and D2](https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/clickhouse-cloud/queries_raw.sql) query one stock’s growing history directly: hourly price summaries and a risk-and-liquidity profile. The chart below follows this raw-data path, which bypasses the materialized views.

<iframe src="/uploads/costbench_p02_13_drill_down_query_latency_refresh_20261016_bddd99e2fa.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>

ClickHouse Cloud is yellow; Snowflake is blue.

<details class="note-box">
  <summary>HOW WERE QUERY LATENCIES AND ACCUMULATED RESULTS COMPARED? (click to expand)</summary>
  <div class="note-body">
    <p class="note-heading"><strong>QUERY DEFINITIONS</strong></p>
    <p><a href="https://clickhouse.com/blog/costbench-real-time-performance-per-dollar">Part 1</a> <a href="https://clickhouse.com/blog/costbench-real-time-performance-per-dollar#how-costbench-keeps-the-comparison-fair">explains</a> the four aggregate queries A1–A4 and two drill-downs D1–D2, including how newly ingested quotes contribute to their answers. Both systems used the same query semantics and schedule.</p>
    <p class="note-heading"><strong>CACHE POLICY</strong></p>
    <p>Query-result caching was disabled, so every measured query ran. Underlying data caches were allowed to remain warm. Both systems used the read-side sizing described in the methodology note.</p>
    <p class="note-heading"><strong>TIMING</strong></p>
    <p>Latency and accumulated runtime use each query’s full client-visible duration. Per-query median, P99, and maximum statistics summarize unsmoothed observations through 100 billion rows.</p>
    <p class="note-heading"><strong>LATENCY CHARTS</strong></p>
    <p>Each system is plotted against its own observed row counts, rather than equal iteration numbers or interpolation between systems. Aggregate trends use a centered seven-observation rolling median; <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/results/t2/charts/run14/drilldown_query_latency_clickhouse_vs_snowflake_wide_summary.json">drill-down trends</a> use a centered five-observation rolling median. The <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/results/t2/charts/run14/aggregate_query_latency_clickhouse_vs_snowflake_wide_summary.json">aggregate chart</a> retains its Snowflake-only Tukey upper-fence rule, with excluded points retained in the source and summary. Drill-downs have no outlier filter.</p>
    <p class="note-heading"><strong>INTERACTIVE READOUTS</strong></p>
    <p>Values beneath the charts follow the displayed trends at the selected row count. Logarithmic scales keep milliseconds and seconds readable together; Linear shows absolute differences. Each chart has independent playback and row-position controls.</p>
    <p class="note-heading"><strong>ACCEPTED ACCUMULATED WORKLOAD</strong></p>
    <p>Runtime and normalized query cost use <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/results/t2/charts/run14/full_path_cost_performance_clickhouse_vs_snowflake_wide_summary.json">209 four-query aggregate batches and 35 two-query drill-down batches</a> per system: 836 + 70 = 906 executions. Observations are aligned by ingestion progress. The first complete-dataset observation belongs to active ingestion; later post-ingestion observations are excluded.</p>
    <p class="note-heading"><strong>ACCUMULATED CHART</strong></p>
    <p>Recorded durations are added at their aligned row positions, without smoothing or interpolation between observations. The total is the sum of query durations, not the elapsed ingestion time. Its aggregate component uses maintained pre-aggregations; the alternative raw-table test described later is outside the headline score.</p>
  </div>
</details>


ClickHouse Cloud:



* 722.5 ms median - 1.38× faster than Snowflake
* 1.15 s P99 - 2.06× faster
* 1.18 s maximum - 2.17× faster

Accumulated runtime was 50.47 seconds. Latency increased more gradually and remained lower at larger data volumes.

**Snowflake:**


* 993.5 ms median
* 2.37 s P99
* 2.56 s maximum

Accumulated runtime was 74.81 seconds. Latency rose more steeply as data accumulated; [all 70 executions remained below the five-second fallback threshold](https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/costs/out/t2/run14/drilldown.json).

<details class="note-box">
  <summary>WHAT DOES SNOWFLAKE’S SERVING CACHE CHANGE? (click to expand)</summary>
  <div class="note-body">
    <p class="note-heading"><strong>EARLIER DIAGNOSTIC EVIDENCE</strong></p>
    <p>Snowflake documents <a href="https://docs.snowflake.com/en/user-guide/interactive#adding-an-interactive-table-to-an-interactive-warehouse">cache warming</a> as a non-blocking background process. <a href="https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/results/t2/query_stats_drilldowns.csv">Earlier query profiles</a> for this workload reported zero or negligible remote reads during active ingestion and a median 100% of bytes served from cache. At roughly 75 MB/s of ingest, the queried working set fit within Small’s approximately <a href="https://docs.snowflake.com/en/user-guide/interactive#choosing-a-size-for-an-interactive-warehouse">600 GB cache</a> and warming kept pace. These diagnostics describe the earlier configuration, rather than separately measuring cache hits for this post’s headline run.</p>
    <p class="note-heading"><strong>WHAT THAT SHOWS</strong></p>
    <p>The earlier diagnostic represented a favorable cache case. Having the relevant data local still leaves the work of reading and filtering the growing raw table.</p>
    <p class="note-heading"><strong>WORKING-SET CAPACITY</strong></p>
    <p>Even the largest Interactive Warehouse, 4X-Large, has a finite documented <a href="https://docs.snowflake.com/en/user-guide/interactive#choosing-a-size-for-an-interactive-warehouse">44 TB cache</a>. A working set that exceeds cache capacity requires more remote reads. Warehouse sizing therefore affects both cache capacity and cost.</p>
    <p class="note-heading"><strong>WARMING THROUGHPUT</strong></p>
    <p>Snowflake documents roughly <a href="https://docs.snowflake.com/en/user-guide/interactive#resuming-and-suspending-an-interactive-warehouse">300–400 MB/s</a> of warming throughput even for X-Small, with larger warehouses warming faster. That exceeds the earlier workload’s roughly 75 MB/s ingest rate. At higher source rates, recently arrived data may require remote reads even when the total working set still fits.</p>
    <p class="note-heading"><strong>SIZING IMPLICATION</strong></p>
    <p>The Interactive Warehouse must be sized for both the data queries touch and the rate at which new data arrives. ClickHouse’s query path reads the ordered MergeTree table without a separately populated Interactive Warehouse serving cache.</p>
  </div>
</details>

> Across the drill-down workload, ClickHouse Cloud was 1.48× faster overall.




### Interactive aggregations over ordered pre-aggregated data

Daily pre-aggregations let [A1–A4](https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/clickhouse-cloud/queries_mv.sql) combine prepared counts, sums, and price minima and maxima. A1–A2 filter the ordered summaries by symbol; A3–A4 read across all symbols. ClickHouse reads current summaries; Snowflake must also include newer rows. The chart below shows the resulting latency gap as data accumulates.

<iframe src="/uploads/costbench_p02_14_aggregate_query_latency_refresh_20261016_76e5ec3151.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>

**ClickHouse Cloud:**



* 13 ms median - 801× faster than Snowflake
* 42 ms P99 - 533× faster
* 111 ms maximum -  218× faster

Accumulated runtime was 11.36 seconds.

**Snowflake:**



* 10.41 s median
* 22.20 s P99
* 24.17 s maximum

Snowflake’s sawtooth curves rise as rows accumulate between refreshes and fall when refreshes catch up. Accumulated runtime was 2.11 hours. Under the benchmark’s five-second fallback proxy, [477 of 836 executions (57%)](https://github.com/ClickHouse/CostBench/blob/main/full-path-realtime/quotes/snowflake/costs/out/t2/run14/dashboard.json) were priced at the fallback rate.

> Across the interactive-aggregation workload, ClickHouse Cloud was 669× faster overall.


### Accumulated query cost and runtime

Those per-query differences accumulate: interactive aggregations account for most of the gap. The charts below combine both workloads and price their measured runtimes using the benchmark’s read-side cost model.

<iframe src="/uploads/costbench_p02_15_query_runtime_and_cost_refresh_20261016_084df5b39f.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>




ClickHouse Cloud accumulated 1.03 minutes of query runtime and Snowflake 2.13 hours. Their normalized query-serving costs were about $0.05 and $15.99, respectively. These are normalized costs for the query work, not complete warehouse invoices.



> Across the query workload, ClickHouse Cloud was 124× faster overall, with 298× lower normalized query cost.


## From the field: Appcues

Appcues’ migration from Snowflake to ClickHouse Cloud shows the same full-path efficiency in production: faster queries, fresher data, and lower analytics spend for its customer-facing dashboards. “Page load time has to be practically instant,” says Chris Brookins, VP of Engineering. Its Snowflake-and-Airflow stack rolled up data every five to ten minutes, delaying fresh results and adding infrastructure to maintain. Computing aggregates during inserts let the team retire its Airflow infrastructure.

P95 query latency fell from over 20 seconds to **under 2 seconds**, and newly arrived events became queryable in **about 5 seconds**, down from ten minutes or more. Analytics spend fell **23%**, even as Appcues added workloads and features. [Read Appcues’ migration story](https://clickhouse.com/blog/appcues-customer-engagement-analytics).



## Snowflake can’t match ClickHouse Cloud for real-time analytics

While fresh data kept arriving, ClickHouse Cloud outperformed Snowflake across the complete workload:



**① Preparation cost: 2.8× lower** for ingestion, ordering, and pre-aggregation.<br/>
**② Normalized query cost: 298× lower** for the matched queries.<br/>
**③ Accumulated query runtime: 124× lower** across both query workloads.

The final chart brings these results together, plotting combined preparation and normalized query cost against accumulated query runtime as fresh data arrives.

<iframe src="/uploads/costbench_p02_16_end_to_end_cost_vs_accumulated_query_clickhouse_snowflake_refresh_20261016_31b6818cee.html" frameborder="0" style="width: 100%; height: 500px; max-height: 800px;"></iframe>


Up is lower total cost; right is lower accumulated query runtime.

> ClickHouse Cloud delivered 412× better end-to-end performance per dollar while ingesting fresh data and serving current answers.

The biggest serving advantage came from interactive aggregations. ClickHouse read current summaries; Snowflake had to combine its summaries with newer rows to return current answers. Preparing that work during ingestion paid off in both query speed and cost.

That is why Snowflake can’t match ClickHouse Cloud for real-time analytics.





