fix(system): retired heartbeat rows no longer alarm on status page

- 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]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 23:31:21 +08:00
1 parent fa261eb6cd
commit b4c9b1ead2
32 files changed
+747 -91

No files matched your search

+2
View File
@@ -42,6 +42,8 @@
| agent.propose_options | 方案;动作叫"选定",都不要叫"退回" | 选项、提案 |
| scope.proposeFirst | 先看方案再执行 | 只要方案、只给方案、plan_only 模式 |
| 工作流引擎的一次执行 | 运行 | run |
| 正在写心跳的进程(process_heartbeat 当前行) | 当前实例;横幅按角色称呼:API / io worker / Agent worker / 单进程 | 活进程、在线实例 |
| 已退出、容器已重建或被同容器新进程替换的心跳行 | 已下线实例;正常退出的标"已退出" | 历史实例、僵尸进程、死进程 |
## 文案规则
+7 -4
View File
@@ -13,7 +13,8 @@
- 每 15 秒 upsert `process_heartbeat` 一行,同时往 `process_heartbeat_sample` 追加一条采样。
- 行主键 `instance_id = <kind>:<host>:<pid>`,容器重启覆盖同一行。
- 写入方每小时裁剪一次:采样保留 7 天,7 天没心跳的进程行删除。
- 正常退出(SIGTERM / SIGINT)时写入器把本行标 `stopped_at`,读取方立即归入已下线实例;重启后同一行的 upsert 把它清回 null。
- 裁剪由定时任务 `system.heartbeat_cleanup`(维护组,1 小时)负责:24 小时没心跳的进程行删除,采样保留 7 天。
- 写库失败只记 debug 日志,不影响业务。
## GET /system/health 字段
@@ -22,8 +23,10 @@
| --- | --- |
| `level` | 各段最差档:`ok` / `warn` / `critical`。取不到数据的段是 `unknown`,不参与汇总 |
| `api` | 处理本次请求的 API 进程:版本、启动时间、运行秒数 |
| `processes[]` | 最近 24 小时有心跳的进程 |
| `processes[].stale` | 超过 45 秒没心跳 |
| `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 进程为 `{}` |
@@ -48,7 +51,7 @@
## 告警阈值建议
- 进程 `stale`:warn。连续 2 分钟仍 stale 就去查容器状态和日志。
- 当前实例 `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。
+1
View File
@@ -30,6 +30,7 @@
| `trash.purge` | 回收站清理 | 维护 | 24 小时 | 入队 `purgeTrash` |
| `pi.token_refresh` | 上游登录刷新 | 维护 | 10 分钟 | 入队 `refreshPiTokens`,幂等键 `refreshPiTokens:<时间槽>`;对 30 分钟内过期的订阅登录上游刷新 token |
| `auth.cleanup` | 登录会话清理 | 维护 | 1 小时 | 直接执行 `cleanupAuthTables`:删 7 天前的登录尝试、30 天前过期或吊销的会话 |
| `system.heartbeat_cleanup` | 进程心跳清理 | 维护 | 1 小时 | 删 24 小时没有心跳的进程行、7 天前的心跳采样 |
| `maintain.backup` | 备份 | 维护 | 每天 03:30(占位) | 未配置。备份目前由宿主机上的 `scripts/backup.sh` 执行 |
| `check.pending` | 待处理巡检 | Agent | 6 小时,默认关闭 | 只读。有未关闭事项时才会运行 |
| `check.downloads` | 下载诊断 | Agent | 2 小时,默认关闭 | 默认只读。打开 `allowRecover` 后,在授权范围内重试或重新校验 |