本节摘要:索引不能每次进程重启都重建——嵌入费用和时间都不允许。本节覆盖三件事:默认存储的
persist/load三件套、外接向量库下的增量更新(insert / 更新 / 删除)、以及多版本索引的生命周期管理(什么时候全量重建、灰度切换怎么做)。
不接外部向量库时,LlamaIndex 的 StorageContext 可以把三部分内容落盘:向量(docstore 里的 Node 与 embedding)、索引结构(每种索引自己的元数据)、文档存储。
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, load_index_from_storage, StorageContext # 存 index = VectorStoreIndex.from_documents(SimpleDirectoryReader("./data").load_data()) index.storage_context.persist(persist_dir="./storage_2024q3") # 取(另一个进程) storage = StorageContext.from_defaults(persist_dir="./storage_2024q3") index2 = load_index_from_storage(storage) print(index2.as_query_engine().query("试用期多长?"))
注意这是"整套存储"的快照,不是数据库——没有并发写保护。单机脚本与实验用它足够;多进程服务请走上一节的外接向量库。
知识库的常态是"每天进来几十篇、改十几篇、删几篇"。外接向量库下三个操作各有讲究:
# 插入:对已有索引对象直接 insert,只嵌入新文档 new_docs = SimpleDirectoryReader(input_files=["new_policy.md"]).load_data() index2.insert(new_docs[0]) # 仅这一篇产生嵌入调用 # 更新 = 删旧 + 插新:稳定 id 在这里生效 from llama_index.core.vector_stores import ( MetadataFilters, ExactMatchFilter) engine = index2.as_query_engine() # 按 2.1 节约定,filename_as_id 让节点 id 稳定可定位 index2.delete_nodes(node_ids=[...]) # 或向量库按 metadata 条件删 index2.insert(revised_doc) # 全刷新:文档有结构性大改(章节重排)时局部修补不可靠,整篇删了重来
# 刷新文档句柄:refresh_documents 会对比新旧并只重嵌有变化的 from llama_index.core import Document fresh = SimpleDirectoryReader("./data").load_data() refreshed = index2.refresh_documents(fresh) print("有变化的文档数:", refreshed)
一个工程细节:refresh_documents 判断"变化"依赖稳定 id 与内容比对,配合 2.4 节的 IngestionPipeline 缓存,可以把"每周全量同步"做得和"只处理变更"一样便宜。
索引一旦承载线上流量,"边改边用"就变成危险动作。稳妥做法是把索引当成有版本的数据资产:
# 版本化目录 + 显式切换 persist(index, "./storage_2024q4_v2") # 上层用一个"当前指针"决定读哪个版本,出问题一键回滚 CURRENT = "./storage_2024q4_v2" index_prod = load_index_from_storage(StorageContext.from_defaults(persist_dir=CURRENT))
什么时候必须全量重建而不是修补?三种情况:换嵌入模型(3.1 节的铁律)、切分策略大改(块边界全变,旧节点失去意义)、向量库迁移。除此之外,增量永远是更经济的选择。
⚠️ 常见坑:内存里
insert了大量新文档却忘了 persist 或没写进外部向量库,重启后全部蒸发。养成"写操作后立刻持久化"的习惯,或干脆全部走带持久化的向量库,绕开这个坑。
persist 目录越攒越多,怎么管理? 当成构建产物管理:保留"当前版 + 上一版 + 最近一个里程碑版"三份,其余清理。配合 3.4 节的版本指针,磁盘占用可控且回滚能力保留。用时间戳命名目录,排查"上周的索引是什么状态"这类问题时有据可查。
增量插入的节点会破坏索引质量吗? 单纯的插入不会,但长期只增不查的"熵增"会:早期文档与新增文档的表述风格漂移、重复内容累积。周期性做一次全量体检(5.4 节评估集回归加重复节点扫描),发现指标下滑再考虑计划性重建,比无脑定期重建省得多。
多个服务进程共享一个向量库,写入怎么协调? 让摄取管道单点写入(离线管道天然单点),线上服务只读。如果确实存在多个写入方,按集合或批次号划分写入范围,避免对同一节点的并发写覆盖。