Company or project name
Company or project name: JetBrains — IJ Perf, the IntelliJ performance dashboard (https://github.com/JetBrains/ij-perf-report-aggregator)
Describe the situation
While evaluating an upgrade from 24.3 LTS we found that every dashboard chart query regressed
20–55% on 26.3/26.6. These queries unnest a Nested column of per-report measurements
(ARRAY JOIN measures) and filter on the measurement name. A per-month bisect on a
self-contained synthetic repro (below) shows three separate steps:
- v25.9 —
ARRAY JOIN over a Nested column got ~25% slower (single-array shapes unaffected).
- v26.1 — filtering unnested string array elements got 30–75% slower across every shape we
tested: plain Array(String), Array(LowCardinality(String)), Nested, and even
arrayCount(x -> x = c, arr) with no ARRAY JOIN at all.
- v26.6 —
ARRAY JOIN over Nested with LowCardinality takes another large step
(~1.9× vs 25.8 overall).
Each step is present in the earliest published release of its series (v25.9.2.1, v26.1.1.912),
so these are not backports into later patches. (v26.1.1.912 was in fact worse — 1238 ms on q4
below vs 1039 ms on v26.1.12.23 — later 26.1 patches recovered part of it.)
The loss is executor CPU, not I/O or planning: read_rows, selected marks, and read bytes are
identical across versions, and single-threaded ratios match multi-threaded ones.
Which ClickHouse versions are affected?
report is our production schema: measures is
Nested(name LowCardinality(String), value Int32, type LowCardinality(String)) on a MergeTree
with a handful of LowCardinality dimension columns; same generator, same 52M elements.
Apple M4 Max/ 64 GB; median of 5 runs inside one clickhouse local process;
each version reads data it generated itself (content is deterministic and identical). Latest
published patch of each series:
| # |
query shape |
25.8.28.1 |
25.9.7.56 |
25.10.7.6 |
25.12.11.4 |
26.1.12.23 |
26.3.17.56 |
26.6.2.81 |
| q1 |
ARRAY JOIN arr AS x WHERE x = c, Array(String) |
79 |
81 |
68 |
66 |
114 |
118 |
154 |
| q2 |
same, Array(LowCardinality(String)) |
82 |
87 |
86 |
82 |
146 |
147 |
144 |
| q3 |
ARRAY JOIN measures WHERE measures.name = c (Nested) |
206 |
261 |
257 |
250 |
260 |
261 |
387 |
| q4 |
q3 with max_threads = 1 |
759 |
930 |
907 |
916 |
1039 |
1065 |
1402 |
| q5 |
sum(arrayCount(x -> x = c, measures.name)) — no ARRAY JOIN |
254 |
253 |
253 |
247 |
311 |
327 |
324 |
| q6 |
full dashboard query (time filter, machine LIKE, ORDER BY, 9 output columns) |
239 |
247 |
241 |
233 |
308 |
314 |
362 |
(ms; bold = where a step lands. 25.11 not tested: 25.12 is clean for the 26.1 step and the
25.9 step had already landed.)
How to reproduce
How to reproduce
Generate deterministic data (identical content on every version) and run one
query per version — official prebuilt binaries, macos-aarch64:
-- clickhouse local --path <fresh dir per version>
CREATE TABLE t (arr Array(String)) ENGINE = MergeTree ORDER BY tuple();
INSERT INTO t
SELECT arrayMap(i -> concat('some.metric.name.of.realistic.len.', toString(cityHash64(number, i) % 15000)),
range(if(cityHash64(number, 5) % 10 = 0, 726, 16)))
FROM numbers(600000);
OPTIMIZE TABLE t FINAL;
-- 52,026,760 array elements total; ~15k distinct ~35-char strings; skewed lengths (90% × 16, 10% × 726).
-- The filter matches 3,420 elements (dashboards routinely filter on rare or absent names):
SELECT count() FROM t ARRAY JOIN arr AS x WHERE x = 'some.metric.name.of.realistic.len.7777';
This query (q1 below) runs in 66 ms on v25.12.11.4 and 114 ms on v26.1.12.23 (1.7×) on
the same machine.
queries.sql
repro.sql
run.sh
Reading the steps:
- 25.9 hits only the Nested shapes (q3: 206→261, q4: 759→930); q1/q2/q5/q6 are flat.
- 26.1 hits every string-filter shape: q1 +73%, q2 +78%, q5 +26%, q6 +32% vs 25.12, and
stacks another +13% onto Nested q4. q5 shows it is not specific to ARRAY JOIN — the same
work as an arrayCount lambda regresses identically.
- 26.6 adds a Nested(+LowCardinality)-specific step: q3 261→387, q4 1065→1402.
- Aside: 25.10 improved q1 (79→68 ms), which the 26.1 step then more than undoes.
Expected performance
Query latency on 26.x comparable to 25.8/25.12 for ARRAY JOIN / array-filter workloads.
The 26.1 step currently blocks our 26.3 LTS upgrade (dashboard p50 is dominated by exactly
this query class).
Related issues and pull requests
The profile resembles #91430, but this repro contains no multiIf and the analyzer is on in both versions.
Additional context
SETTINGS enable_analyzer = 0 on 26.3 recovers the Nested shape only partially
(261 → 233 ms; 25.8 is 206 ms). compatibility = '24.3' had no effect in our full
evaluation. Both versions run the new analyzer by default, so this is not a
settings-default change.
- In our production-shaped evaluation (45 real dashboard queries, restored production data,
24.3 baseline): all ARRAY JOIN measures queries were 1.2–1.6× slower on 26.3/26.6 with
identical result rows, while peak memory of heavy aggregations dropped ~4× (164 → 43 MB on
26.6).
Company or project name
Company or project name: JetBrains — IJ Perf, the IntelliJ performance dashboard (https://github.com/JetBrains/ij-perf-report-aggregator)
Describe the situation
While evaluating an upgrade from 24.3 LTS we found that every dashboard chart query regressed
20–55% on 26.3/26.6. These queries unnest a
Nestedcolumn of per-report measurements(
ARRAY JOIN measures) and filter on the measurement name. A per-month bisect on aself-contained synthetic repro (below) shows three separate steps:
ARRAY JOINover aNestedcolumn got ~25% slower (single-array shapes unaffected).tested: plain
Array(String),Array(LowCardinality(String)),Nested, and evenarrayCount(x -> x = c, arr)with no ARRAY JOIN at all.ARRAY JOINoverNestedwithLowCardinalitytakes another large step(~1.9× vs 25.8 overall).
Each step is present in the earliest published release of its series (v25.9.2.1, v26.1.1.912),
so these are not backports into later patches. (v26.1.1.912 was in fact worse — 1238 ms on q4
below vs 1039 ms on v26.1.12.23 — later 26.1 patches recovered part of it.)
The loss is executor CPU, not I/O or planning:
read_rows, selected marks, and read bytes areidentical across versions, and single-threaded ratios match multi-threaded ones.
Which ClickHouse versions are affected?
reportis our production schema:measuresisNested(name LowCardinality(String), value Int32, type LowCardinality(String))on a MergeTreewith a handful of LowCardinality dimension columns; same generator, same 52M elements.
Apple M4 Max/ 64 GB; median of 5 runs inside one
clickhouse localprocess;each version reads data it generated itself (content is deterministic and identical). Latest
published patch of each series:
ARRAY JOIN arr AS x WHERE x = c,Array(String)Array(LowCardinality(String))ARRAY JOIN measures WHERE measures.name = c(Nested)max_threads = 1sum(arrayCount(x -> x = c, measures.name))— no ARRAY JOINmachine LIKE, ORDER BY, 9 output columns)(ms; bold = where a step lands. 25.11 not tested: 25.12 is clean for the 26.1 step and the
25.9 step had already landed.)
How to reproduce
How to reproduce
Generate deterministic data (identical content on every version) and run one
query per version — official prebuilt binaries, macos-aarch64:
This query (q1 below) runs in 66 ms on v25.12.11.4 and 114 ms on v26.1.12.23 (1.7×) on
the same machine.
queries.sql
repro.sql
run.sh
Reading the steps:
stacks another +13% onto Nested q4. q5 shows it is not specific to ARRAY JOIN — the same
work as an
arrayCountlambda regresses identically.Expected performance
Query latency on 26.x comparable to 25.8/25.12 for ARRAY JOIN / array-filter workloads.
The 26.1 step currently blocks our 26.3 LTS upgrade (dashboard p50 is dominated by exactly
this query class).
Related issues and pull requests
The profile resembles #91430, but this repro contains no
multiIfand the analyzer is on in both versions.Additional context
SETTINGS enable_analyzer = 0on 26.3 recovers the Nested shape only partially(261 → 233 ms; 25.8 is 206 ms).
compatibility = '24.3'had no effect in our fullevaluation. Both versions run the new analyzer by default, so this is not a
settings-default change.
24.3 baseline): all
ARRAY JOIN measuresqueries were 1.2–1.6× slower on 26.3/26.6 withidentical result rows, while peak memory of heavy aggregations dropped ~4× (164 → 43 MB on
26.6).