Files
junhong_cmp_fiber/openspec/changes/fix-main-package-activation-starvation/proposal.md
break a0de08d789
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m6s
避免套餐过期后排队权益永久失联
线上保持现有纯 Asynq 架构,以公平孤儿扫描和提交后投递消除永久饥饿及事务可见性竞态。

Constraint: 线上保持现有纯 Asynq 架构,不引入 Outbox、迁移或新任务基础设施。

Rejected: 事务内投递或扩大扫描 LIMIT | 无法消除竞态和永久饥饿。

Confidence: high

Scope-risk: narrow

Directive: 后续分支整合时按目标分支的套餐接续架构独立处理,不混用本热修实现。

Tested: go build ./...(退出码 0);git diff --check;openspec validate fix-main-package-activation-starvation --strict。

Not-tested: 按用户要求未新增、修改或运行自动化测试;线上 SQL、查询计划和日志待部署后核验。
2026-08-03 09:58:05 +08:00

2.4 KiB
Raw Blame History

Why

功能 IDhotfix-main-package-activation-recovery

线上已第二次出现原主套餐成功过期、队首待生效套餐仍长期停留在 status=0 的故障。只读诊断确认当前孤儿扫描固定取出的 100 条记录全部仍有占位套餐,真实孤儿永远无法进入恢复窗口;同时,过期事务提交前投递 Asynq 会让消费者读到旧套餐仍为生效中并把跳过误判为成功。

What Changes

  • PostgreSQL 在 LIMIT 100 前完成每个载体队首选择和 status IN (1,2) 占位排除,删除逐条检查的 N+1 查询。
  • 旧主套餐过期和加油包失效事务提交成功后,才查询队首套餐并投递现有 package:queue:activation Asynq 任务。
  • Redis 激活锁冲突返回现有套餐激活冲突错误,让 Asynq 按既有策略重试,不再确认假成功。
  • Asynq Handler 仅在实际推进套餐或确认已完成幂等事实时记录成功;占位阻塞、等待实名和锁冲突记录明确原因。
  • 不新增 Outbox、数据库迁移、依赖或新任务类型保持线上现有纯 Asynq 架构。
  • 按用户明确要求,不新增、修改或运行自动化测试;使用全量构建、只读 SQL、查询计划和日志核验。

Capabilities

New Capabilities

无。

Modified Capabilities

  • package-queue-activation:补充纯 Asynq 过期接续的提交后投递、真实孤儿公平扫描、锁冲突重试和准确成功语义。

Impact

  • 适用范围:仅当前 main 线上代码。
  • 架构通道:旧套餐轮询复杂写用例,沿用 Polling Handler → GORM transaction → Asynq → Package Activation Service;不迁移未触碰的套餐模块。
  • 代码internal/polling/package_activation_handler.gointernal/service/package/activation_service.go
  • 数据库:无迁移;仅调整 tb_package_usage 查询顺序与过滤。
  • API/前端:无改动。
  • 依赖:继续使用 GORM、PostgreSQL、Redis、Asynq 和 Zap不新增依赖。
  • 性能:孤儿扫描由最多 101 次查询收敛为一次候选查询和有限任务投递;使用查询计划确认数据库耗时。
  • 审计与可靠性Audit Event、Domain Ledger、Integration Log、Outbox 均为 N/Atb_package_usage 是权威状态,现有 Asynq + 周期孤儿扫描负责最终恢复。
  • 验证:不写自动化测试;执行 gofmt、静态检查、go build ./...、只读 SQL及日志核验。