Skip to content

Commit b786db5

Browse files
committed
feat(clickhouse): emit per-query OTel metrics for pool sizing and cap tuning
Every ClickHouse read query now records duration (wall-clock and server-side), rows and bytes read, peak memory usage, an in-flight gauge, and an error counter tagged by ClickHouse error type, all keyed by client and operation. These give the runs-list pool live signal for sizing max_open_connections and the per-query caps instead of one-off query_log snapshots.
1 parent 524ed61 commit b786db5

3 files changed

Lines changed: 533 additions & 312 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { ClickHouse } from "@internal/clickhouse";
2+
import { containerTest } from "@internal/testcontainers";
3+
import { describe, expect, vi } from "vitest";
4+
import { RunsRepository } from "~/services/runsRepository/runsRepository.server";
5+
import {
6+
createRun,
7+
insertTaskRunV2Rows,
8+
seedParents,
9+
} from "./helpers/apiRunListPresenterTestHelpers";
10+
import { createInMemoryMetrics } from "./utils/tracing";
11+
import { histogramCount, latestMetrics, metricSum } from "./otlpMetrics.helpers";
12+
13+
vi.mock("~/db.server", () => ({ prisma: {}, $replica: {} }));
14+
15+
vi.setConfig({ testTimeout: 90_000 });
16+
17+
describe("clickhouse query metrics", () => {
18+
containerTest(
19+
"records duration + read_rows on success and an error metric with the ClickHouse error type",
20+
async ({ clickhouseContainer, prisma }) => {
21+
const ctx = await seedParents(prisma, "chm");
22+
const run = await createRun(prisma, ctx, { friendlyId: "run_chm" });
23+
24+
const seedClient = new ClickHouse({
25+
url: clickhouseContainer.getConnectionUrl(),
26+
name: "clickhouse-metrics-seed",
27+
});
28+
await insertTaskRunV2Rows(seedClient, [{ ...run, createdAt: new Date() }]);
29+
30+
const listArgs = {
31+
page: { size: 10 } as const,
32+
organizationId: ctx.organizationId,
33+
projectId: ctx.projectId,
34+
environmentId: ctx.environmentId,
35+
};
36+
37+
const okMetrics = createInMemoryMetrics();
38+
const okClient = new ClickHouse({
39+
url: clickhouseContainer.getConnectionUrl(),
40+
name: "clickhouse-metrics-ok",
41+
meter: okMetrics.meter,
42+
});
43+
const okRepo = new RunsRepository({ prisma, clickhouse: okClient });
44+
const result = await okRepo.listRuns(listArgs);
45+
expect(result.runs.map((r) => r.friendlyId)).toEqual(["run_chm"]);
46+
47+
await vi.waitFor(
48+
async () => {
49+
const rm = await latestMetrics(okMetrics);
50+
expect(
51+
histogramCount(rm, "clickhouse.query.duration", {
52+
client: "clickhouse-metrics-ok",
53+
status: "ok",
54+
})
55+
).toBeGreaterThanOrEqual(1);
56+
},
57+
{ timeout: 5000, interval: 50 }
58+
);
59+
const okRm = await latestMetrics(okMetrics);
60+
expect(
61+
histogramCount(okRm, "clickhouse.query.read_rows", { client: "clickhouse-metrics-ok" })
62+
).toBeGreaterThanOrEqual(1);
63+
expect(
64+
histogramCount(okRm, "clickhouse.query.memory_usage", { client: "clickhouse-metrics-ok" })
65+
).toBeGreaterThanOrEqual(1);
66+
await okMetrics.shutdown();
67+
68+
const errMetrics = createInMemoryMetrics();
69+
const cappedClient = new ClickHouse({
70+
url: clickhouseContainer.getConnectionUrl(),
71+
name: "clickhouse-metrics-capped",
72+
clickhouseSettings: { max_memory_usage: "1" },
73+
meter: errMetrics.meter,
74+
});
75+
const errRepo = new RunsRepository({ prisma, clickhouse: cappedClient });
76+
await expect(errRepo.listRuns(listArgs)).rejects.toThrow();
77+
78+
await vi.waitFor(
79+
async () => {
80+
const rm = await latestMetrics(errMetrics);
81+
expect(
82+
metricSum(rm, "clickhouse.query.errors", {
83+
client: "clickhouse-metrics-capped",
84+
error_type: "MEMORY_LIMIT_EXCEEDED",
85+
})
86+
).toBeGreaterThanOrEqual(1);
87+
},
88+
{ timeout: 5000, interval: 50 }
89+
);
90+
await errMetrics.shutdown();
91+
}
92+
);
93+
});

0 commit comments

Comments
 (0)