4.3 写入、更新与删除 扩容机制就位后,本节跟着一条数据走完它的一生。承接 4.1 的写入路径与 3.3 的分段机制,本节把写入、更新、删除这三类日常操作的语义讲透:写进去多久能查到、更新到底发生了什么、删除为什么不是真删。旧版教程的"插入与批量导入""更新与删除操作"两节在此合并为一条完整的生命周期叙事。 一条记录的状态机 一条向量记录从提交到消亡,会经过下面这些状态: 状态机里有两条最值得留意的边。其一,从"已写入日志"到"内存段可见"之间的时间差,就是客户端感知到的可见性延迟——多数系统返回写入成功即代表日志落盘且进入可检索集合,但个别实现要等刷新周期,秒级延迟在此发生;做"写入后立即可查"的链路时务必核对你所用产品的可见性语义。
扩容机制就位后,本节跟着一条数据走完它的一生。承接 4.1 的写入路径与 3.3 的分段机制,本节把写入、更新、删除这三类日常操作的语义讲透:写进去多久能查到、更新到底发生了什么、删除为什么不是真删。旧版教程的"插入与批量导入""更新与删除操作"两节在此合并为一条完整的生命周期叙事。
一条向量记录从提交到消亡,会经过下面这些状态:
状态机里有两条最值得留意的边。其一,从"已写入日志"到"内存段可见"之间的时间差,就是客户端感知到的可见性延迟——多数系统返回写入成功即代表日志落盘且进入可检索集合,但个别实现要等刷新周期,秒级延迟在此发生;做"写入后立即可查"的链路时务必核对你所用产品的可见性语义。其二,从任意在库状态到"墓碑态"的边是逻辑删除,物理回收要等后台合并,3.3 讲过的墓碑比率问题就从这条边上长出来。
逐条写入在向量库里的开销远比关系库显著:每条都要走一次路由、一次日志、一次段内插入,图索引还要为它连边。批量接口把开销摊平,吞吐常见能差出一个数量级,所以离线灌库一律批量,在线写入也尽量微批攒写。批量大小不是越大越好——单批几十兆字节以内通常较优,过大反而拖长确认时间、放大失败重试的成本。灌库还有一条捷径:如果是从零建库,走"先灌数据、后建索引"或直接用离线构建工具生成段文件再挂载,比"先建空索引再逐批插入"快得多,3.2 的训练加灌入两阶段在系统层面就对应这个流程。
# 一次规范的批量写入会话(伪代码) rows = [] for doc in daily_delta: # 日增量数据 vec = embed(doc.text) # 用与库内一致的模型版本 rows.append({ "id": doc.id, "vector": normalize(vec), # 两端口径一致,见 2.3 "payload": {"tenant": doc.tenant, "updated_at": doc.ts}, }) if len(rows) >= 512: # 微批,过大过小都不好 collection.upsert(rows) rows.clear() if rows: collection.upsert(rows)
向量库的更新多数是 upsert 语义:ID 存在则替换向量与载荷,不存在则插入。看似简单,三个坑分布在外围。坑一:部分字段更新。有些系统允许只更新载荷(过滤字段)而不动向量,有些则是整行替换——只想改个上架状态却把向量覆盖成了空值,这类事故要求使用前必读接口语义。坑二:向量改了、模型没对齐。更新向量必须用当前版本的嵌入模型重算,混用新旧版本等于把两套坐标系的数据写进同一个索引,2.3 复盘过这个静默故障。坑三:删除后立刻重插同一 ID。旧向量的墓碑还在段里,新向量进了新段,查询端要靠系统正确去重;个别实现在墓碑回收前会同时命中新旧两版,表现为"删了又插的结果出现了两次",遇到时优先怀疑墓碑回收节奏。
删除同样有讲究:按过滤条件批量删除(比如清退某租户全部数据)在大集合上可能触发电机般的扫描,量大时优先用系统的按分区删除或直接删分区。所有删除操作都要记得 3.3 的账:墓碑比率进入监控,超标安排重建。
背景。 某内容平台把商品库从关系库向量化后供搜索使用,初始方案是每十分钟全量重灌一次,集合约两千万条。上线初期平稳,随库存增长,重灌窗口从数分钟涨到近一个钟头,且重灌期间查询延迟明显抖动。
操作。 改造为增量管道:关系库的变更流(新增、改价、下架)进入消息队列,消费端微批解析,向量按当前模型版本重算后以 upsert 写入,下架事件映射为删除;另加一道对账任务,每小时抽样比对向量库与关系库的记录数与更新时间戳,缺口自动补偿。写入侧还按租户做了分区,热门类目单独一个分区削峰。
结果。 数据新鲜度从最长十分钟缩到秒级,重灌作业取消,全天查询延迟曲线趋于平坦;对账任务上线后抓到过两批因模型版本残留导致的坐标错位,在进入索引前被拦下。
解读。 这个案例的实质是把"生命周期"当作一等公民来设计:新增走 upsert、下架走删除、对账兜底,每类事件都有明确的语义映射,而不是用全量重灌掩盖增量语义的缺失。抖动消失则印证了 3.3 的分段吸收逻辑——稳定的微批写入让后台合并从容运转。
变式。 同样的管道骨架可迁移到知识库文档更新(切片重算与旧墓碑清理)、用户兴趣向量滚动更新(按行为事件流微批刷新)、以及多模态资产库(缩略图重建时批量换向量)。
沿写入路径逆推。第一步看日志层:落盘延迟是否抬升(磁盘抖动或刷盘策略过保守);第二步看可变段:段是否涨得过快——合并线程卡住时写入会被反压,这是最常见原因;第三步看分片倾斜:某条热点键把流量全引到一个分片;第四步看批量参数:上游是否因为重试把批量拆得粉碎。按这条顺序走,绝大多数写入劣化能在两轮内定位。把每一步的关键指标(日志落盘延迟、段大小增速、分片写入分布)预先配上监控,排查就从猜变成读仪表盘。
可以且经常应该。初次建库或模型换代时,全量灌入走离线通道(先灌数据后建索引),业务增量 meanwhile 走变更日志缓存;全量完成后,把缓存的增量按时间顺序回放,追平后切换。关键在两点:增量缓存的保留窗口要盖住全量时长(估算不准就把窗口加倍);回放要幂等(按主键 upsert 天然幂等,重复回放无害)。这套"快照加回放"的模式与 3.3 的双索引切换是同一思想在数据面的应用。
数据的一生走完了。下一节看它如何被保护:落盘时机、快照恢复,以及压进内存的最后一招。