Skip to content

Commit 01bd657

Browse files
authored
Parameterize queued_jobs_aggregate ClickHouse query (#8007)
**Impact:** AWS autoscaler Lambda and any future consumers of the `queued_jobs_aggregate` HUD API endpoint **Risk:** low ## What Replaces hardcoded filter values in the `queued_jobs_aggregate` ClickHouse query with named parameters that have sensible defaults, and adds support for a `defaults` mechanism in `queryClickhouseSaved` so callers can omit parameters and get the previous behavior. ## Why The `queued_jobs_aggregate` query powers the AWS autoscaler (`scale-up-chron` Lambda) that decides how many CI runners to spin up based on queued job counts. The query previously hardcoded the queued-time threshold (30 min), max age window (3 days), and org filter (`pytorch`, `pytorch-labs`, `meta-pytorch`) — making it impossible for different callers (e.g., a per-repo autoscaler or a new fleet) to customize behavior without duplicating the query. Part of the work tracked in [pytorch/ci-infra#499](pytorch/ci-infra#499) and companion changes in [pytorch/ci-infra#500](pytorch/ci-infra#500) and [jeanschmidt/actions-runner-controller#1](jeanschmidt/actions-runner-controller#1). ## How - Introduced a `"defaults"` key in `params.json` — a new convention for ClickHouse saved queries. When a caller omits a parameter, the default value is used instead, preserving backward compatibility for existing callers that pass `{}`. - Used ClickHouse's `toIntervalMinute()` / `toIntervalDay()` functions instead of `INTERVAL` literals to accept parameterized values. - Added an optional `repo` filter (`{repo: String}`) that, when empty string (the default), matches all repos — allowing callers to scope results to a single repository without changing the query structure. ## Changes - **`torchci/clickhouse_queries/queued_jobs_aggregate/query.sql`** - `INTERVAL 30 MINUTE` → `toIntervalMinute({queuedThresholdMinutes: Int64})` - `INTERVAL 3 DAY` → `toIntervalDay({maxAgeDays: Int64})` - Hardcoded org list → `{orgs: Array(String)}` parameter - Added optional `{repo: String}` filter (no-op when empty string) - **`torchci/clickhouse_queries/queued_jobs_aggregate/params.json`** - Declared four typed parameters: `queuedThresholdMinutes`, `maxAgeDays`, `orgs`, `repo` - Added `defaults` block preserving the original hardcoded values (30 min, 3 days, pytorch orgs, all repos) - Added a test case with zeroed-out / empty values - **`torchci/lib/clickhouse.ts`** - `queryClickhouseSaved` now reads `defaults` from `params.json` and falls back to them when `inputParams` does not provide a value (via `inputParams[key] !== undefined ? inputParams[key] : defaults[key]`) - Backward compatible: existing queries without a `defaults` key default to `{}` via `?? {}` ## Notes - This is the first query to use the `defaults` convention. Other queries can adopt the same pattern if needed. - The autoscaler Lambda currently calls this endpoint with no parameters — it will automatically get the default values and see no behavior change. - The companion ci-infra PR ([#500](pytorch/ci-infra#500)) updates the autoscaler to pass custom parameters when needed. ## Testing - Verify existing behavior is preserved: call `/api/clickhouse/queued_jobs_aggregate?parameters={}` and confirm the response matches the previous hardcoded query results. - Test parameter overrides: call with explicit `queuedThresholdMinutes`, `maxAgeDays`, `orgs`, and `repo` values and verify the results reflect the custom filters. - Test partial overrides: call with only some parameters (e.g., just `repo`) and confirm defaults are applied for the rest. - Run `yarn build` in `torchci/` to verify TypeScript compilation. Signed-off-by: Jean Schmidt <contato@jschmidt.me>
1 parent 865c77c commit 01bd657

3 files changed

Lines changed: 32 additions & 14 deletions

File tree

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
11
{
2-
"params": {},
3-
"tests": []
4-
}
2+
"params": {
3+
"queuedThresholdMinutes": "Int64",
4+
"maxAgeDays": "Int64",
5+
"orgs": "Array(String)",
6+
"repo": "String"
7+
},
8+
"defaults": {
9+
"queuedThresholdMinutes": 30,
10+
"maxAgeDays": 3,
11+
"orgs": ["pytorch", "pytorch-labs", "meta-pytorch"],
12+
"repo": ""
13+
},
14+
"tests": [
15+
{
16+
"queuedThresholdMinutes": 0,
17+
"maxAgeDays": 0,
18+
"orgs": [],
19+
"repo": ""
20+
}
21+
]
22+
}

torchci/clickhouse_queries/queued_jobs_aggregate/query.sql

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ WITH possible_queued_jobs AS (
1414
status = 'queued'
1515
AND created_at < (
1616
-- Only consider jobs that have been queued for a significant period of time
17-
CURRENT_TIMESTAMP() - INTERVAL 30 MINUTE
17+
CURRENT_TIMESTAMP() - toIntervalMinute({queuedThresholdMinutes: Int64})
1818
)
1919
AND created_at > (
2020
-- Queued jobs are automatically cancelled after this long. Any allegedly pending
2121
-- jobs older than this are actually bad data
22-
CURRENT_TIMESTAMP() - INTERVAL 3 DAY
22+
CURRENT_TIMESTAMP() - toIntervalDay({maxAgeDays: Int64})
2323
)
2424
),
2525

@@ -49,9 +49,8 @@ queued_jobs AS (
4949
WHERE
5050
job.id IN (SELECT id FROM possible_queued_jobs)
5151
AND workflow.id IN (SELECT run_id FROM possible_queued_jobs)
52-
AND workflow.repository.owner.login IN (
53-
'pytorch', 'pytorch-labs', 'meta-pytorch'
54-
)
52+
AND workflow.repository.owner.login IN {orgs: Array(String)}
53+
AND ({repo: String} = '' OR workflow.repository.name = {repo: String})
5554
AND job.status = 'queued'
5655
/* These two conditions are workarounds for GitHub's broken API. Sometimes */
5756
/* jobs get stuck in a permanently "queued" state but definitely ran. We can */

torchci/lib/clickhouse.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,15 @@ export async function queryClickhouseSaved(
8484
`${process.cwd()}/clickhouse_queries/${queryName}/query.sql`,
8585
"utf8"
8686
);
87-
let paramsText =
88-
require(`clickhouse_queries/${queryName}/params.json`).params;
89-
if (paramsText === undefined) {
90-
paramsText = {};
91-
}
87+
const paramsJson = require(`clickhouse_queries/${queryName}/params.json`);
88+
const paramsText = paramsJson.params ?? {};
89+
const defaults: Record<string, unknown> = paramsJson.defaults ?? {};
9290

9391
const queryParams = new Map(
94-
Object.entries(paramsText).map(([key, _]) => [key, inputParams[key]])
92+
Object.entries(paramsText).map(([key, _]) => [
93+
key,
94+
inputParams[key] !== undefined ? inputParams[key] : defaults[key],
95+
])
9596
);
9697
return await thisModule.queryClickhouse(
9798
query,

0 commit comments

Comments
 (0)