Files
UHD_Media_Tool/docs/runbook/ops.md
T
rootandClaude Opus 5.5 e8745115f4 feat(system): process heartbeat + /system/health, engine runs business fields (W9-I)
- 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]>
2026-09-28 15:23:37 +08:00

90 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 进程与健康
控制台「运行日志 → 进程与健康」的数据来源。后端在 `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>`,容器重启覆盖同一行。
- 写入方每小时裁剪一次:采样保留 7 天,7 天没心跳的进程行删除。
- 写库失败只记 debug 日志,不影响业务。
## GET /system/health 字段
| 字段 | 含义 |
| --- | --- |
| `level` | 各段最差档:`ok` / `warn` / `critical`。取不到数据的段是 `unknown`,不参与汇总 |
| `api` | 处理本次请求的 API 进程:版本、启动时间、运行秒数 |
| `processes[]` | 最近 24 小时有心跳的进程 |
| `processes[].stale` | 超过 45 秒没心跳 |
| `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 就去查容器状态和日志。
- 没有任何存活的 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:
```json
{ "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 表达式):
1. run 输入里的业务 id 键:`agentRunId`、`recordId`、`downloadTaskId`、`acquisitionPlanId`、`subscriptionId`、`acquisitionSlotId`、`feedSourceId`、`siteId`、`accountId`(换成站点)、`decisionId`、`mediaEntityId`。
2. 业务任务行 `task`(`engine_run_id` = run id)的 `subject_type` / `subject_id`。
3. 没有业务 id 的定时任务(`purgeTrash`、`refreshPiTokens`、`cleanupDownloadCache`、`reconcileLibrary`),且幂等键不是手动或重跑。
`triggeredBy` 的推断顺序:
1. Agent 会话看 `agent_run.trigger`。
2. 看 `task.triggered_by`。
3. 幂等键是 `retry:` 或含 `manual` 的记为 `user`。
4. 引擎幂等键能对上 outbox 事件的记为 `event`。
5. 调度器时间槽键记为 `schedule`。
筛选参数:`businessKind`、`businessRefId`,可以一起用。这两个参数对每行都要算一次推断表达式,runs 很多时要带上时间范围。