Skip to content

Commit 23d8f3f

Browse files
committed
fix(clickhouse): keep limit breaches on our own queries at error level
Downgrading quota errors regardless of who wrote the SQL was wrong. An internal analytics query hitting the memory ceiling or a timeout is a runaway query of ours, and it was losing the alert it used to raise. Both downgrades are now gated on the same condition: ClickHouse rejected SQL that the caller wrote. Anything on SQL we generated stays at error, which also makes query, queryFast and the streaming path plain error logs again, since none of them can carry caller-written SQL.
1 parent 572f394 commit 23d8f3f

2 files changed

Lines changed: 39 additions & 29 deletions

File tree

internal-packages/clickhouse/src/client/client.ts

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -179,16 +179,7 @@ export class ClickhouseClient implements ClickhouseReader, ClickhouseWriter {
179179
queryId,
180180
};
181181

182-
switch (classifyClickhouseError(clickhouseError, false)) {
183-
case "quota":
184-
this.logger.warn("Query exceeded a ClickHouse limit", errorLogFields);
185-
break;
186-
case "invalid-sql":
187-
this.logger.warn("ClickHouse rejected an invalid query", errorLogFields);
188-
break;
189-
default:
190-
this.logger.error("Error querying clickhouse", errorLogFields);
191-
}
182+
this.logger.error("Error querying clickhouse", errorLogFields);
192183

193184
recordClickhouseError(span, clickhouseError);
194185

@@ -494,16 +485,7 @@ export class ClickhouseClient implements ClickhouseReader, ClickhouseWriter {
494485
queryId,
495486
};
496487

497-
switch (classifyClickhouseError(clickhouseError, false)) {
498-
case "quota":
499-
this.logger.warn("Query exceeded a ClickHouse limit", errorLogFields);
500-
break;
501-
case "invalid-sql":
502-
this.logger.warn("ClickHouse rejected an invalid query", errorLogFields);
503-
break;
504-
default:
505-
this.logger.error("Error querying clickhouse", errorLogFields);
506-
}
488+
this.logger.error("Error querying clickhouse", errorLogFields);
507489

508490
recordClickhouseError(span, clickhouseError);
509491

@@ -651,11 +633,7 @@ export class ClickhouseClient implements ClickhouseReader, ClickhouseWriter {
651633
queryId,
652634
};
653635

654-
if (error instanceof Error && classifyClickhouseError(error, false) === "quota") {
655-
self.logger.warn("Streamed query exceeded a ClickHouse limit", errorLogFields);
656-
} else {
657-
self.logger.error("Error streaming clickhouse", errorLogFields);
658-
}
636+
self.logger.error("Error streaming clickhouse", errorLogFields);
659637

660638
if (error instanceof Error) {
661639
recordClickhouseError(span, error);
@@ -1053,8 +1031,8 @@ export class ClickhouseClient implements ClickhouseReader, ClickhouseWriter {
10531031

10541032
/**
10551033
* ClickHouse error types raised by a query that is valid but asks for more than
1056-
* the caller is allowed to spend. The caller gets a 4xx and there is nothing on
1057-
* our side to fix, so these are logged at warn rather than error.
1034+
* it is allowed to spend. Only downgraded for SQL the caller wrote: a runaway
1035+
* query we generated is our bug and still has to alert.
10581036
*/
10591037
const CLICKHOUSE_QUOTA_ERROR_TYPES = new Set([
10601038
"MEMORY_LIMIT_EXCEEDED",
@@ -1098,13 +1076,13 @@ function classifyClickhouseError(
10981076
error: Error,
10991077
userAuthoredQuery: boolean | undefined
11001078
): ClickhouseErrorCategory {
1101-
if (!(error instanceof ClickHouseError) || error.type === undefined) {
1079+
if (!userAuthoredQuery || !(error instanceof ClickHouseError) || error.type === undefined) {
11021080
return "fault";
11031081
}
11041082
if (CLICKHOUSE_QUOTA_ERROR_TYPES.has(error.type)) {
11051083
return "quota";
11061084
}
1107-
if (userAuthoredQuery && CLICKHOUSE_INVALID_SQL_ERROR_TYPES.has(error.type)) {
1085+
if (CLICKHOUSE_INVALID_SQL_ERROR_TYPES.has(error.type)) {
11081086
return "invalid-sql";
11091087
}
11101088
return "fault";

internal-packages/clickhouse/src/tsql.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1763,11 +1763,43 @@ describe("TSQL Error Log Levels", () => {
17631763
organization_id: { op: "eq", value: "org_tenant1" },
17641764
},
17651765
tableSchema: [taskRunsSchema],
1766+
userAuthoredQuery: true,
17661767
clickhouseSettings: { max_rows_to_read: "1" },
17671768
});
17681769

17691770
expect(error).not.toBeNull();
17701771
expect(logged(warnSpy)).toContain("Query exceeded a ClickHouse limit");
17711772
expect(logged(errorSpy)).not.toContain("Error querying clickhouse");
17721773
});
1774+
1775+
clickhouseTest(
1776+
"keeps a limit breach on a query we generated at error level",
1777+
async ({ clickhouseContainer }) => {
1778+
const client = new ClickhouseClient({
1779+
name: "test",
1780+
url: clickhouseContainer.getConnectionUrl(),
1781+
});
1782+
1783+
await insertTaskRuns(client, { async_insert: 0 })([
1784+
createTaskRun({ run_id: "run_intlimit1" }),
1785+
createTaskRun({ run_id: "run_intlimit2" }),
1786+
createTaskRun({ run_id: "run_intlimit3" }),
1787+
]);
1788+
1789+
const [error] = await executeTSQL(client, {
1790+
name: "test-internal-resource-limit",
1791+
query: "SELECT run_id FROM task_runs",
1792+
schema: z.object({ run_id: z.string() }),
1793+
enforcedWhereClause: {
1794+
organization_id: { op: "eq", value: "org_tenant1" },
1795+
},
1796+
tableSchema: [taskRunsSchema],
1797+
clickhouseSettings: { max_rows_to_read: "1" },
1798+
});
1799+
1800+
expect(error).not.toBeNull();
1801+
expect(logged(errorSpy)).toContain("Error querying clickhouse");
1802+
expect(logged(warnSpy)).not.toContain("Query exceeded a ClickHouse limit");
1803+
}
1804+
);
17731805
});

0 commit comments

Comments
 (0)