97 lines
6.8 KiB
Markdown
97 lines
6.8 KiB
Markdown
# 进程与健康
|
|
|
|
控制台「运行日志 → 进程与健康」的数据来源。后端在 `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 小时没心跳的进程行删除,采样保留
|
|
`storage.retentionDays.heartbeatHours`(缺省 24 小时,CACHE-2 前是 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 小时失败运行数 |
|
|
| `storage` | 存储预算档位与告警文案(PG 库体积、图片缓存、备份、会话与证据对照 `settings.storage.budgets`),明细见 `GET /system/storage`([storage.md](storage.md)) |
|
|
|
|
单个路径的 statfs 或哨兵检查超过 3 秒记 `timeout`:磁盘段标 critical,挂载段标 `timeout`。请求本身不会被卡住。
|
|
|
|
## GET /system/health/history?hours=
|
|
|
|
- `hours` 取 1–168,缺省 6。采样只保留 `heartbeatHours`(缺省 24 小时),更早的部分为空。
|
|
- 按桶聚合,每条序列最多约 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。
|
|
- PG 库体积 ≥ `pgDataWarnGb`(20 GB)记 warn,≥ `pgDataGb`(25 GB)记 critical;备份、会话与证据 ≥ 预算 80% 记 warn、≥ 预算记 critical;
|
|
图片缓存超预算 10% 记 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 很多时要带上时间范围。
|