- GET /system/health splits processes into current instances (hosts with
a beat in the last 10 min, newest per host+kind, not stopped) and
retiredProcesses[]; 45s stale and level only consider current ones
- heartbeat writer marks stopped_at on graceful exit (migration 0043),
upsert clears it; pure api role now stops its writer on SIGTERM
- scheduled job system.heartbeat_cleanup (maintain, 1h) deletes rows
without a beat for 24h and samples older than 7 days
- status page: per-role alarms ("io worker 45 秒没有心跳"), collapsible
retired instances; glossary/runbook updated
- bump all members to 0.2.2, CHANGELOG [0.2.2]
Co-Authored-By: Claude Opus 5.5 <[email protected]>
6.3 KiB
6.3 KiB
进程与健康
控制台「运行日志 → 进程与健康」的数据来源。后端在 apps/server/src/modules/system/,两个端点都只给 admin。
心跳怎么写
| 进程 | 谁写 | process_kind |
|---|---|---|
--role api |
system 模块注册路由时起写入器 | api |
--role worker(io 组) |
worker/index.ts |
worker |
--role worker:agent |
worker/index.ts |
worker:agent |
--role all |
worker/index.ts(同进程的 API 不再单写) |
all |
- 每 15 秒 upsert
process_heartbeat一行,同时往process_heartbeat_sample追加一条采样。 - 行主键
instance_id = <kind>:<host>:<pid>,容器重启覆盖同一行。 - 正常退出(SIGTERM / SIGINT)时写入器把本行标
stopped_at,读取方立即归入已下线实例;重启后同一行的 upsert 把它清回 null。 - 裁剪由定时任务
system.heartbeat_cleanup(维护组,1 小时)负责:24 小时没心跳的进程行删除,采样保留 7 天。 - 写库失败只记 debug 日志,不影响业务。
GET /system/health 字段
| 字段 | 含义 |
|---|---|
level |
各段最差档:ok / warn / critical。取不到数据的段是 unknown,不参与汇总 |
api |
处理本次请求的 API 进程:版本、启动时间、运行秒数 |
processes[] |
当前实例:最近 10 分钟内有心跳的 host 上、同 host 同 kind 最新启动且没标 stopped_at 的进程 |
processes[].stale |
超过 45 秒没心跳。只有当前实例参与告警 |
processes[].stoppedAt |
正常退出时间;非 null 即已下线 |
retiredProcesses[] |
已下线实例:正常退出的、所在 host 10 分钟没有心跳(容器已重建)的、被同 host 新进程替换的。不计入 level,页面折叠显示 |
processes[].eventLoopLagMs |
最近一次采样的事件循环延迟(1 秒采一次) |
processes[].eventLoopLagMaxMs |
最近一个心跳周期(15 秒)内的最大延迟 |
processes[].concurrency |
引擎 worker 并发 {io, agent};api 进程为 {} |
processes[].runningRuns |
本组任务正在执行的 run 数。同组多实例时是整组合计;api 进程为 null |
processes[].sseConnections |
本进程打开的 SSE 流数。不托管 HTTP 的进程为 null |
processes[].extra |
rssBytes、heapUsedBytes、lifetimeMaxLagMs,worker 另有 group、lagWarnings、probeMaxLagMs |
sse.connections |
存活 api/all 进程的 SSE 连接合计 |
db |
select 1 延迟、库大小、本库连接数(按 active / idle / idle in transaction 分)与 max_connections |
disks[] |
DATA_DIR、每个启用的库根、每个下载器路径映射的 statfs 用量 |
diskThresholds |
取自设置 cache.resumePercent(warn)和 cache.hardThresholdPercent(critical) |
mounts[] |
库根做哨兵检查(checkRoot),下载目录只查目录可读。status:online / offline / timeout |
engine |
待处理 run 数、执行中 run 数、最老待处理时长、24 小时结束数 / 失败数 / 失败率 |
schedules |
定时任务总数、启用数、执行中数、最近一次失败的启用任务、24 小时失败运行数 |
单个路径的 statfs 或哨兵检查超过 3 秒记 timeout:磁盘段标 critical,挂载段标 timeout。请求本身不会被卡住。
GET /system/health/history?hours=
hours取 1–168,缺省 6。- 按桶聚合,每条序列最多约 360 个点。桶宽最小 15 秒,168 小时时是 1680 秒。
- 每个进程一条序列,每个点有
lagMs(桶内平均)、lagMaxMs(桶内最大)、runningRuns、sseConnections。
告警阈值建议
- 当前实例
stale:warn。连续 2 分钟仍 stale 就去查容器状态和日志。容器整个没了 10 分钟后它的进程转入已下线实例,此时靠下一条"没有存活的 worker"兜底。 - 没有任何存活的 worker / all 进程:warn。任务不会被执行。
- 事件循环最大延迟 ≥ 1 秒记 warn,≥ 5 秒记 critical。延迟接近 15 秒时,引擎租约有丢失风险。
- DB 延迟 ≥ 100 ms 或连接数 ≥ 75%
max_connections记 warn;≥ 500 ms 或 ≥ 90% 记 critical。 idle in transaction连接超过 5 个记 warn,通常是有事务没提交。- 磁盘用量 ≥
cache.resumePercent(缺省 85%)记 warn,≥cache.hardThresholdPercent(缺省 90%)记 critical。 - 挂载根
offline或timeout:critical。先查宿主机挂载,再查哨兵文件。 - 最老待处理 run 等待 ≥ 5 分钟记 warn,≥ 30 分钟记 critical。通常是 worker 没起来或并发被占满。
- 24 小时失败率 ≥ 10% 记 warn;失败率 ≥ 30% 且结束数 ≥ 10 记 critical。
- 有启用的定时任务最近一次运行失败:warn。
引擎 runs 的业务字段
GET /engine/runs 和 GET /engine/runs/{id} 的每个 run 多了 business,推断不出时为 null:
{ "kind": "feed", "refId": "<feed_source.id>", "title": "HDSky RSS", "triggeredBy": "event" }
kind:subscription/download/organization/agent/schedule/feed/site/decision/media。refId:业务对象 id;schedule是定时任务 key(如trash.purge)。download优先指向下载任务,计划还没提交时指向收录计划 id。title:业务对象的展示名。对象已删除时为 null。triggeredBy:user/api_key/schedule/agent/event,推断不出为 null。
kind 与 refId 的推断顺序(auth-settings/engine-business.ts,列表筛选和展示用同一条 SQL 表达式):
- run 输入里的业务 id 键:
agentRunId、recordId、downloadTaskId、acquisitionPlanId、subscriptionId、acquisitionSlotId、feedSourceId、siteId、accountId(换成站点)、decisionId、mediaEntityId。 - 业务任务行
task(engine_run_id= run id)的subject_type/subject_id。 - 没有业务 id 的定时任务(
purgeTrash、refreshPiTokens、cleanupDownloadCache、reconcileLibrary),且幂等键不是手动或重跑。
triggeredBy 的推断顺序:
- Agent 会话看
agent_run.trigger。 - 看
task.triggered_by。 - 幂等键是
retry:或含manual的记为user。 - 引擎幂等键能对上 outbox 事件的记为
event。 - 调度器时间槽键记为
schedule。
筛选参数:businessKind、businessRefId,可以一起用。这两个参数对每行都要算一次推断表达式,runs 很多时要带上时间范围。