- process_heartbeat / process_heartbeat_sample (0034): api / worker / worker:agent / all
upsert every 15s (event loop lag last/max, concurrency, running runs, SSE connections, memory)
- GET /system/health (admin): processes with 45s stale flag, SSE, DB latency/size/connections,
disks (DATA_DIR / library roots / download dirs, cache thresholds), mount sentinel checks
with IO timeout, engine pending/oldest/24h failure rate, schedules
- GET /system/health/history?hours=: bucketed lag / running / SSE series
- /engine/runs and /engine/runs/{id}: business {kind, refId, title, triggeredBy} joined from
run input / task / agent_run / tasks_run_meta / outbox; businessKind / businessRefId filters
- docs/runbook/ops.md: field meanings and alert thresholds
Co-Authored-By: Claude Opus 5.5 <[email protected]>
55 lines
2.2 KiB
TypeScript
55 lines
2.2 KiB
TypeScript
import type { Kysely } from "kysely";
|
|
import { dropTables, execDdl, oneOf } from "../src/migration-helpers.ts";
|
|
|
|
// W9-I 进程与健康:各常驻进程(api / worker / worker:agent / all)每 15s upsert 一行心跳,
|
|
// 同时追加一条采样到 process_heartbeat_sample 供 GET /system/health/history 画图(保留 7 天,写入方顺手裁剪)。
|
|
// instance_id = <kind>:<host>:<pid>:同一容器重启覆盖旧行,不会越积越多;超过 45s 没心跳由读取方标 stale。
|
|
// 另给 task.engine_run_id 补索引:引擎 runs 列表按业务字段筛选时按 run id 反查业务任务行。
|
|
|
|
// biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型
|
|
export async function up(db: Kysely<any>): Promise<void> {
|
|
await execDdl(
|
|
db,
|
|
`
|
|
create table process_heartbeat (
|
|
instance_id text primary key,
|
|
process_kind text not null ${oneOf("process_kind", ["api", "worker", "worker:agent", "all"])},
|
|
pid integer not null,
|
|
host text not null,
|
|
version text not null,
|
|
started_at timestamptz not null,
|
|
last_beat_at timestamptz not null default now(),
|
|
event_loop_lag_ms integer not null default 0 check (event_loop_lag_ms >= 0),
|
|
event_loop_lag_max_ms integer not null default 0 check (event_loop_lag_max_ms >= 0),
|
|
concurrency jsonb not null default '{}'::jsonb,
|
|
running_runs integer check (running_runs is null or running_runs >= 0),
|
|
extra jsonb not null default '{}'::jsonb
|
|
);
|
|
create index process_heartbeat_beat_idx on process_heartbeat (last_beat_at);
|
|
|
|
create table process_heartbeat_sample (
|
|
id bigserial primary key,
|
|
instance_id text not null,
|
|
process_kind text not null,
|
|
at timestamptz not null default now(),
|
|
event_loop_lag_ms integer not null default 0,
|
|
event_loop_lag_max_ms integer not null default 0,
|
|
running_runs integer,
|
|
sse_connections integer
|
|
);
|
|
create index process_heartbeat_sample_at_idx on process_heartbeat_sample (at, instance_id);
|
|
|
|
create index if not exists task_engine_run_idx on task (engine_run_id) where engine_run_id is not null;
|
|
`,
|
|
);
|
|
}
|
|
|
|
// biome-ignore lint/suspicious/noExplicitAny: 同上
|
|
export async function down(db: Kysely<any>): Promise<void> {
|
|
await execDdl(
|
|
db,
|
|
`drop index if exists task_engine_run_idx;
|
|
${dropTables(["process_heartbeat_sample", "process_heartbeat"])}`,
|
|
);
|
|
}
|