Skip to content

Commit db4e7ba

Browse files
authored
chore: upgrade ClickHouse to 26.10 (#3624)
## Summary Upgrades ClickHouse from 26.8.36.3 to 26.10.3.7 and updates the tests that assumed the 26.8 TimeSeries layout. Needed for PromQL label lookups through ClickHouse's Prometheus HTTP API (26.9+). - Pins `26.10.3.7` in the four compose files (incl. e2e) and the all-in-one `Dockerfile`. - `metadata.int.test.ts`: pins the `getTimeSeriesTableColumns` tables to `version = 7` (26.10 defaults to version 8, which moves `min_time`/`max_time` out of the tags table), expects the renamed `metric_family` column, and adds `getTimeSeriesTableVersion` coverage. - `timeseriesEngine.int.test.ts`: the version 8 suite fails instead of silently skipping. - `seed-clickhouse.ts`: the e2e PromQL seed writes to `samples` (new tables have no `time_series` column). No production code changes were needed. On an existing volume, `system.metric_log` history moves to `system.metric_log_0`. Local otel smoke tests need `clickhouse-client` 26.9+. ### References - Linear Issue: part of HDX-5420
1 parent f172a48 commit db4e7ba

9 files changed

Lines changed: 80 additions & 36 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@hyperdx/api': minor
3+
---
4+
5+
chore: upgrade bundled ClickHouse to 26.10.3.7
6+
7+
The all-in-one image and the Docker Compose files now use
8+
`clickhouse/clickhouse-server:26.10.3.7-alpine`. New TimeSeries tables default
9+
to schema version 8, which keeps each series' time range in a separate inner
10+
table; existing tables keep their layout. `system.metric_log` switches to the
11+
bucketed schema, and rows from before the upgrade move to `system.metric_log_0`.

‎docker-compose.ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ services:
2222
depends_on:
2323
- ch-server
2424
ch-server:
25-
image: clickhouse/clickhouse-server:26.8.36.3-alpine
25+
image: clickhouse/clickhouse-server:26.10.3.7-alpine
2626
environment:
2727
# default settings
2828
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1

‎docker-compose.dev.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ services:
112112
hdx.dev.service: clickhouse
113113
hdx.dev.port: '${HDX_DEV_CH_HTTP_PORT:-8123}'
114114
hdx.dev.url: 'http://localhost:${HDX_DEV_CH_HTTP_PORT:-8123}'
115-
image: clickhouse/clickhouse-server:26.8.36.3-alpine
115+
image: clickhouse/clickhouse-server:26.10.3.7-alpine
116116
ports:
117117
- '${HDX_DEV_CH_HTTP_PORT:-8123}:8123' # http api
118118
- '${HDX_DEV_CH_NATIVE_PORT:-9000}:9000' # native

‎docker-compose.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ services:
109109
- ch-server
110110
- db
111111
ch-server:
112-
image: clickhouse/clickhouse-server:26.8.36.3-alpine
112+
image: clickhouse/clickhouse-server:26.10.3.7-alpine
113113
# WARNING: Exposing the database port will make it accessible from outside the container,
114114
# potentially allowing unauthorized access. If you uncomment the ports below,
115115
# ensure to secure your database (e.g., with strong authentication, proper network rules, and firewalls).

‎docker/hyperdx/Dockerfile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# - Otel Collector (otelcol)
99

1010
ARG NODE_VERSION=22.23.4
11-
ARG CLICKHOUSE_VERSION=26.8.36.3
11+
ARG CLICKHOUSE_VERSION=26.10.3.7
1212
ARG OTEL_COLLECTOR_VERSION=0.155.0
1313
ARG OTEL_COLLECTOR_CORE_VERSION=1.61.0
1414

‎packages/api/src/controllers/__tests__/timeseriesEngine.int.test.ts‎

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -505,13 +505,10 @@ describe('timeseriesEngine controller', () => {
505505
});
506506

507507
// Schema version 8 (the default from ClickHouse 26.10) moved min_time and
508-
// max_time out of the tags table into a separate time ranges table. Older
509-
// servers don't know the `version` setting, so these run only where the
510-
// table can be created.
508+
// max_time out of the tags table into a separate time ranges table.
511509
describe('table of schema version 8 (time ranges table)', () => {
512510
const V8_TABLE = 'test_ts_label_values_v8';
513511
const V8_NO_RANGES_TABLE = 'test_ts_label_values_v8_no_ranges';
514-
let supported = true;
515512

516513
const v8LabelValues = (
517514
args: Partial<Parameters<typeof queryLabelValues>[0]> & {
@@ -520,23 +517,18 @@ describe('timeseriesEngine controller', () => {
520517
) => labelValues({ tableName: V8_TABLE, ...args });
521518

522519
beforeAll(async () => {
523-
try {
524-
await seedTimeSeriesTagsTable({
525-
table: V8_TABLE,
526-
series: SERIES,
527-
withSamples: true,
528-
timeRanges: true,
529-
});
530-
await seedTimeSeriesTagsTable({
531-
table: V8_NO_RANGES_TABLE,
532-
series: SERIES,
533-
storeTimeBounds: false,
534-
timeRanges: true,
535-
});
536-
} catch (e) {
537-
if (!/Unknown setting 'version'/.test(String(e))) throw e;
538-
supported = false;
539-
}
520+
await seedTimeSeriesTagsTable({
521+
table: V8_TABLE,
522+
series: SERIES,
523+
withSamples: true,
524+
timeRanges: true,
525+
});
526+
await seedTimeSeriesTagsTable({
527+
table: V8_NO_RANGES_TABLE,
528+
series: SERIES,
529+
storeTimeBounds: false,
530+
timeRanges: true,
531+
});
540532
});
541533

542534
afterAll(async () => {
@@ -546,7 +538,6 @@ describe('timeseriesEngine controller', () => {
546538
});
547539

548540
it('scopes label values to the window through the time ranges table', async () => {
549-
if (!supported) return;
550541
expect(
551542
await v8LabelValues({
552543
labelName: 'job',
@@ -557,7 +548,6 @@ describe('timeseriesEngine controller', () => {
557548
});
558549

559550
it('applies lone bounds against the time ranges', async () => {
560-
if (!supported) return;
561551
expect(
562552
await v8LabelValues({
563553
labelName: '__name__',
@@ -573,7 +563,6 @@ describe('timeseriesEngine controller', () => {
573563
});
574564

575565
it('returns nothing for a window no series covers', async () => {
576-
if (!supported) return;
577566
expect(
578567
await v8LabelValues({
579568
labelName: '__name__',
@@ -584,7 +573,6 @@ describe('timeseriesEngine controller', () => {
584573
});
585574

586575
it('evaluates match[] and narrows it by the window', async () => {
587-
if (!supported) return;
588576
expect(
589577
await v8LabelValues({
590578
labelName: 'job',
@@ -602,7 +590,6 @@ describe('timeseriesEngine controller', () => {
602590
});
603591

604592
it('scopes label names to the window and the selector', async () => {
605-
if (!supported) return;
606593
const names = (
607594
args: Partial<Parameters<typeof queryLabelNames>[0]> = {},
608595
) =>
@@ -627,7 +614,6 @@ describe('timeseriesEngine controller', () => {
627614
});
628615

629616
it('rejects match[] without a time ranges table, and ignores the bounds', async () => {
630-
if (!supported) return;
631617
await expect(
632618
labelValues({
633619
tableName: V8_NO_RANGES_TABLE,

‎packages/app/tests/e2e/docker-compose.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ services:
1515
memory: 512M
1616
cpus: '0.5'
1717
ch-server:
18-
image: clickhouse/clickhouse-server:26.8.36.3-alpine
18+
image: clickhouse/clickhouse-server:26.10.3.7-alpine
1919
ports:
2020
- ${HDX_E2E_CH_PORT:-20500}:8123 # http api
2121
- ${HDX_E2E_CH_NATIVE_PORT:-20600}:9000 # native

‎packages/app/tests/e2e/seed-clickhouse.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1175,7 +1175,7 @@ async function insertPromqlMetric(
11751175
.join('\n UNION ALL\n ');
11761176

11771177
await client.query(`
1178-
INSERT INTO ${table} (metric_name, tags, time_series)
1178+
INSERT INTO ${table} (metric_name, tags, samples)
11791179
${rows}
11801180
`);
11811181
}

‎packages/common-utils/src/__tests__/metadata.int.test.ts‎

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,14 +1159,17 @@ describe('Metadata Integration Tests', () => {
11591159
clickhouse_settings: { allow_experimental_time_series_table: 1 },
11601160
});
11611161

1162+
// Pinned to version 7: from version 8 (the default since ClickHouse 26.10)
1163+
// min_time/max_time live in a separate time ranges table, and
1164+
// store_min_time_and_max_time is rejected.
11621165
beforeAll(async () => {
11631166
await timeSeriesCommand(`DROP TABLE IF EXISTS default.${boundedTable}`);
11641167
await timeSeriesCommand(`DROP TABLE IF EXISTS default.${unboundedTable}`);
11651168
await timeSeriesCommand(
1166-
`CREATE TABLE default.${boundedTable} ENGINE = TimeSeries`,
1169+
`CREATE TABLE default.${boundedTable} ENGINE = TimeSeries SETTINGS version = 7`,
11671170
);
11681171
await timeSeriesCommand(
1169-
`CREATE TABLE default.${unboundedTable} ENGINE = TimeSeries SETTINGS store_min_time_and_max_time = 0`,
1172+
`CREATE TABLE default.${unboundedTable} ENGINE = TimeSeries SETTINGS store_min_time_and_max_time = 0, version = 7`,
11701173
);
11711174
});
11721175

@@ -1219,7 +1222,7 @@ describe('Metadata Integration Tests', () => {
12191222
});
12201223

12211224
it.each([
1222-
['Metrics', ['metric_family_name', 'type', 'unit', 'help']],
1225+
['Metrics', ['metric_family', 'type', 'unit', 'help']],
12231226
['Data', ['id', 'timestamp', 'value']],
12241227
] as const)(
12251228
'describes the %s inner table',
@@ -1307,6 +1310,50 @@ describe('Metadata Integration Tests', () => {
13071310
});
13081311
});
13091312

1313+
// The version decides which samples column a query reads (`samples` from
1314+
// version 3, `time_series` before), so a version read as 0 silently breaks
1315+
// queries on new tables.
1316+
describe('getTimeSeriesTableVersion', () => {
1317+
const defaultTable = 'test_ts_version_default';
1318+
const v7Table = 'test_ts_version_7';
1319+
1320+
const timeSeriesCommand = (query: string) =>
1321+
client.command({
1322+
query,
1323+
clickhouse_settings: { allow_experimental_time_series_table: 1 },
1324+
});
1325+
1326+
const version = (tableName: string) =>
1327+
new Metadata(hdxClient, new MetadataCache()).getTimeSeriesTableVersion({
1328+
connectionId: 'test_connection',
1329+
databaseName: 'default',
1330+
tableName,
1331+
});
1332+
1333+
beforeAll(async () => {
1334+
await timeSeriesCommand(
1335+
`CREATE OR REPLACE TABLE default.${defaultTable} ENGINE = TimeSeries`,
1336+
);
1337+
await timeSeriesCommand(
1338+
`CREATE OR REPLACE TABLE default.${v7Table} ENGINE = TimeSeries SETTINGS version = 7`,
1339+
);
1340+
});
1341+
1342+
afterAll(async () => {
1343+
await timeSeriesCommand(`DROP TABLE IF EXISTS default.${defaultTable}`);
1344+
await timeSeriesCommand(`DROP TABLE IF EXISTS default.${v7Table}`);
1345+
});
1346+
1347+
it('reads the version from the table definition', async () => {
1348+
expect(await version(defaultTable)).toBe(8);
1349+
expect(await version(v7Table)).toBe(7);
1350+
});
1351+
1352+
it('answers 0 for a table that does not exist', async () => {
1353+
expect(await version('test_ts_version_missing')).toBe(0);
1354+
});
1355+
});
1356+
13101357
describe('getTimeSeriesTables', () => {
13111358
const database = 'test_ts_discovery';
13121359

0 commit comments

Comments
 (0)