mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): reconcile legacy shadow runs on startup
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
- 决策者:QingLong Maintainers
|
||||
- 关联 RFC:[QL-RFC-0001](../QINGLONG_3_0_ARCHITECTURE_RFC.md)
|
||||
- 前置决策:[ADR-0001](./ADR-0001-run-state-and-transaction-boundaries.md)
|
||||
- Amended by:[ADR-0445](./ADR-0445-schedule-service-origin-shadow-run-coverage.md)、[ADR-0446](./ADR-0446-system-crond-stable-shadow-admission.md)、[ADR-0447](./ADR-0447-boot-shadow-and-non-origin-boundaries.md)
|
||||
- Amended by:[ADR-0445](./ADR-0445-schedule-service-origin-shadow-run-coverage.md)、[ADR-0446](./ADR-0446-system-crond-stable-shadow-admission.md)、[ADR-0447](./ADR-0447-boot-shadow-and-non-origin-boundaries.md)、[ADR-0448](./ADR-0448-bounded-legacy-shadow-startup-reconciliation.md)
|
||||
|
||||
## 1. 决策摘要
|
||||
|
||||
@@ -239,7 +239,7 @@ Shadow Adapter 不得:
|
||||
|
||||
Shadow 转换仍必须遵守 ADR-0001。无法合法映射时追加 compat.transition_mismatch,不能强行覆盖终态。
|
||||
|
||||
当前 Alpha 切片对 `manual` 与 `scheduled_node` 直接观察 Node ChildProcess 的 spawn、error 和 exit 事件,因此不依赖 Shell callback 才能形成基本终态。下述两级关联已补充 Shell callback、stop/cancel 和乱序/迟到回调,但启动后协调与差异对账仍是进入 Primary 前的门禁,不能由进程内观察能力替代。
|
||||
当前 Alpha 切片对已审 Node worker origin 直接观察同一 ChildProcess 的 spawn、error 和 exit 事件,因此不依赖 Shell callback 才能形成基本终态。下述两级关联已补充 Shell callback、stop/cancel 和乱序/迟到回调;ADR-0448 又补充了监听前一次性启动恢复。差异对账、可采集指标与 Primary gate 仍不能由进程内观察或启动 summary 替代。
|
||||
|
||||
### 9.4 `next` Alpha callback 与 stop 关联
|
||||
|
||||
@@ -253,7 +253,7 @@ Shadow 转换仍必须遵守 ADR-0001。无法合法映射时追加 compat.trans
|
||||
6. 取消事实在 Legacy kill 前投递;同 worker 的后续 exit 排在取消之后。跨 worker 使用持久化定位器尽力关联,任何查询或写入失败都不能阻断 kill 或改变 2.x API 响应。
|
||||
7. 乱序 finished 可以从 queued/claimed 补齐 dispatching、starting、running 和终态;重复终态 callback、取消后的迟到成功 callback 不覆盖终态,也不追加重复完成事件。
|
||||
|
||||
这仍不是完整 Reconciler:当前没有启动后批量扫描、缺失事实修复、公开指标采集器或 Shadow/Legacy 差异报表。上述能力必须在 Primary 门禁前补齐,但 edge 默认不得因此增加常驻 watcher 或无界内存队列。
|
||||
这仍不是完整的 Shadow→Primary 门禁:ADR-0448 已提供启动后有界批量扫描、终态证据补齐、lost/abandoned 收敛和两事务 response-loss 修复;当前仍没有公开指标采集器、Shadow/Legacy 差异报表与正式 Primary gate。后续能力不得让 edge 增加常驻 watcher 或无界内存队列。
|
||||
|
||||
### 9.3 Shadow 写失败
|
||||
|
||||
@@ -445,11 +445,13 @@ Run 使用创建时的 task revision/snapshot。更新 Crontab 只影响后续 R
|
||||
|
||||
### 15.1 off/shadow
|
||||
|
||||
Legacy 启动行为暂时保持。Shadow Reconciler 在有界批次内扫描非终态 Shadow Run:
|
||||
Legacy 启动归一行为保持。HTTP worker 在归一之后、Primary activation 与 listen 之前运行一次 Shadow Reconciler:
|
||||
|
||||
- 根据唯一 RunningInstance/PID/log path 证据更新。
|
||||
- 无法确认时标记 Attempt lost 或写 mismatch,不把 Legacy 任务强制改状态。
|
||||
- edge Profile 默认低频、有限批次运行,禁止全表高频扫描。
|
||||
- 使用 `(created_at_ms, run_id)` keyset,只扫描 enabled origin 的 queued/dispatching/running legacy-owned Run;单页最多 64。
|
||||
- 每个 Cron 最多读取 8 条 RunningInstance,只有唯一 PID/log/实例终态证据才补齐 succeeded/failed/cancelled;冲突与截断保持 ambiguous。
|
||||
- Node worker-owned dispatching/running 在 owner 重启且无终态证据时标记 lost;queued/claimed 收敛为 abandoned cancellation。
|
||||
- scheduled_system 无终态证据时保持 pending,等待稳定 execution ID callback,不把 HTTP worker 生命周期误当成 system crond 生命周期。
|
||||
- edge 每次启动最多 `8 × 1 page`,standalone 最多 `32 × 4 pages`,不启动 timer/watcher;cluster-control/worker 拒绝本机 SQLite 装配。
|
||||
|
||||
### 15.2 primary
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# ADR-0448:有界 Legacy Shadow 启动恢复
|
||||
|
||||
- 状态:Accepted
|
||||
- 日期:2026-08-18
|
||||
- 关联 RFC:QL-RFC-0001 D-02、D-356、PR-4
|
||||
- 关联 ADR:ADR-0001、ADR-0002、ADR-0445、ADR-0446、ADR-0447
|
||||
- Amends:ADR-0002 的 Shadow 启动与恢复边界
|
||||
|
||||
## 上下文
|
||||
|
||||
Legacy Shadow 已能旁路观察同一 ChildProcess、关联跨 worker callback/stop,并为 system crond 提供 response-loss-safe execution ID;但 HTTP worker
|
||||
重启后,原 worker 的内存注册表和 ChildProcess listener 都已消失。数据库可能留下 queued/dispatching/running 的 legacy-owned Run,而 Legacy
|
||||
`initData` 又会在 HTTP 启动期间先把旧 RunningInstance 从 running 归一为 stopped。
|
||||
|
||||
因此不能照搬 Primary Reconciler:Legacy Attempt 没有可复验的 durable Executor handle,PID 可能重用,system crond 还可能独立于 HTTP worker
|
||||
继续运行。仅用 PID 存活检查会伪造 owner 连续性;把所有非终态 Run 一律 lost,又会提前终结仍可通过稳定 callback 收敛的 system crond。
|
||||
|
||||
部署范围同时覆盖低性能路由设备、standalone 和 cluster 节点。启动恢复不得引入常驻 watcher、第二个 SQLite authority 或对 cluster profile 的本机
|
||||
数据库误装配。
|
||||
|
||||
## 决策
|
||||
|
||||
1. 只有 `QL3_SHADOW_ORIGINS` 至少显式启用一个已审 origin 时,HTTP worker 才加载恢复 Source、Run Repository、Writer 和 Reconciler。默认 off
|
||||
不导入重型 adapter、不查询数据库,也不创建 timer/listener。
|
||||
2. 恢复发生在 Legacy `initData` 完成 RunningInstance 状态归一之后、manual Primary activation 与 HTTP listen 之前。这样启动期间没有新的 HTTP
|
||||
manual admission 与恢复扫描竞争,Primary 也不会先于 Shadow 遗留事实审计激活。
|
||||
3. 新的只读 Source 使用 `(created_at_ms, run_id)` keyset 分页,只扫描 enabled origin、legacy owner 且状态为 queued/dispatching/running 的
|
||||
Run。单页硬上限 64;每个 Cron 的 RunningInstance 证据硬上限 8,超限、重复身份或冲突一律 ambiguous,不猜测更新。
|
||||
4. Source 只把 RunningInstance 的 log path 转换为现有 36 字符 opaque log artifact ID;原始路径、command、用户名、Run ID 和 Cron ID 不进入
|
||||
audit message 或指标 label。
|
||||
5. 状态裁决如下:
|
||||
- 唯一 PID/log/唯一实例证据已经 finished/error:复用 Shadow Writer 补齐 succeeded/failed;
|
||||
- 唯一 stopped 且带 finished time:补齐 cancelled,reason 为 reconcile;
|
||||
- queued + claimed 且没有 spawn 证据:以 reconciler actor 收敛为 cancelled,并记录 acceptance abandoned;
|
||||
- dispatching/running 的 Node worker-owned origin 在重启后没有终态证据:Attempt/Run 收敛为 lost;
|
||||
- scheduled_system 没有终态证据:保持 pending,等待稳定 execution ID callback,不能因 HTTP worker 重启提前终结外部 crond;
|
||||
- 多 Attempt、证据截断、身份冲突或非法状态:只计 ambiguous/failed,不覆盖 Legacy UI 状态。
|
||||
6. lost/abandoned 的 Attempt 与 Run 使用两个既有原子命令事务推进。若 Attempt 已提交而 Run 响应丢失或进程退出,下次启动会从唯一 terminal Attempt
|
||||
修复 active Run;不产生第二个 Attempt,也不重放外部副作用。
|
||||
7. Reconciler 每次 HTTP 启动只运行一次:edge 为 `8 × 1 page`,standalone 为 `32 × 4 pages`。页预算耗尽返回稳定 resume cursor 和
|
||||
`remaining=true`,不在进程内排队、不自动循环;后续差异报表和正式 Primary gate 必须把 remaining/ambiguous/failed 视为未闭合证据。
|
||||
8. `cluster-control` 与 `worker` profile 拒绝本机 Legacy Shadow 恢复装配。它们未来必须使用 PostgreSQL/shared authority 的独立 Reconciler,不能复用
|
||||
Legacy SQLite。
|
||||
9. Source、写入、配置或 audit sink 失败均保持 Shadow fail-open,只输出低敏 error type/有界 summary,不阻止 2.x HTTP 服务启动。
|
||||
|
||||
## 资源与部署影响
|
||||
|
||||
- 不新增 package、生产依赖、schema、migration、表、索引、进程、线程、端口、timer、watcher 或 Kubernetes 对象。
|
||||
- edge 每次启动最多扫描 8 个 Run,每个带 Cron identity 的候选最多读取 8 条 RunningInstance;standalone 最多扫描 128 个 Run。
|
||||
- 查询使用现有 Runs/RunAttempts 索引与 keyset,不用 OFFSET、不全表加载 Attempt,也不把原始日志路径带出 adapter。
|
||||
- 默认 off 和 cluster/worker profile 都是零 Repository、零恢复查询、零写入;只有显式 Shadow 的本机 profile 承担一次性启动成本。
|
||||
|
||||
## 被拒绝的替代方案
|
||||
|
||||
### 复用 Primary Startup Reconciler
|
||||
|
||||
拒绝。Primary 依赖 durable Executor handle、receipt journal 和 runtime owner;Legacy Shadow 不具备这些证据,复用会把 PID 猜测伪装成精确身份。
|
||||
|
||||
### 启动后定时全表扫描
|
||||
|
||||
拒绝。它会给路由设备增加常驻 timer、重复数据库唤醒和不可控历史扫描,也与当前一次性启动门边界不符。
|
||||
|
||||
### 所有旧 active Run 一律 lost
|
||||
|
||||
拒绝。system crond 独立于 HTTP worker,并可用稳定 execution ID 在重启后补发终态 callback;提前 lost 会丢弃更强事实。
|
||||
|
||||
### 在 Source 中复用原始 log path 作为跨层 identity
|
||||
|
||||
拒绝。原始路径可能暴露任务结构或用户信息;adapter 内必须先转换为现有 opaque artifact ID。
|
||||
|
||||
### 在 cluster profile 打开同一 SQLite 恢复器
|
||||
|
||||
拒绝。多节点会形成多个本机 authority,既无法看到共享事实,也可能产生冲突终态。
|
||||
|
||||
## 验证
|
||||
|
||||
- 真实 SQLite 覆盖唯一 RunningInstance 成功终态、startup-reset lost、spawn 前 abandoned、显式 stopped、system-crond pending、重复身份拒绝、
|
||||
terminal Attempt response-loss 修复和稳定 keyset 分页。
|
||||
- Bootstrap 合同覆盖默认关闭零 execute、cluster/worker 拒绝、edge/standalone 独立预算、低敏失败开放,以及
|
||||
`Legacy normalization → Shadow recovery → Primary activation → HTTP listen` 顺序。
|
||||
- 聚焦测试 `15/15`;Legacy/Shadow 扩展 `63/63`,Legacy 身份专项 `8/8`,两者串行组合 `71/71`;完整 backend
|
||||
`1,434 pass / 0 fail / 2 conditional skip`;18 个 QL3 package 均完成 clean build/test,`build:back` 通过。
|
||||
- 14/14 static audit 与 14/14 artifact audit 通过。edge/standalone 的 base、adopted、application、application-api、AI、
|
||||
application+AI、MCP 产物分别为 `2,589,998 / 2,590,076`、`2,809,293 / 2,809,416`、
|
||||
`3,632,877 / 3,632,997`、`3,800,430 / 3,800,574`、`3,069,251 / 3,069,341`、
|
||||
`4,493,151 / 4,493,283`、`7,315,930 / 7,316,038` bytes,与前一阶段一致。
|
||||
- 本切片不改 schema/migration、数据库连接拓扑、容器或 Kubernetes 资源,未重跑物理 PostgreSQL HA/K3s 门;对象存储
|
||||
backup/WAL/restore/PITR 与 cert-manager mTLS 轮换继续保留为发布最终化现场证据门。
|
||||
@@ -451,6 +451,7 @@
|
||||
| [ADR-0445](./ADR-0445-schedule-service-origin-shadow-run-coverage.md) | ScheduleService 执行来源的 Shadow Run 覆盖 | Accepted(`scheduled_system` 后续由 ADR-0446 完成) |
|
||||
| [ADR-0446](./ADR-0446-system-crond-stable-shadow-admission.md) | System Crond 稳定 Shadow 准入与回调重放 | Accepted |
|
||||
| [ADR-0447](./ADR-0447-boot-shadow-and-non-origin-boundaries.md) | Boot Shadow 准入与 once/gRPC 非 Origin 边界 | Accepted |
|
||||
| [ADR-0448](./ADR-0448-bounded-legacy-shadow-startup-reconciliation.md) | 有界 Legacy Shadow 启动恢复 | Accepted |
|
||||
|
||||
## 规则
|
||||
|
||||
|
||||
Reference in New Issue
Block a user