6.5 KiB
ADR-0107:PostgreSQL 权威 Cluster Scheduler 时钟
- 状态:Accepted
- 日期:2026-07-22
- 关联 RFC:QL-RFC-0001 D-05、D-19、D-53、D-56、D-104、D-106
- 关联 ADR:ADR-0054、ADR-0057、ADR-0103、ADR-0105
- Supersedes:ADR-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_ms,Coordinator 又用另一次节点 Date.now() 计算 cron 决策,commit 最后继续用调用方时间判断租约并写 Run/Event。
这会让 Pod 时钟漂移进入数据库 authority:快节点可以提前接管仍有效的 claim 或提前调度 occurrence,慢节点会延迟到期工作;NTP 回拨还可能让同一 replica 写出早于 claim 的状态时间。owner/token/version 只能阻止旧 owner 提交,不能修复由错误 observation 产生的错误 winner。
决策
ClaimClusterScheduleCommand只接受ownerId、UUIDclaimToken与有界leaseMs,拒绝observedAtMs及任何扩展字段。Coordinator 配置同样拒绝clock,节点墙上时钟不得成为 scheduler authority。- 领取 SQL 在一个
MATERIALIZEDCTE 中精确调用一次 PostgreSQLclock_timestamp(),以该 observation 同时裁决 due cursor、过期 takeover、未来updated_at_ms,并写claim_expires_at_ms = observation + leaseMs。 - 活跃 claim 的既有
trigger_schedules.updated_at_ms被解释为持久化claimAcquiredAtMs。pg-0014已要求 claim expiry 大于 updated time,因此无需新增列或迁移;公开 claim 必须满足 1–60 秒 lease 差值并把 acquired time 纳入 exact claim 比较。 - cron/misfire 决策只能使用 claim 返回的
claimAcquiredAtMs。resolveClusterScheduleDecision不再接受 observation 参数,commit normalization 要求decision.observedAtMs === claim.claimAcquiredAtMs。 - commit 的
SERIALIZABLEtransaction 在锁定 schedule 的同一 statement 中再次精确采样一次数据库时间。该时间达到 expiry 时只返回raced;早于claimAcquiredAtMs时以数据库时钟回退失败关闭;成功时作为 Run、Attempt、双 Event 与 schedule 的创建/更新时间。 - cycle summary 只报告首尾
claimAcquiredAtMs,空轮次为null。它不再伪造一个节点级 observation,也不改变每轮 16/256、30 秒 lease、非重叠 lifecycle 或 Profile 闭包。
被否决的替代方案
- 保留调用方时间并配置 NTP 偏差容忍:不能证明每个 Pod 同步,也会把容忍窗口变成提前 takeover 窗口,拒绝。
- 只在 claim 使用数据库时间,commit 继续使用节点时间:慢/快节点仍可错误判断 expiry 或写回倒退时间,拒绝。
- 为 acquired time 新增列和 capability migration:活跃 claim 的
updated_at_ms已被 claim shape 约束为同一 durable acquisition fact;重复列会形成双事实,拒绝。 - 使用 transaction timestamp 并跨多个 transaction 复用:领取和提交是两个独立 transaction,必须分别取得实际 statement observation,拒绝。
- 改用全局 scheduler leader:时钟问题不会因单 leader 自动消失,并重新引入 ADR-0105 已拒绝的粗粒度故障域,拒绝。
验收证据
- runtime-core contract 测试拒绝 caller-timed claim command,并绑定
claimAcquiredAtMs、lease 差值和 decision observation。 - PostgreSQL repository 测试证明 claim SQL 使用一次数据库
clock_timestamp()且参数中没有节点时间;commit 使用第二次数据库 observation 裁决 expiry、时钟回退和所有 durable timestamp。 - Coordinator 测试证明配置中的
clock被拒绝、多个 claim 的数据库时间被保留,且只为 fenced admission 分配 identity/发送通知。 - 三个目标包 build 与 scheduler 定向测试 14/14 通过;runtime-core 116/116、cluster-postgres 66/66、cluster-control 73/73 通过,后两个包各有 1 个在未提供 PostgreSQL URL 时显式跳过的 integration。
- 23 个 QL3 package 的完整 build/test、依赖闭包审计、edge import 审计全部通过;backend 669/669 通过。
- PostgreSQL 16.10 真实实例完成全部 migration,并以最小权限
ql3_runtimerole 验证 claim/commit。双角色完整 integration 18/18 通过,仅因未配置 admin/worker-ingress 独立 URL 显式跳过 2 项;CI 已补齐 Task/Trigger/Schedule 对 runtime/admin role 的最小授权。 - 当前本机 arm64 PostgreSQL 18 四角色 integration 为 23/23、0 skip;两个独立 runtime pool 的 scheduler integration 又证明同一 occurrence 单赢家和持 claim 副本关闭后的数据库时间 expiry takeover。新增用例只补 active query 连接失效的 availability 报告,不重新引入节点 clock。
- ADR-0125 的 physical-promotion 门禁在 primary 数据库时间
1784824222846取得 15 秒 claim,WAL replay 到 standby 后于旧主 fencing 前再次由数据库观察到 14,641 ms 剩余且 occurrence 数为 0;timeline 1→2 promotion 的数据库时间为1784824223677,fresh control 最终在1784824238338准入,晚于 claim expiry1784824237846。claim version 1→2、最终清空并只产生 1 Run/1 Attempt/2 Event,证明节点时钟没有参与跨 promotion takeover。 - 同一 promotion fixture 在另一个到期 schedule 的 decision transaction 完成
COMMIT后终止 transaction backend,并让 Coordinator 上层观察ECONNRESET。该提交在 promotion 前已由 standby WAL replay,promotion 后仍只有 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。