ADR-0002: Force 与 Invalidate 缓存语义
历史决策记录:保留当时的背景、状态和设计。当前接口与实现归属以 开发入口 和现行规范为准;文中的历史模块路径不保证仍存在。
状态: Accepted,已实现 日期: 2026-06-09 相关文档: ../architecture.md, ../workflows.md, ../cli/mineru-parse.md, ../sdk/doclib-client.md 补充决策: 0003-parse-request-wait-batches.md, 0004-doclib-http-api-resources.md
背景
doclib 只持久化按页组织的 Middle JSON 批次文件。同一文档、同一解析档位可以因为增量页码解析、重新解析或后台合并而存在多个 done batch。
因此需要明确两个容易混淆的动作:
--force: 用户要求本次请求重新解析。invalidate: 用户或系统要求已有缓存不再参与后续读取、搜索和覆盖判断。
如果二者语义混在一起,重新解析失败时可能丢失可用旧结果;如果读取逻辑不区分有效批次和失效批次,缓存覆盖、搜索索引和 compaction 都会出现不一致。
决策
--force 与 invalidate 是两个不同动作。
--force 的语义:
- 对本次请求的
sha256 + tier + page_range忽略已有缓存覆盖判断。 - 不忽略
pending/parsing的 active parse;已经覆盖请求页码的 active parse 可以复用并提升 priority。 - 只为未被 active parse 覆盖的页码创建新的 parse record。
- 不删除、不标记失效、不提前 supersede 旧的 done batch。
- 本次请求返回
wait_parse_ids、created_parse_ids和reused_parse_ids。调用方等待wait_parse_ids,而不是轮询文档聚合状态。 - wait parse 成功后,读取同一页时按有效 done batch 的
done_at选择最新页面内容。 - wait parse 失败时,本次 force 请求失败;旧的 done batch 仍然有效,仍可被读取、搜索和后续 compaction 使用。
invalidate 的语义:
- 第一版通过
POST /invalidate执行,要求target="parses"。 - 将指定文档和可选 tier 的已有 done parse 标记为失效;第一版不提供页码或单 batch 粒度。
- invalidate 本身不要求立即重新解析。
- 失效 parse 不参与缓存覆盖判断、读取时页合并、搜索索引刷新和 compaction 的“最新页”选择。
- invalidate 后需要刷新搜索索引:若仍有有效 done parse,则从最高有效 tier 重建 FTS;若没有有效 done parse,则删除对应 FTS 内容。
- 对应 JSON 文件的物理删除应由 cleanup 或 compaction 异步完成,不作为 invalidate 的同步完成条件。
读取合并规则:
- 只扫描有效的 done batch。
- 忽略 JSON 文件缺失的 batch。
- 按
page_idx合并页面。 - 同一页出现多次时,选择
done_at最新的有效 batch。
替代方案
方案 A: --force 前删除旧缓存
拒绝。重新解析失败时会丢失原本可用的结果,不符合缓存作为稳定可读结果的定位。
方案 B: --force 开始时先把旧 batch 标记为 superseded
拒绝。它仍然会让失败的重新解析破坏旧结果可用性。只有新 batch 成功后,读取层按 done_at 自然选择新页面即可,不需要提前改变旧 batch 状态。
方案 C: force 严格总是创建新 parse
拒绝。已有 active parse 覆盖请求页码时,重复创建 parse 会浪费算力。--force 的核心语义是跳过 done cache,不是强制制造并发重复任务。
方案 D: invalidate 自动触发重新解析
拒绝。失效缓存和创建新解析任务是两个独立意图。调用方可以在 invalidate 后再显式 parse,但 invalidate 不应隐式消耗算力。
影响
实现影响:
- parse cache coverage 需要过滤失效 batch。
- read-time render 需要按有效 done batch 合并页面,并用
done_at解决重复页。 - compaction 只处理有效 done batch,不能把失效 batch 合入新文件。
- 搜索索引刷新不能从失效 batch 读取文本;invalidate 后需要删除或降级重建 FTS。
force=True的 parse 请求只绕过 done 缓存命中,不改变旧 batch 生命周期。POST /parses需要返回wait_parse_ids、created_parse_ids、reused_parse_ids。
测试影响:
- 覆盖
force=True复用 active parse,并只为未覆盖页创建新 parse。 - 覆盖 force wait parse 失败后本次请求失败,但旧结果仍可读取。
- 覆盖重复页读取时最新有效
done_at胜出。 - 覆盖 invalidated batch 不参与缓存命中、读取合并和 compaction。
- 覆盖 invalidate 后 FTS 删除或降级重建。
用户体验影响:
--force是低风险重新解析动作;它跳过 done cache,但不制造重复 active parse。- invalidate 是显式缓存生命周期管理动作,应通过 SDK、管理 API 或维护命令暴露,不应隐藏在普通 parse 行为里。
后续动作
- 在 SQLite schema 和 parse row 状态中使用
status=superseded表达已失效缓存。 - 更新 parse cache coverage、read-time render、search refresh 和 compaction 的过滤条件。
- 更新
POST /parses和 CLI wait,等待wait_parse_ids。 - 为
force和invalidate增加独立测试。