# 功能决策记录(FDR):任务四象限自动平移(懒触发) ## 1. 基本信息 - 记录编号:FDR-2026-03-TASK-URGENCY-PROMOTE - 功能名称:任务四象限自动平移(不紧急 -> 紧急) - 记录日期:2026-03-16 - 决策状态:已采纳 - 负责人:项目协作实现(你 + Codex) - 关联模块: - `backend/service/task.go` - `backend/service/events/task_urgency_promote.go` - `backend/dao/task.go` - `backend/infra/outbox/*` - `backend/middleware/cache_deleter.go` ## 2. 背景与问题 - 业务背景:任务在创建时属于某个四象限,随着时间推进,部分“不紧急”任务应自动进入“紧急”象限。 - 关键约束: - 不引入全局定时任务(避免资源常驻扫描)。 - 已有缓存删除机制依赖 `cache_deleter`(GORM 写库后自动删缓存)。 - 已有 outbox 总线可承载异步更新。 - 原始问题: - 如果“读时直接改库 + 删缓存”,会引入写放大与重复触发; - 如果“只靠 outbox 异步更新”,在消费延迟窗口内二次访问可能读到旧缓存; - 需要在一致性与性能之间取平衡,且用户体验不能差。 ## 3. 决策目标 - 目标 1:用户访问任务列表时,优先看到“符合当前时间语义”的象限结果。 - 目标 2:真实数据修正异步执行,避免把写库延迟绑定到读接口。 - 目标 3:避免重复投递与重复更新,控制总线与数据库压力。 - 非目标: - 本次不做定时批处理平台; - 本次不做分布式多级缓存一致性框架; - 本次不做任务系统分库分表改造。 ## 4. 备选方案 ### 方案 A:定时任务全量扫描并更新 - 描述:后台周期扫描 `tasks`,到线即批量更新优先级。 - 优点:数据收敛快、语义直观。 - 缺点:常驻资源开销高;扫描窗口与峰值冲突风险高;运维复杂度增加。 - 成本:中高(需要调度、监控、限流与容错)。 ### 方案 B:读时同步改库(请求内直接 UPDATE) - 描述:用户读任务列表时,检测到到线任务后立即写库并删缓存。 - 优点:实现简单,数据立刻落地。 - 缺点:读接口变“读+写”;高并发时放大锁竞争;延迟直接转嫁给用户请求。 - 成本:中(实现快,但后续稳定性成本高)。 ### 方案 C(采纳):读时派生 + SETNX 去重 + outbox 异步落库 - 描述: - 读请求先按 `urgency_threshold_at` 做“派生展示”(仅本次响应生效); - 对需要平移的任务做 Redis `SETNX` 去重; - 去重成功后发布 outbox 事件,消费者异步条件更新 DB; - DB 更新后由 `cache_deleter` 自动删缓存,完成最终收敛。 - 优点: - 用户先看到正确业务语义,不被异步延迟影响体感; - 读链路不阻塞写库; - 复用现有 outbox 与 cache_deleter,侵入小、可扩展。 - 缺点: - 派生视图与真实落库存在短暂时间差; - 需要维护去重键 TTL 与消费可观测性。 - 成本:中(实现复杂度适中,长期收益更高)。 ## 5. 最终决策 - 采纳方案:方案 C(读时派生 + 异步收敛)。 - 关键理由: - 最符合“性能优先 + 最终一致”目标; - 与现有 outbox、缓存失效机制天然兼容; - 面向后续通用事件总线扩展(更多异步业务)更友好。 ## 6. 影响范围 - 涉及模块: - `TaskService.GetUserTasks`:新增读时派生与异步触发。 - `TaskDAO.PromoteTaskUrgencyByIDs`:新增幂等条件更新。 - `service/events`:新增任务平移事件发布与消费注册。 - `cmd/start.go`:新增 handler 注册与 service 注入。 - 数据与存储影响: - `tasks` 新增字段:`urgency_threshold_at`。 - 新增复合索引:`(user_id, is_completed, urgency_threshold_at, priority)`。 - outbox 新增事件类型:`task.urgency.promote.requested`。 - 接口影响: - 对外 API 协议不变; - 行为层面变为“读时先派生展示、随后异步收敛”。 - 监控与日志影响: - 新增任务平移事件发布/消费日志; - 可按 `event_type` 维度观测积压与重试。 ## 7. 风险与应对 - 风险 1:outbox 消费延迟导致短时间内 DB 仍是旧值。 - 应对策略:读时派生保证用户看到新语义;最终由异步消费收敛。 - 风险 2:同一任务被多次重复投递。 - 应对策略:`SETNX + TTL` 去重;DAO 层条件更新保证幂等。 - 风险 3:缓存一致性混乱(误删/抖动)。 - 应对策略:不在读链路主动删缓存;只在真实写库后由 `cache_deleter` 自动失效。 ## 8. 验证与回滚 - 验证方式: - 用到线前后样例任务验证 `GetUserTasks` 返回象限变化; - 验证 outbox 事件是否成功入队、消费并更新 DB; - 验证 DB 更新后缓存是否由 `cache_deleter` 自动清理。 - 成功判定标准: - 用户可在到线后第一时间看到“已平移”结果; - DB 在异步窗口后收敛为相同状态; - 无明显重复投递和异常写放大。 - 回滚方案: - 关闭任务平移事件发布(保留读时派生)可快速止损; - 或回退到“只读不平移”行为(临时关闭派生逻辑)。 ## 9. 里程碑与后续计划 - 里程碑 1:完成懒触发主链路与事件消费者落地(已完成)。 - 里程碑 2:完成联调与基本回归(已完成)。 - 后续优化项: - 增加按 `event_type=task.urgency.promote.requested` 的积压监控面板; - 根据线上情况调优去重 TTL; - 引入批量聚合发布(按用户或时间窗口)进一步降事件量。 ## 10. 复盘结论(上线后补充) - 实际效果:待补充。 - 与预期偏差:待补充。 - 是否需要二次决策:待补充。