feat(worker): IO Worker 分道(WK-1)
io worker 进程内按任务声明的 lane 分文件 / 网络 / 维护三条道,各自并发(缺省 2/4/1)、 各自排队;维护道在文件道或网络道有排队/运行时让路(连续 10 分钟放行一个),调度器内联的 清理/回填/保留期裁剪也走维护道。并发在 设置 → 自动化 → 高级 热生效,env 覆盖;旧 WORKER_CONCURRENCY 映射到网络道。运行状态页按道显示运行中/并发/排队。归属表写进 docs/runbook/dev.md,ci 与注册表逐项核对。 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
68f53390f6
commit
da33cad81d
38 files changed
+1240
-105
No files matched your search
@@ -1,12 +1,13 @@
|
||||
import { readdir, stat } from "node:fs/promises";
|
||||
import { checkRoot, DEFAULT_ROOT_SENTINEL, type DiskUsage, diskUsage } from "@umt/adapters";
|
||||
import type {
|
||||
HealthLevel,
|
||||
SystemDisk,
|
||||
SystemHealth,
|
||||
SystemHealthHistory,
|
||||
SystemMount,
|
||||
SystemProcess,
|
||||
import {
|
||||
type HealthLevel,
|
||||
type SystemDisk,
|
||||
type SystemHealth,
|
||||
type SystemHealthHistory,
|
||||
type SystemMount,
|
||||
type SystemProcess,
|
||||
SystemWorkerLane,
|
||||
} from "@umt/contracts";
|
||||
import { type DB, type Kysely, type Selectable, sql } from "@umt/db";
|
||||
import { CURRENT_HOST_WINDOW_MS, HEARTBEAT_ROW_RETENTION_MS, HEARTBEAT_STALE_MS } from "./heartbeat.ts";
|
||||
@@ -61,7 +62,13 @@ function toProcess(r: HeartbeatRow, now: Date): SystemProcess {
|
||||
const beat = toDate(r.last_beat_at);
|
||||
const age = Math.max(0, now.getTime() - beat.getTime());
|
||||
const extra = (r.extra ?? {}) as Record<string, unknown>;
|
||||
const conc = (r.concurrency ?? {}) as { io?: number; agent?: number };
|
||||
const conc = (r.concurrency ?? {}) as Record<string, unknown>;
|
||||
const lanes = Array.isArray(extra.lanes)
|
||||
? extra.lanes.flatMap((l) => {
|
||||
const p = SystemWorkerLane.safeParse(l);
|
||||
return p.success ? [p.data] : [];
|
||||
})
|
||||
: [];
|
||||
const sse = extra.sseConnections;
|
||||
return {
|
||||
instanceId: r.instance_id,
|
||||
@@ -76,10 +83,12 @@ function toProcess(r: HeartbeatRow, now: Date): SystemProcess {
|
||||
stoppedAt: r.stopped_at ? toDate(r.stopped_at).toISOString() : null,
|
||||
eventLoopLagMs: r.event_loop_lag_ms,
|
||||
eventLoopLagMaxMs: r.event_loop_lag_max_ms,
|
||||
concurrency: {
|
||||
...(typeof conc.io === "number" ? { io: conc.io } : {}),
|
||||
...(typeof conc.agent === "number" ? { agent: conc.agent } : {}),
|
||||
},
|
||||
concurrency: Object.fromEntries(
|
||||
(["io", "agent", "file", "network", "maintenance"] as const).flatMap((k) =>
|
||||
typeof conc[k] === "number" ? [[k, conc[k]]] : [],
|
||||
),
|
||||
),
|
||||
lanes,
|
||||
runningRuns: r.running_runs,
|
||||
sseConnections: typeof sse === "number" ? sse : null,
|
||||
extra,
|
||||
|
||||
@@ -51,7 +51,8 @@ export function createLagSampler(intervalMs = 1000) {
|
||||
}
|
||||
|
||||
export interface HeartbeatSample {
|
||||
concurrency?: { io?: number; agent?: number };
|
||||
/** io(各道合计)/ agent,以及 io 组的 file / network / maintenance */
|
||||
concurrency?: Partial<Record<"io" | "agent" | "file" | "network" | "maintenance", number>>;
|
||||
runningRuns?: number | null;
|
||||
sseConnections?: number | null;
|
||||
extra?: Record<string, unknown>;
|
||||
|
||||
@@ -185,6 +185,7 @@ export function createSystemModule(opts: SystemModuleOptions = {}): ServerModule
|
||||
for (const j of RETENTION_JOBS) {
|
||||
registerScheduledJob(ctx, {
|
||||
key: j.jobId,
|
||||
lane: "maintenance",
|
||||
defaults: {
|
||||
name: j.name,
|
||||
description: j.description,
|
||||
@@ -208,6 +209,7 @@ export function createSystemModule(opts: SystemModuleOptions = {}): ServerModule
|
||||
if (everyMs <= 0) return;
|
||||
registerScheduledJob(ctx, {
|
||||
key: "system.heartbeat_cleanup",
|
||||
lane: "maintenance",
|
||||
defaults: {
|
||||
name: "进程心跳清理",
|
||||
description: "删除过期的进程状态记录",
|
||||
|
||||
Reference in new issue
Block a user