Essay 013 · 软件工程

物化衍生数据:用更新成本换查询速度

从 GraphRAG 社区报告的更新痛点出发,给出"衍生链深度 × 下游扇入 × 可局部修改性"三因子公式,判断一份数据该不该预先生成并存下来。

首版:给出衍生数据物化的三因子判断公式

Version 1.0

一句话结论:把一份数据预先加工成另一份数据存起来(物化),本质是拿”上游一变、下游跟着重算”的更新成本,去换”查询时不用现算”的速度。这笔交易是赚是亏,由三个因子决定:衍生链有多深、下游扇入有多大、衍生物能不能局部修改。

为什么写这篇

研究 GraphRAG(微软开源的知识图谱 RAG 方案)时注意到它的核心机制”社区报告”:系统先从语料抽出实体和关系构成图谱,再用聚类算法把图切成若干社区,最后让大模型给每个社区写一份主题摘要。宏观问题(“这批文档整体在讨论什么”)靠读这些预先生成的摘要来回答。

这套设计在数据更新时非常痛苦:插入一篇新文档,可能改变社区划分,进而让一批报告全部作废。但同样是”预先生成的数据”,向量索引更新起来却便宜得多——改一篇文档,只需重算它自己的几十个切片向量。为什么两种”预生成”的更新成本天差地别?把两者放进同一个框架比较后发现,这不是 RAG 领域的特殊现象,而是衍生数据(derived data)的通用规律,缓存、物化视图、构建系统、前端渲染都在上演同一幕。

衍生链:上游一动,下游动一串

“A 数据由 B 数据衍生”意味着 B 变了 A 就必须跟着变,否则 A 就是错的(过期、不一致)。GraphRAG 的衍生链有六环:

原文 → 切片 → 向量 → 实体/关系 → 图 → 社区划分 → 社区报告

人改动的永远是最左端的原文,但要重算的是箭头右边的一切。链越深,一次上游改动牵连的重算环节就越多。三种 RAG 方案放在一起比较,差异立刻显现:

序号 方案 衍生链 更新时要重算几环
1 纯向量 RAG 原文 → 切片 → 向量 2 环
2 LightRAG 原文 → 切片 → 向量 → 实体/关系 3 环
3 GraphRAG 原文 → 切片 → 向量 → 实体/关系 → 图 → 社区 → 报告 6 环

纯向量 RAG 更新最便宜,GraphRAG 最痛,链长是第一层原因。

三因子公式

链长只是一半。完整的更新成本由三个因子相乘决定。

因子一:衍生链深度。 上游一次改动要传导几环,每环都要付出重算成本(在 GraphRAG 里每一环都是大模型调用,是真金白银)。

因子二:下游扇入(fan-in)。 一个衍生物由多少上游数据共同决定。切片到向量是一对一:改一篇文档,只需重算它自己那几十个切片的向量,其他切片完全不受影响,更新精准而便宜。实体关系到社区是多对一:一个社区由几百个实体共同决定,上游任何一处小改动都落在它的依赖范围内——改一条边,整个社区可能要重新聚类。扇入越大,单次改动波及的衍生物越多。

因子三:衍生物能否局部修改。 向量是数值数组,哪个切片变了就替换哪个,可以精准点更新。社区报告是大模型写的一段散文:一条边变了,理论上报告里只有一句话受影响,但散文无法打补丁,只能整篇重写。衍生物越不可局部修改,重算粒度就越粗,“改一个字、重写一整篇”的浪费就越严重。

三因子相乘,向量索引落在”赚”端(浅链、一对一、可点更新),社区报告落在”亏”端(深链、多对一、只能整体重写)。同为物化,命运迥异。

同一个问题,五个领域的化身

这条规律不是 RAG 独有。把”链深 × 扇入 × 可修改性”套到其他领域,每个都能对上号:

序号 领域 衍生物 更新难题 所处光谱
1 数据库 物化视图 底表一改视图就过期;PostgreSQL 默认全量刷新,Oracle 等支持按日志增量刷新 全量重算 → 增量维护
2 缓存 缓存条目 著名的行业笑话:“计算机科学只有两大难题——缓存失效、命名和差一错误”,失效难题说的就是上游变了下游不知道 手动失效
3 构建系统 编译产物 make、Bazel 显式维护源文件到产物的依赖图,改哪个文件就只重编受影响的目标 增量维护
4 前端 渲染结果 React 中状态一变整棵组件树重渲染;memo 等手动优化就是在砍不必要的衍生传导 增量维护(手动)
5 数据仓库 下游层层 ETL 表 上游表结构一变,下游任务成片报错;血缘分析(lineage)这个行当就是专门追踪”谁由谁衍生”的 依赖追踪 + 重跑

各领域应对同一个问题的手段,收敛到一条光谱上:

  1. 不物化:什么都不预生成,查询时现算。零更新成本,查询最贵。长上下文大模型直接读全量语料就是这一端。
  2. 物化 + 全量重算:预生成,上游一变就整体重建。查询快,静态数据划算,频繁更新则是灾难。GraphRAG 的社区报告落在这里——而且因为报告是散文,连”部分重建”都做不到。
  3. 物化 + 增量维护:预生成,只重算受影响的部分。查询快、更新可控,但需要一套依赖追踪机制,工程复杂度最高。Bazel、数据库增量视图是代表。
  4. 不可变数据:只允许新增、不允许修改,删除用标记。衍生关系永远只增不减,回避了”变”本身。日志系统、事件溯源是代表。

位置越靠右,查询越快、更新越省事,但要付出的工程复杂度越高。没有哪个点天然正确,只有和你的数据变更频率、查询频率匹配与否。

结论与行动

物化不是免费的加速,而是一笔先收钱、后还债的交易:查询时省下的,是提前存下的;更新时要还的,是衍生链连本带利的传导。动手物化之前,用三个问题做快速判断:

  1. 这份衍生物离原始数据几环?每多一环,更新时多一笔重算。
  2. 它由多少上游数据共同决定?扇入越大,被改动波及越频繁。
  3. 上游变了它能局部修补吗?不能的话,每次更新都是整体重写。

三个答案都不乐观时,“不物化、查询时现算”往往是更诚实的方案——LightRAG 砍掉社区报告、把宏观问题交还给查询时实时组装,正是这个判断在真实项目里的一次落地。

参考资料

  • microsoft/graphrag:社区发现与社区报告的原始实现
  • HKUDS/LightRAG:砍掉社区报告、改走查询时关系链组装的轻量方案
  • Martin Kleppmann《Designing Data-Intensive Applications》:衍生数据(derived data)概念的系统阐述
查看本文修订记录(1)
  1. v1.0 · 2026.10.15 首次发布。