Skip to content

finding(objectql): lifecycle 的 settings 覆盖窗口没有下限校验 —— 运维把 maxAge 调到某个消费方的假设之下时,后果是静默的(以 sys_job_queue 去重窗为例) #5195

Description

@os-zhuang

观察类 finding,来自 #5179 的实现(PR #5192)。默认部署撞不到,只有运维主动下调覆盖窗口才会发生。

事实(origin/main + #5192)

  • ADR-0057 P4 允许经 lifecycle settings 命名空间按环境/租户覆盖任一对象的 retention.maxAge(lifecycle-service.ts:670effectiveWindowMs:覆盖值胜过声明值,只在解析失败时回落到声明值);
  • 也就是说覆盖只有「能不能解析」这一道校验,没有任何下限;
  • fix(service-queue,platform-objects): sys_job_queue 的 completed 行按声明式 retention 到期即清(#5179) #5192DbQueueAdapter 的去重契约建立在 sys_job_queue 声明的保留窗上(去重按 created_atidempotencyWindowMs,reaper 按同一根轴切),并在构造时强制 idempotencyWindowMs ≤ 声明保留窗;
  • 但构造时读的是对象声明,读不到 settings 覆盖。运维把 lifecycle.overrides.sys_job_queue.maxAge 设成 '1h',则 completed 行 1 小时就被清,而 publish 仍以为 24 小时内的重复会被挡 —— 窗口内的重复 publish 被重新接受,日志里一行都没有
  • 这个形状不是队列独有:任何「消费方对保留窗有下限假设」的表都同理(fix(service-queue,platform-objects): sys_job_queue 的 completed 行按声明式 retention 到期即清(#5179) #5192 的 README 只能写一句「Keep it ≥ your idempotency window」提醒人)。

可能的形状(供定夺,别当成 #5179 的搭车)

  1. 声明侧加 retention.minAge / 或让 LifecycleService 在应用覆盖时 clamp 到声明值的某个下限,越界就 warn 并拒绝该覆盖(声明 = 契约,覆盖只能在契约允许的范围内动);
  2. 消费方侧:给需要下限的对象注册一个「窗口消费者」登记,sweep 时校验;
  3. 什么都不做,只在文档里写清楚 —— 但这正是 service-queue: completed 任务行无人清理 —— purge() 零生产调用方、sys_job_queue 未声明 retention,队列表只增不减 #5179 反对的那种「静默」。

倾向 1:与「declared = enforced」一致,且把错误挡在配置时而不是几天后的重复投递上。

Found-during: #5179 / PR #5192

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions