Files
qinglong/docs/adr/ADR-0107-postgresql-authoritative-cluster-scheduler-clock.md

6.5 KiB
Raw Permalink Blame History

ADR-0107PostgreSQL 权威 Cluster Scheduler 时钟

  • 状态:Accepted
  • 日期:2026-07-22
  • 关联 RFCQL-RFC-0001 D-05、D-19、D-53、D-56、D-104、D-106
  • 关联 ADRADR-0054、ADR-0057、ADR-0103、ADR-0105
  • SupersedesADR-0105 中由 Coordinator 提供 claim/decision observation 的局部协议

背景

ADR-0105 已用 owner/token/version/expiry 与 schedule state 建立完整行租约 fence,但最初的 ClaimClusterScheduleCommand 仍接受 control replica 的 observedAtMs。领取 SQL 用该值判断 due、claim expiry takeover 和 updated_at_msCoordinator 又用另一次节点 Date.now() 计算 cron 决策,commit 最后继续用调用方时间判断租约并写 Run/Event。

这会让 Pod 时钟漂移进入数据库 authority:快节点可以提前接管仍有效的 claim 或提前调度 occurrence,慢节点会延迟到期工作;NTP 回拨还可能让同一 replica 写出早于 claim 的状态时间。owner/token/version 只能阻止旧 owner 提交,不能修复由错误 observation 产生的错误 winner。

决策

  1. ClaimClusterScheduleCommand 只接受 ownerId、UUID claimToken 与有界 leaseMs,拒绝 observedAtMs 及任何扩展字段。Coordinator 配置同样拒绝 clock,节点墙上时钟不得成为 scheduler authority。
  2. 领取 SQL 在一个 MATERIALIZED CTE 中精确调用一次 PostgreSQL clock_timestamp(),以该 observation 同时裁决 due cursor、过期 takeover、未来 updated_at_ms,并写 claim_expires_at_ms = observation + leaseMs
  3. 活跃 claim 的既有 trigger_schedules.updated_at_ms 被解释为持久化 claimAcquiredAtMspg-0014 已要求 claim expiry 大于 updated time,因此无需新增列或迁移;公开 claim 必须满足 1–60 秒 lease 差值并把 acquired time 纳入 exact claim 比较。
  4. cron/misfire 决策只能使用 claim 返回的 claimAcquiredAtMsresolveClusterScheduleDecision 不再接受 observation 参数,commit normalization 要求 decision.observedAtMs === claim.claimAcquiredAtMs
  5. commit 的 SERIALIZABLE transaction 在锁定 schedule 的同一 statement 中再次精确采样一次数据库时间。该时间达到 expiry 时只返回 raced;早于 claimAcquiredAtMs 时以数据库时钟回退失败关闭;成功时作为 Run、Attempt、双 Event 与 schedule 的创建/更新时间。
  6. cycle summary 只报告首尾 claimAcquiredAtMs,空轮次为 null。它不再伪造一个节点级 observation,也不改变每轮 16/256、30 秒 lease、非重叠 lifecycle 或 Profile 闭包。

被否决的替代方案

  1. 保留调用方时间并配置 NTP 偏差容忍:不能证明每个 Pod 同步,也会把容忍窗口变成提前 takeover 窗口,拒绝。
  2. 只在 claim 使用数据库时间,commit 继续使用节点时间:慢/快节点仍可错误判断 expiry 或写回倒退时间,拒绝。
  3. 为 acquired time 新增列和 capability migration:活跃 claim 的 updated_at_ms 已被 claim shape 约束为同一 durable acquisition fact;重复列会形成双事实,拒绝。
  4. 使用 transaction timestamp 并跨多个 transaction 复用:领取和提交是两个独立 transaction,必须分别取得实际 statement observation,拒绝。
  5. 改用全局 scheduler leader:时钟问题不会因单 leader 自动消失,并重新引入 ADR-0105 已拒绝的粗粒度故障域,拒绝。

验收证据

  1. runtime-core contract 测试拒绝 caller-timed claim command,并绑定 claimAcquiredAtMs、lease 差值和 decision observation。
  2. PostgreSQL repository 测试证明 claim SQL 使用一次数据库 clock_timestamp() 且参数中没有节点时间;commit 使用第二次数据库 observation 裁决 expiry、时钟回退和所有 durable timestamp。
  3. Coordinator 测试证明配置中的 clock 被拒绝、多个 claim 的数据库时间被保留,且只为 fenced admission 分配 identity/发送通知。
  4. 三个目标包 build 与 scheduler 定向测试 14/14 通过;runtime-core 116/116、cluster-postgres 66/66、cluster-control 73/73 通过,后两个包各有 1 个在未提供 PostgreSQL URL 时显式跳过的 integration。
  5. 23 个 QL3 package 的完整 build/test、依赖闭包审计、edge import 审计全部通过;backend 669/669 通过。
  6. PostgreSQL 16.10 真实实例完成全部 migration,并以最小权限 ql3_runtime role 验证 claim/commit。双角色完整 integration 18/18 通过,仅因未配置 admin/worker-ingress 独立 URL 显式跳过 2 项;CI 已补齐 Task/Trigger/Schedule 对 runtime/admin role 的最小授权。
  7. 当前本机 arm64 PostgreSQL 18 四角色 integration 为 23/23、0 skip;两个独立 runtime pool 的 scheduler integration 又证明同一 occurrence 单赢家和持 claim 副本关闭后的数据库时间 expiry takeover。新增用例只补 active query 连接失效的 availability 报告,不重新引入节点 clock。
  8. ADR-0125 的 physical-promotion 门禁在 primary 数据库时间 1784824222846 取得 15 秒 claimWAL replay 到 standby 后于旧主 fencing 前再次由数据库观察到 14,641 ms 剩余且 occurrence 数为 0timeline 1→2 promotion 的数据库时间为 1784824223677fresh control 最终在 1784824238338 准入,晚于 claim expiry 1784824237846。claim version 1→2、最终清空并只产生 1 Run/1 Attempt/2 Event,证明节点时钟没有参与跨 promotion takeover。
  9. 同一 promotion fixture 在另一个到期 schedule 的 decision transaction 完成 COMMIT 后终止 transaction backend,并让 Coordinator 上层观察 ECONNRESET。该提交在 promotion 前已由 standby WAL replaypromotion 后仍只有 1 Run/1 Attempt/2 Event、claim 清空且 0 duplicate,证明不确定响应没有重新引入节点时间或绕过 durable idempotency fact。该证据不等同 raw-wire packet-loss。

后续约束

数据库权威时钟消除了 Pod 间墙上时钟作为调度 authority;本机 PostgreSQL 18、双独立 pool 与 ADR-0125 的 timeline 1→2 physical promotion 已证明短时多控制面竞争、claim 持有期间的 WAL 连续性、promotion 后数据库 expiry takeover、exact occurrence admission 和 PostgresClient 边界的 decision COMMIT-response-loss 收敛。尚未覆盖 raw-wire packet-loss、跨区域 leap/slew、真实 Kubernetes Pod 网络边界或网络分区安全。其余逐 mutation failover 与多 Pod 长时间竞争仍是发布门禁;测试不得通过重新注入调用方 observedAtMs 来伪造 takeover。