|
1 | | -import { ClickHouse } from "@internal/clickhouse"; |
| 1 | +import { ClickHouse, type ClickHouseSettings } from "@internal/clickhouse"; |
2 | 2 | import { createHash } from "crypto"; |
3 | 3 | import { ClickhouseEventRepository } from "~/v3/eventRepository/clickhouseEventRepository.server"; |
4 | 4 | import { env } from "~/env.server"; |
@@ -292,6 +292,45 @@ function initializeRealtimeClickhouseClient(): ClickHouse { |
292 | 292 | }); |
293 | 293 | } |
294 | 294 |
|
| 295 | +/** |
| 296 | + * Server-side query protection for the runs-list read pool. Every setting here is PER-QUERY, so a |
| 297 | + * pathological query only ever kills itself: a slow one hits `max_execution_time`, a memory-hungry |
| 298 | + * one hits `max_memory_usage`, a thread-hungry one hits `max_threads`. Per-USER limits |
| 299 | + * (`max_*_for_user`) are deliberately NOT used: everything connects as `default`, so a per-user cap |
| 300 | + * would reject whichever query arrives once the shared budget is hit, punishing innocent tenants |
| 301 | + * for a noisy one. The node itself is protected by the server-level `max_server_memory_usage`. |
| 302 | + * Safe as client-level settings ONLY because this pool is read-only; on a mixed read+write pool a |
| 303 | + * client-level `max_execution_time` would also kill slow inserts. `readonly=2` enforces read-only |
| 304 | + * while still allowing these settings to apply (`readonly=1` rejects them). |
| 305 | + */ |
| 306 | +/** |
| 307 | + * Client request timeout for the runs-list pool, forced above the server-side `max_execution_time` |
| 308 | + * so the server cap is what stops a slow query and the client stays connected to receive that |
| 309 | + * error. If the client timed out first, it would abort while ClickHouse kept executing, which is |
| 310 | + * the abandoned-query behaviour this pool is trying to prevent. |
| 311 | + */ |
| 312 | +function getRunsListRequestTimeoutMs() { |
| 313 | + return Math.max( |
| 314 | + env.RUNS_LIST_CLICKHOUSE_REQUEST_TIMEOUT_MS, |
| 315 | + (env.RUNS_LIST_CLICKHOUSE_MAX_EXECUTION_TIME + 5) * 1000 |
| 316 | + ); |
| 317 | +} |
| 318 | + |
| 319 | +function getRunsListClickhouseSettings(): ClickHouseSettings { |
| 320 | + const settings: ClickHouseSettings = { |
| 321 | + max_execution_time: env.RUNS_LIST_CLICKHOUSE_MAX_EXECUTION_TIME, |
| 322 | + timeout_before_checking_execution_speed: 0, |
| 323 | + max_threads: env.RUNS_LIST_CLICKHOUSE_MAX_THREADS, |
| 324 | + max_memory_usage: env.RUNS_LIST_CLICKHOUSE_MAX_MEMORY_USAGE.toString(), |
| 325 | + }; |
| 326 | + |
| 327 | + if (env.RUNS_LIST_CLICKHOUSE_READONLY !== "0") { |
| 328 | + settings.readonly = env.RUNS_LIST_CLICKHOUSE_READONLY; |
| 329 | + } |
| 330 | + |
| 331 | + return settings; |
| 332 | +} |
| 333 | + |
295 | 334 | /** Runs list reads — dashboard + API (`RUNS_LIST_CLICKHOUSE_URL`); |
296 | 335 | * falls back to the default client if unset. */ |
297 | 336 | const defaultRunsListClickhouseClient = singleton( |
@@ -319,6 +358,8 @@ function initializeRunsListClickhouseClient(): ClickHouse { |
319 | 358 | request: env.RUNS_LIST_CLICKHOUSE_COMPRESSION_REQUEST === "1", |
320 | 359 | }, |
321 | 360 | maxOpenConnections: env.RUNS_LIST_CLICKHOUSE_MAX_OPEN_CONNECTIONS, |
| 361 | + requestTimeoutMs: getRunsListRequestTimeoutMs(), |
| 362 | + clickhouseSettings: getRunsListClickhouseSettings(), |
322 | 363 | }); |
323 | 364 | } |
324 | 365 |
|
@@ -550,10 +591,25 @@ function buildOrgClickhouseClient(url: string, clientType: ClientType): ClickHou |
550 | 591 | }, |
551 | 592 | maxOpenConnections: env.REALTIME_BACKEND_NATIVE_CLICKHOUSE_MAX_OPEN_CONNECTIONS, |
552 | 593 | }); |
| 594 | + case "runsList": |
| 595 | + return new ClickHouse({ |
| 596 | + url: parsed.toString(), |
| 597 | + name, |
| 598 | + keepAlive: { |
| 599 | + enabled: env.RUNS_LIST_CLICKHOUSE_KEEP_ALIVE_ENABLED === "1", |
| 600 | + idleSocketTtl: env.RUNS_LIST_CLICKHOUSE_KEEP_ALIVE_IDLE_SOCKET_TTL_MS, |
| 601 | + }, |
| 602 | + logLevel: env.RUNS_LIST_CLICKHOUSE_LOG_LEVEL, |
| 603 | + compression: { |
| 604 | + request: env.RUNS_LIST_CLICKHOUSE_COMPRESSION_REQUEST === "1", |
| 605 | + }, |
| 606 | + maxOpenConnections: env.RUNS_LIST_CLICKHOUSE_MAX_OPEN_CONNECTIONS, |
| 607 | + requestTimeoutMs: getRunsListRequestTimeoutMs(), |
| 608 | + clickhouseSettings: getRunsListClickhouseSettings(), |
| 609 | + }); |
553 | 610 | case "standard": |
554 | 611 | case "query": |
555 | 612 | case "admin": |
556 | | - case "runsList": |
557 | 613 | return new ClickHouse({ |
558 | 614 | url: parsed.toString(), |
559 | 615 | name, |
|
0 commit comments