34 ms against a 3-billion-row table.
On a single ClickHouse node
Managed service for ClickHouse® on Nirvana × Altinity. Single-node ClickHouse 25.8.16, managed by Altinity, running on Nirvana high-performance cloud. Star Schema Benchmark at SF=100 and SF=500, full per-query latency from three executions per query: run 1 approximately cold, runs 2-3 warm.
Single-node · ClickHouse 25.8.16 · SSB SF=100 and SF=500
Selective query against a 3-billion-row table
SF=500 · Q1.3 warm average
Sustained scan throughput from disk
SF=500 · full-scan family · sustained
Largest dataset tested · lineorder_flat
SF=500 · single-node
Why we ran it at two sizes.
The Star Schema Benchmark (SSB) is a benchmark for OLAP databases that stresses scans, filters, aggregations, and joins on a star schema. Most published results, ours included, run against lineorder_flat because it removes join overhead and isolates storage + query-execution behavior. Running it at two scale factors separates cached / CPU-bound performance from storage-bound performance. Both matter, but only one looks like a real production workload.
600M rows · fits in page cache · CPU-bound
At SF=100 the working set fits comfortably in the OS page cache after the cold run, so most reads are satisfied from RAM. Warm averages here approach the CPU-bound limit — how fast ClickHouse can parse, decompress, and aggregate data already in memory. Useful as an upper bound; less useful as a production prediction.
3B rows · exceeds page cache · I/O matters
At SF=500 the working set exceeds RAM. Every warm run has to fetch cold blocks from disk, so latency rises and the ceiling becomes storage bandwidth rather than CPU. This is the production-realistic baseline: the number to quote when sizing a real cluster.
What we tested on.
Everything below was measured on a single-node ClickHouse 25.8.16 cluster managed end-to-end by Altinity, running on Nirvana high-performance cloud.
Cluster under test
- Cluster
- nirvanalabs-o5nod.altinity.cloud
- Engine
- ClickHouse 25.8.16
- Storage policy
- Two-volume default; JBOD biases writes to the emptier volume
- Table
- lineorder_flat · PARTITION BY toYear(LO_ORDERDATE)
- Operator
- Altinity.Cloud — fully managed
- Substrate
- Nirvana Labs purpose-built cloud
- Workload
- SSB SF=100 + SF=500 · 13 queries · 3 runs each · single session
How much data we loaded.
SSB is a denormalized star schema derived from TPC-H. We load all five base tables, then build lineorder_flat — the canonical wide table the SSB queries hit — at both scale factors.
| Table | Role | SF = 100 | SF = 500 |
|---|---|---|---|
| lineorder | Fact | 600,037,902 | 2,999,989,709 |
| lineorder_flat | Wide fact (canonical SSB table) | 600,037,902 | 2,999,989,709 |
| customer | Dimension | 3,000,000 | 15,000,000 |
| supplier | Dimension | 200,000 | 1,000,000 |
| part | Dimension | 1,400,000 | 1,800,000 |
| date | Dimension | 2,556 | 2,556 |
- Role
- Fact
- SF = 100
- 600,037,902
- SF = 500
- 2,999,989,709
- Role
- Wide fact (canonical SSB table)
- SF = 100
- 600,037,902
- SF = 500
- 2,999,989,709
- Role
- Dimension
- SF = 100
- 3,000,000
- SF = 500
- 15,000,000
- Role
- Dimension
- SF = 100
- 200,000
- SF = 500
- 1,000,000
- Role
- Dimension
- SF = 100
- 1,400,000
- SF = 500
- 1,800,000
- Role
- Dimension
- SF = 100
- 2,556
- SF = 500
- 2,556
lineorder_flat is the denormalized join of lineorder with all four dimensions. The 13 SSB queries operate exclusively on this table, which is why all query timings below are scaled by its row count.
How we ran it.
Three blocks below — build, load, query — reproduced from the run log as executed. Where we would do something differently next time, it is called out as a recommendation in the remarks below rather than folded in here.
We use the canonical vadimtk/ssb-dbgen generator. Build it on the same host that will load data — keeps the I/O local and avoids large network copies of the raw .tbl files.
Generate each table at the chosen scale factor and stream it into ClickHouse. Dim tables load immediately and free their .tbl; the fact table is split into parallel chunks at SF=100 and single-streamed at SF=500.
SSB queries hit the wide denormalized lineorder_flat. Build it once per scale factor, then run each of the 13 queries three times back-to-back: run 1 is approximately cold, runs 2-3 are warm.
Selective queries stay under 100 ms. Full scans take 24–44 seconds.
Toggle between SF=100 and SF=500. Filter by query class to isolate selective lookups, partition-bounded aggregates, and full scans. Hover any bar for the full per-run breakdown. SF=100 latencies are in ms; SF=500 in seconds.
| Query | Class | Warm avg | Run 1 (~cold) | Warm runs | Rows scanned |
|---|---|---|---|---|---|
| Q1.1Year filter; sum revenue × discount. | Partition-bounded | 1.38 s | 4.48 s | 1.34 / 1.41 s | 91M |
| Q1.2Year + month + discount band; tight aggregate. | Partition-bounded | 0.11 s | 0.41 s | 0.11 / 0.11 s | 7.8M |
| Q1.3Year + week + quantity band; narrow slice of fact table. | Highly selective | 0.034 s | 0.16 s | 0.03 / 0.04 s | 1.8M |
| Q2.1Revenue by year × brand for one part category. | Full scan | 31.05 s | 31.84 s | 32.37 / 29.72 s | 600M |
| Q2.2Revenue by year × brand for one brand range. | Full scan | 28.13 s | 29.35 s | 26.53 / 29.73 s | 600M |
| Q2.3Revenue by year × brand for one brand. | Full scan | 27.12 s | 26.4 s | 28.64 / 25.6 s | 600M |
| Q3.1Revenue by region × year; reads ~91% of the fact table. | Full scan | 31.02 s | 31.41 s | 29.96 / 32.09 s | 547M |
| Q3.2Revenue by nation × year (one region); ~91% of rows read. | Full scan | 24.71 s | 24.07 s | 24.85 / 24.58 s | 547M |
| Q3.3Revenue by city × year (one nation); ~91% of rows read. | Full scan | 24.33 s | 22.08 s | 25.57 / 23.09 s | 547M |
| Q3.4Year + month + city + city; primary-index hit. | Index hit | 0.063 s | 0.055 s | 0.064 / 0.062 s | 7.7M |
| Q4.1Profit by year × nation for one region (largest scan). | Full scan | 43.76 s | 44.79 s | 45.08 / 42.45 s | 600M |
| Q4.2Profit by year × nation, 2-year window. | Partition-bounded | 5.29 s | 8.72 s | 6.73 / 3.85 s | 144M |
| Q4.3Profit by year × city, 2-year, one nation. | Partition-bounded | 3.45 s | 4.24 s | 3.91 / 2.98 s | 144M |
- Class
- Partition-bounded
- Warm avg
- 1.38 s
- Run 1 (~cold)
- 4.48 s
- Warm runs
- 1.34 / 1.41 s
- Rows scanned
- 91M
- Class
- Partition-bounded
- Warm avg
- 0.11 s
- Run 1 (~cold)
- 0.41 s
- Warm runs
- 0.11 / 0.11 s
- Rows scanned
- 7.8M
- Class
- Highly selective
- Warm avg
- 0.034 s
- Run 1 (~cold)
- 0.16 s
- Warm runs
- 0.03 / 0.04 s
- Rows scanned
- 1.8M
- Class
- Full scan
- Warm avg
- 31.05 s
- Run 1 (~cold)
- 31.84 s
- Warm runs
- 32.37 / 29.72 s
- Rows scanned
- 600M
- Class
- Full scan
- Warm avg
- 28.13 s
- Run 1 (~cold)
- 29.35 s
- Warm runs
- 26.53 / 29.73 s
- Rows scanned
- 600M
- Class
- Full scan
- Warm avg
- 27.12 s
- Run 1 (~cold)
- 26.4 s
- Warm runs
- 28.64 / 25.6 s
- Rows scanned
- 600M
- Class
- Full scan
- Warm avg
- 31.02 s
- Run 1 (~cold)
- 31.41 s
- Warm runs
- 29.96 / 32.09 s
- Rows scanned
- 547M
- Class
- Full scan
- Warm avg
- 24.71 s
- Run 1 (~cold)
- 24.07 s
- Warm runs
- 24.85 / 24.58 s
- Rows scanned
- 547M
- Class
- Full scan
- Warm avg
- 24.33 s
- Run 1 (~cold)
- 22.08 s
- Warm runs
- 25.57 / 23.09 s
- Rows scanned
- 547M
- Class
- Index hit
- Warm avg
- 0.063 s
- Run 1 (~cold)
- 0.055 s
- Warm runs
- 0.064 / 0.062 s
- Rows scanned
- 7.7M
- Class
- Full scan
- Warm avg
- 43.76 s
- Run 1 (~cold)
- 44.79 s
- Warm runs
- 45.08 / 42.45 s
- Rows scanned
- 600M
- Class
- Partition-bounded
- Warm avg
- 5.29 s
- Run 1 (~cold)
- 8.72 s
- Warm runs
- 6.73 / 3.85 s
- Rows scanned
- 144M
- Class
- Partition-bounded
- Warm avg
- 3.45 s
- Run 1 (~cold)
- 4.24 s
- Warm runs
- 3.91 / 2.98 s
- Rows scanned
- 144M
SF=500 (disk-bound, 3B rows), values in s · 3 executions per query (run 1 ≈cold, runs 2-3 warm) · 13 of 13 queries.
Selective queries stayed interactive as the dataset grew 5×.
Five times the data, and selective queries still answer in under 100 ms (34 ms and 63 ms warm): latency grew 2.8–3.9×, well behind the data. Full scans move from cache speed to disk speed over the same jump, which is where the 13–35× figure comes from: at 600M rows the table fits in page cache, at 3B rows it doesn’t. That’s a change of substrate, not a loss of efficiency, and it settles into a predictable ~70M rows/sec.
Sustained SF=500 scan throughput from disk
Selective-query latency growth · 5× more data
Q1.3 warm latency against a 3B-row table · SF=500
Full-scan latency growth at SF=500
Each line is one of the 13 queries. The selective query and the index hit (Q1.3, Q3.4) stay near the bottom across both scale factors. The full scans trend upward at SF=500 as the dataset moves past page-cache size and warm runs pull cold blocks from disk.
Five things we learned.
Selective queries are the strong signal.
Q3.4 lands at 63 ms warm against a 3-billion-row table because a tight (year, month, city, city) filter rides partition pruning (toYYYYMM = 199712) and sort-key skipping (LO_ORDERDATE first in the order key). It reads 7.7M rows, not 3B. Q1.3 sits at 34 ms on 1.8M rows read, for the same reason. This is what well-aligned analytical queries look like at this hardware class.
Memory never became a constraint.
At SF=100, peak memory across all 13 queries was ~16 MB: ClickHouse aggregates in place over streamed data, and nothing tripped the external group-by or external sort thresholds. SF=500 memory was not captured. The work is read-bound rather than memory-bound, so similar behavior is expected — but that is an expectation, not a measurement.
Full-scan throughput is solid and predictable.
At SF=500, Q4.1 reads 3B rows in 43.76 s warm: roughly 70M rows/sec sustained from disk. The same query at SF=100 read 600M rows in 2.80 s, about 215M rows/sec from cache. That ~3× gap between cached and disk-bound is the number to plan capacity against.
Performance is stable run to run.
Repeat runs land on the same number: Q3.2 at 24.85 s then 24.58 s, Q4.1 at 45.08 s then 42.45 s. The cold-to-warm ratio narrows from 2–5× at SF=100 to 1.0–1.1× at SF=500: past cache, first and second runs both read from disk, so there is no cache bonus to chase. Q4.2 (8.72 → 6.73 → 3.85 s) is the one outlier — I/O variance, not query variance.
Size for the working set, not the cache.
Full scans at SF=500 run at disk speed rather than cache speed, which is why their latency grows 13–35× while the dataset grows 5×. Q3.1–Q3.3 are counted in this family: they read roughly 91% of the fact table, so they behave like scans rather than bounded aggregates. SF=500 is the production-realistic baseline; SF=100 measures CPU and decompression in isolation.
Fast selective queries. Predictable storage-bound scans.
Selective queries — the shape most production dashboards actually run — land at 34–63 ms against a 3-billion-row table. That’s the headline number, and it’s the one to quote. We are not publishing a comparison against other clouds here: a parity claim needs the same dataset and the same queries measured on both sides, and we have only run one side.
warm selective queries against a 3-billion-row table, and ~70M rows/sec sustained on storage-bound full scans
Single-node ClickHouse 25.8.16, Altinity-managed, Nirvana compute · SSB SF=500.
On comparisons. Published single-node ClickHouse numbers vary widely with CPU, RAM, storage class and settings, and we did not run any of them ourselves, so quoting a range from memory would not be evidence of anything. If you want a comparison, the useful one is your own workload on both substrates: bring your dataset and queries and we’ll measure them side by side.
Remarks for anyone reproducing this.
What went wrong, what we would do differently, and the details that decide whether your run matches ours.
Why these numbers look like this.
The performance above isn’t accidental. Nirvana is purpose-built for analytical workloads like ClickHouse. The substrate matters, especially at SF=500 where storage bandwidth becomes the ceiling.
Compute and storage, one private-network hop apart.
Your ClickHouse, workload (like a decision engine), and even blockchain node sit on the same private network on Nirvana’s infrastructure. The compute and ClickHouse process sit on the same Kubernetes node, with storage one private-network hop away — no public-internet round trip between the engine and its working set.
Purpose-built block storage, no hyperscaler underneath.
Nirvana built the entire cloud stack in-house. Nirvana ABS gives you 20,000 baseline IOPS included with no per-IOP surcharges, multi-GB/s bandwidth at large block sizes, and stable sequential read throughput under sustained load.
Dedicated ClickHouse per customer.
Multi-tenant ClickHouse was explicitly rejected for security and compliance reasons. Each customer gets a dedicated cluster running on shared underlying infrastructure with Kubernetes-level isolation.
Altinity-operated.
ClickHouse 25.8.16 fully managed by Altinity, running on Nirvana NKS: one operational support path for both the engine and the substrate.
Benchmark your ClickHouse workload on Nirvana.
Bring your own dataset or your own queries and compare performance on the same infrastructure this report was measured on.
Request a benchmark →