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、查询计划和日志待部署后核验。
37 lines
2.4 KiB
Markdown
37 lines
2.4 KiB
Markdown
## Why
|
||
|
||
功能 ID:`hotfix-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.go`、`internal/service/package/activation_service.go`。
|
||
- **数据库**:无迁移;仅调整 `tb_package_usage` 查询顺序与过滤。
|
||
- **API/前端**:无改动。
|
||
- **依赖**:继续使用 GORM、PostgreSQL、Redis、Asynq 和 Zap,不新增依赖。
|
||
- **性能**:孤儿扫描由最多 101 次查询收敛为一次候选查询和有限任务投递;使用查询计划确认数据库耗时。
|
||
- **审计与可靠性**:Audit Event、Domain Ledger、Integration Log、Outbox 均为 N/A;`tb_package_usage` 是权威状态,现有 Asynq + 周期孤儿扫描负责最终恢复。
|
||
- **验证**:不写自动化测试;执行 `gofmt`、静态检查、`go build ./...`、只读 SQL及日志核验。
|