Skip to content

Commit f307b28

Browse files
committed
feat(clickhouse): paginated concurrency-key ranking query (TRI-12438)
1 parent 395a516 commit f307b28

2 files changed

Lines changed: 74 additions & 0 deletions

File tree

internal-packages/clickhouse/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
getQueueRanking,
4141
getQueueRankingNames,
4242
getQueueRankingCount,
43+
getConcurrencyKeyRanking,
4344
} from "./queueMetrics.js";
4445
import {
4546
getSessionTagsQueryBuilder,
@@ -279,6 +280,7 @@ export class ClickHouse {
279280
ranking: getQueueRanking(this.reader),
280281
rankingNames: getQueueRankingNames(this.reader),
281282
rankingCount: getQueueRankingCount(this.reader),
283+
concurrencyKeyRanking: getConcurrencyKeyRanking(this.reader),
282284
};
283285
}
284286

internal-packages/clickhouse/src/queueMetrics.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,4 +219,76 @@ export function getQueueRankingCount(reader: ClickhouseReader) {
219219
});
220220
}
221221

222+
// --- Per-concurrency-key ranking (the queue detail "Concurrency keys" table) ---
223+
224+
const ConcurrencyKeyRankingParams = z.object({
225+
organizationId: z.string(),
226+
projectId: z.string(),
227+
environmentId: z.string(),
228+
queueName: z.string(),
229+
startTime: z.string(),
230+
endTime: z.string(),
231+
/** Case-insensitive substring filter on the key ('' = no filter). */
232+
nameContains: z.string(),
233+
limit: z.number(),
234+
offset: z.number(),
235+
});
236+
237+
const ConcurrencyKeyRankingRow = z.object({
238+
concurrency_key: z.string(),
239+
started: z.coerce.number(),
240+
peak_backlog: z.coerce.number(),
241+
peak_running: z.coerce.number(),
242+
mean_wait_ms: z.coerce.number(),
243+
ranked_total: z.coerce.number(),
244+
});
245+
246+
// The per-key table (queue_metrics_ck_v1) is activity-bound and its ORDER BY starts with the
247+
// tenant + queue, so filtering to one queue prunes to a contiguous index range — the aggregate
248+
// is bounded by real activity, never by total key cardinality. There is no per-key 5m rollup,
249+
// so this reads the 10s tier directly (the pre-existing LIMIT-50 query did the same).
250+
const CK_RANKING_WHERE = `organization_id = {organizationId: String}
251+
AND project_id = {projectId: String}
252+
AND environment_id = {environmentId: String}
253+
AND queue_name = {queueName: String}
254+
AND bucket_start >= {startTime: DateTime}
255+
AND bucket_start < {endTime: DateTime}
256+
AND ({nameContains: String} = '' OR positionCaseInsensitive(concurrency_key, {nameContains: String}) > 0)`;
257+
258+
/**
259+
* One page of a queue's concurrency keys ranked by peak backlog over the window, with the total
260+
* ranked-key count on every row (window function) so page + count cost a single scan — the same
261+
* shape as getQueueRanking. The `concurrency_key ASC` tiebreak makes OFFSET paging stable across
262+
* keys that share a peak. Range stats (started/peak_backlog/peak_running/mean wait) come back on
263+
* the same rows; live "now" counts are enriched per page from Redis by the caller.
264+
*/
265+
export function getConcurrencyKeyRanking(reader: ClickhouseReader) {
266+
return reader.query({
267+
name: "getConcurrencyKeyRanking",
268+
query: `SELECT
269+
concurrency_key,
270+
started,
271+
peak_backlog,
272+
peak_running,
273+
mean_wait_ms,
274+
count() OVER () AS ranked_total
275+
FROM (
276+
SELECT
277+
concurrency_key,
278+
deltaSumTimestampMerge(started_delta) AS started,
279+
max(max_queued) AS peak_backlog,
280+
max(max_running) AS peak_running,
281+
if(sum(wait_ms_count) > 0, round(sum(wait_ms_sum) / sum(wait_ms_count)), 0) AS mean_wait_ms
282+
FROM trigger_dev.queue_metrics_ck_v1
283+
WHERE ${CK_RANKING_WHERE}
284+
GROUP BY concurrency_key
285+
ORDER BY peak_backlog DESC, concurrency_key ASC
286+
)
287+
LIMIT {limit: UInt32} OFFSET {offset: UInt32}`,
288+
params: ConcurrencyKeyRankingParams,
289+
schema: ConcurrencyKeyRankingRow,
290+
settings: QUEUE_METRICS_CACHE_SETTINGS,
291+
});
292+
}
293+
222294
// (per-queue detail series is now fetched via TRQL + fillGaps from the metric resource route)

0 commit comments

Comments
 (0)