# 功能决策记录（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. 复盘结论（上线后补充）
- 实际效果：待补充。
- 与预期偏差：待补充。
- 是否需要二次决策：待补充。
