- Sprint 执行计划 v1.1:§1.1 补第一批交付实据;T9 SPIKE 结论回写 Sprint 1 表 (T-P0-16 通过 2.8ms;T-P0-12 有条件;T-P0-10 原型 6/6 + 真实照片重验前置); Sprint 2 表标注 T-P2-01~06 交付;新增 §9 两批交付记录 - 开发任务拆解 v1.5:交付回写 + 新风险登记(RTMPose 域差异、BiRefNet 异步化) - 新增 开发任务包/:三批多 agent 并行的协作规程与 14 份任务契约 - 新增 前置开通清单-云资源与资质.md - 清理已用毕的评审报告(文档评审 2026-08-13、项目整体评审 2026-08-14), README 中相应引用改为留痕表述
5.1 KiB
5.1 KiB
T14 · 检索结果缓存与发布失效
对应任务:
T-P2-10| 需求:发布后检索缓存自然失效(Sprint 2 验收行)| 分支:task/t14-retrieval-cache| worktree:/home/xsl/meida_backend-t14先读《README.md(协作规程)》,遵守全部纪律。
1. 目标
给 RetrievalService.search 加进程内结果缓存(TTL + 容量上限 + 代数失效),把协调提交里的 invalidate_cache() 桩变成真实现。缓存解决的是"同一 query 高频重复检索"(Agent 多轮、黄金集回归);失效保证知识发布后不吐旧结果。
2. 已就绪的契约(协调提交 9c18021 已提供)
- 配置(config.py 已加,.env.example 已示例):
retrieval_cache_enabled=True、retrieval_cache_ttl_seconds=60、retrieval_cache_max_entries=512。 - 钩子已存在:
packages/retrieval/service.py模块级invalidate_cache()(当前只是repo.INVALIDATION_VERSION += 1)与packages/retrieval/repo.py的INVALIDATION_VERSION = 1——运营后台(T12)已经在调它。你实现真缓存,不改钩子签名。 RetrievalService.__init__(session, *, embedding, weights, top_k)与from_settings(session, settings)——缓存参数经from_settings读 Settings 注入(不改__init__既有签名,加带默认值的 keyword-only 参数)。
3. 实现要点
允许改动:packages/retrieval/cache.py(新建)、packages/retrieval/service.py(缓存接线)、测试文件。不改 repo.py 的既有函数与 INVALIDATION_VERSION 语义。
packages/retrieval/cache.py(纯逻辑,重点单测):CacheKey:(version, query_text, body_shape, occasion, entity_types, rule_boost_hash, top_k)——全部影响结果的输入都必须进键,缺一个就会串结果(rule_boost 是 dict[int,float],排序后序列化再哈希);TtlCache:容量上限用简单 LRU(OrderedDict移动末尾 + 超限 popitem(last=False));TTL 用惰性过期(读时比对时间戳,过期视作 miss);线程安全不需要(asyncio 单循环,无 await 竞态窗口——注释写明这个前提,别加锁);- 指标:
hits/misses/evictions计数器(Prometheus 可后续接,先暴露属性)+stats()方法。
- service.py 接线:
RetrievalService增加cache: TtlCache | None(None = 禁用,行为与现在完全一致);search():cache非 None 且非degraded路径——命中直接返回缓存SearchOutcome;未命中照常检索后只在有结果时写缓存(空结果不缓存:record_gap每次都要跑,否则缺口计数被缓存吞掉);from_settings按 Settings 装配缓存;模块级invalidate_cache():把 service 模块持有的共享缓存实例(或 repo.INVALIDATION_VERSION,二选一,选实现更干净的那个)作废——调用方(admin 发布编排)不需要知道细节。
- 正确性红线(写进 cache.py docstring):缓存的是
SearchOutcome(frozen dataclass,不可变)——绝不缓存可变对象引用;命中路径不得复用 session(缓存命中时 DB 零交互)。
4. 测试要求
- 单测
tests/unit/test_retrieval_cache.py(fake repo/embedding,不连库):- 同参数二连查:第二次零 DB 调用(fake 计数断言)、结果相等;
- 任一键分量变化(query/occasion/body_shape/rule_boost/top_k)→ miss;
- TTL 过期 → miss(注入 clock);
- LRU 容量超限 → 最旧被逐、计数正确;
- 空结果不缓存(degraded 路径每次都触发 gap 记录 fake 调用);
invalidate_cache()后同参数 → miss(代数失效);- cache=None → 行为与现在一致。
- 集成
tests/integration/test_retrieval_cache.py(新文件):- seed 实体 + chunk → search 命中 → 发布新实体(直接调 kb service 或 SQL 置 published)+
invalidate_cache()→ 再 search 能看到新实体(这是 T-P2-10 的验收语义); - 不失效等 TTL 过期(TTL 配 1s,sleep 1.1s)→ 新实体同样可见(双保险)。
- seed 实体 + chunk → search 命中 → 发布新实体(直接调 kb service 或 SQL 置 published)+
5. 禁改清单
通用禁改之外:packages/retrieval/repo.py 的既有函数、packages/{kb,rules}/**、apps/**、所有已有测试文件。T12 会调 invalidate_cache()——签名不变是你与它的契约。
6. 验收标准(T-P2-10)
- 同参重复检索零 DB 交互(单测断言);
- 发布后新内容立即可见(invalidate 路径)且 TTL 到期自然可见(双路径集成测试);
- 影响结果的输入全量进缓存键(rule_boost 哈希);
- 空结果不缓存(gap 埋点不被吞);
- 全部检查绿(mypy 范围照 T6 文档同口径 +
packages/retrieval)。
7. 提交信息
feat(retrieval): TTL+LRU result cache with generation-based invalidation
TtlCache(全输入进键/惰性过期/容量逐出/命中零 DB)+ search 接线
(空结果不缓存,保住 gap 埋点)+ invalidate_cache 真实现(T-P2-10)。
偏差说明:<如有写在这里,无则写"无">