一个真实的运维困惑:某团队按 14 天
TTL(collection.ttl.seconds = 1209600)清理知识库里的过期文档,监控里”集合行数”在第
15 天准时掉了一截,但对象存储的 bucket
体积在接下来两天几乎没变化,第三天才开始明显下降。有人怀疑
TTL
没生效,但查询确认过期数据确实已经搜不到了。真正发生的事是:“搜索不可见”和”对象存储已清理”是两个不同时刻的两个不同事件,中间隔着一次
compaction。这个时间差不是 bug,是 Milvus 用软删 bitset
而不是原地擦除来处理变更的直接后果——本文按 2.6.x 官方文档把
delete、upsert、TTL
这三种”看起来是不同操作”的机制,收束回同一套底层生命周期。
本文是「向量检索引擎」系列第 13 篇(共 19 篇)。→ 系列目录
版本锚定:Milvus 2.6.x Delete Entities、Upsert Entities、Set Collection TTL、Bitset。
一、Delete:过滤或主键,都走同一套软删
Delete Entities:可按过滤表达式或主键删除,也可限定 Partition。
- 过滤删除:适合”按属性批量删”,例如按
color in [...]清理一批过期分类。 - 主键删除:适合”精确删几条”,例如
ids=[18, 19]。
复杂过滤可用 Filter Templating(第 11 篇)降低解析成本。无论走哪条路径,Delete 都进入 DML 路径写入 WAL,与 insert 共享同一套时间戳与顺序体系(第 5、12 篇)。
两条路径的前置代价不同,容易被忽略:主键删除可以直接命中主键索引,定位到具体行;过滤删除首先要经过第
11 篇讲的”表达式 →
filter_bitset“这条链路——对没有建标量索引的字段,这一步退化成逐行扫描判断谓词,字段值越多、条件越复杂,生成
filter_bitset
本身的耗时越高。也就是说,”按属性批量删一批过期分类”这类操作,delete
请求本身的处理时间可能主要花在算出该删哪些行,而不是”标记删除”这个动作——标记删除只是对已经算出的位图做一次写入,代价相对固定。
1.1 软删的位置:不是”删除时立刻改索引”
Bitset 文档明确:删除信息存在段内
bitset,标记删除的实体在 search/query
不参与计算(第 11
篇已给出该 bitset 与过滤 bitset 做 OR
组合的完整取反逻辑)。Streaming Service
文档补充:Query Delegator 负责向其它 Query Node 广播
Delete——历史段上的可见性靠删除广播,而不是立刻重写整张图索引。第
8 篇已确认更细的一层:对 Knowhere 的 HNSW
索引而言,被标记删除的节点仍然可以作为图遍历的中继点继续参与导航,只是不进最终候选堆。这意味着:
\[ \text{delete 成功(逻辑)} \;\Rightarrow\; \text{后续搜索应不可见(在一致性水位满足时)} \]
但
\[ \text{图结构本身缩小、对象存储字节回收} \;\Leftarrow\; \text{compaction 等后台过程(第 10 篇)} \]
两者不是同一时刻,也不是同一机制触发。
1.2 为什么不能”原地”改一个向量的值
一个自然的疑问:既然只是改几个字段或者向量本身,为什么
Milvus 不提供真正的原地
UPDATE——直接在原有内存/磁盘位置上把旧向量换成新向量?答案要回到索引的物理结构上找。
对 IVF 类索引,一个向量被分配到哪个 posting list,取决于它在构建/插入时与哪个聚类中心最近;向量值一变,它理论上应该属于的聚类中心也可能变,原地覆盖只是换了 posting list 里的数据,并不会重新计算它该归属哪个簇——如果不重新分配,检索时按新向量去别的簇找它的邻居,会在旧的(错误的)簇里找不到。对 HNSW 类图索引,问题更直接:一个节点的出边和入边,是构建/插入时按当时的向量值与邻居的距离决定的;向量值改变后,它与原邻居的距离关系可能整体变化,原来的边不再是”最近的若干条边”,但图结构不会因为你换了向量值就自动重新连边——第 8 篇已经确认 Knowhere 的软删是”标记后节点仍留在图里当中继”,这套机制解决的是”删除”,没有涉及”原地改值后重新连边”这个更复杂的问题。
也就是说,“改一个向量”在索引层面的真实代价接近”删一个旧节点 + 插一个新节点”,不管产品层面把这个操作叫 update 还是 upsert,底层都绕不开重新参与一次索引结构的插入过程。Milvus 把这个代价显式化成”override = insert + delete”,没有假装存在一种更便宜的原地更新路径——这也是第六节要讨论的、FreshDiskANN/IP-DiskANN 想要解决的问题:能不能让”改一个点”比”删一个点再插一个点”更便宜。
二、Upsert:两种内部路径,不是同一件事换了个名字
Upsert Entities:按请求中的主键是否已存在,决定插入新实体或更新已有实体。Milvus 2.6.x 提供两种模式,内部实现路径完全不同。
2.1 Override 模式:插入 + 删除
官方:override 模式的 upsert 组合 insert 与
delete——对已存在主键,插入请求载荷中的新数据,并删除原主键对应的旧实体。若主键字段开启
autoID:请求仍须带目标实体主键以便定位;Milvus
用该主键定位待替换实体,并为载荷生成新主键再插入。这意味着
override 在 autoID
下不是”原地改同一主键行”的朴素想象——旧行被删除、新行拿到全新主键,如果上层业务用旧主键做去重或外部引用,会在这里踩坑。
2.2 Merge 模式(2.6.2+):先做一次强一致性查询
Upsert Entities 文档给出的 merge
模式内部步骤,是本文最值得画出来的一段机制:设置
partial_update=True 后,Milvus
收到请求会先按强一致性发起一次查询,取回该主键当前的完整行,再用请求里携带的字段覆盖对应值,插入合并后的新行,最后删除旧主键对应的旧行。也就是说
merge
模式不是”只更新几个字段”这么轻量,它在写路径内部反过来依赖了第 12 篇的
Strong
一致性查询语义——这是一个容易被忽略的跨篇依赖:如果不理解
Strong 一致性意味着”等待 ServiceTime 追上”,就不会想到 merge
模式的 upsert 延迟里,可能藏着一次隐藏的一致性等待。
把两种模式的内部步骤画成一张时序图对照:
sequenceDiagram
participant C as Client
participant Px as Proxy
participant QN as Query Node
participant WAL as WAL / 段存储
rect rgb(45,51,59)
Note over C,WAL: Override 模式(主键已存在)
C->>Px: upsert(pk=1, full_row, partial_update=False)
Px->>WAL: insert(new_row,若 autoID 则生成新 pk)
Px->>WAL: delete(old_pk)
end
rect rgb(45,51,59)
Note over C,WAL: Merge 模式(partial_update=True,2.6.2+)
C->>Px: upsert(pk=1, {issue: "vol.14"}, partial_update=True)
Px->>QN: query(pk=1, consistency=Strong)
QN-->>Px: 返回当前完整行(title、vector、issue 等全部字段)
Px->>Px: 用请求字段覆盖对应值,其余保留原值
Px->>WAL: insert(merged_row)
Px->>WAL: delete(old_pk)
end
两种模式的共同点是最终都落成 insert + delete
这一对 DML;差别在于 merge
模式在落地之前多了一次同步的强一致性读。这也解释了官方为什么要求
merge
模式下同一批次的多个实体必须包含相同字段集合——因为合并逻辑是在
Proxy 侧按字段做值覆盖,不是数据库层面的原生
UPDATE ... SET。
2.3 两种模式的代价该怎么估
把 2.2 节的时序图翻译成一次请求要经过的”跳数”,能更直接看出两种模式换来什么、花掉什么:
| 步骤 | Override | Merge(partial_update=True) |
|---|---|---|
| 客户端 → Proxy | 携带完整行(全部字段) | 携带需要更新的字段子集 |
| 是否有内部同步查询 | 无 | 有:Proxy → Query Node 的一次 Strong 一致性
query,需等待该次查询的 GuaranteeTs 满足(第 12
篇) |
| 落地的 DML | 1 次 insert + 1 次 delete | 1 次 insert + 1 次 delete(合并后的完整行) |
| 请求体大小 | 大(全字段) | 小(改动字段) |
| 端到端延迟构成 | Proxy 拆包 + WAL 写入等待 | 多一段”内部查询等待水位”的时间,量级取决于当时的一致性水位追赶速度(第 12 篇第三节的等待时序) |
这张表把”选 merge
换来的是什么”说得更精确:不是延迟更低,而是网络传输的字节数更少——对字段很多、只改一两个字段的场景(例如给一条商品记录只更新库存字段),merge
模式能省下大量重复传输已有字段的带宽;但代价是多了一次同步的强一致性查询,这次查询本身要排队等待水位(如果当时执行节点的
Service_timestamp
落后,等待时间可能远超省下的传输时间)。选择哪种模式,应该按”请求体大小”和”当前集群一致性水位追赶速度”两个维度权衡,不能默认”新模式一定更好”。
2.4 与手工”先 delete 再 insert”的差别
如果业务自己分两次请求做”delete 旧行、insert 新行”,中间存在一个可观察的窗口:两次请求之间,若有其它查询用 Bounded 或更弱的一致性级别命中,可能读到”旧行已被删、新行还没写”的瞬间状态,或者在极端时序下读到两版本同时存在的重复结果(去重逻辑没跟上时)。override / merge 模式把 insert 与 delete 收进同一次请求处理,降低了这个窗口的暴露面,但官方文档没有对”这一次请求内的插入与删除是否原子生效”给出显式的原子性承诺——本篇不代入未声明的保证,只指出”同一请求”和”两次独立请求”在暴露面上的差异是真实存在的。
三、TTL:集合级过期与 compaction 的耦合
Set Collection TTL:通过属性
collection.ttl.seconds(整数秒)配置集合级
TTL,每个实体在 插入时间 + TTL 秒数
过期。官方举的例子:按天摄入但只想保留 14 天数据,设置
14 × 24 × 3600 = 1209600 秒即可。
官方明确三点:
- 过期实体立即停止出现在 search / query 结果里(一旦对应一致性水位满足);
- 物理删除依赖后续 compaction 周期,通常在 24 小时内完成;
- 可以通过
dataCoord.compaction.expiry.tolerance配置项控制触发时机——默认-1表示沿用既有 compaction 间隔,设为正整数(如12)则表示”实体过期后再等这么多小时触发一次专门的 compaction”。
这与本文开篇的运维困惑完全对应:第 15
天行数掉了(逻辑不可见立即生效),字节数要等 compaction
周期(默认间隔或 expiry.tolerance
配置的窗口)才会下降。容量规划不能假设”TTL=14
天则对象存储精确保持 14 天字节”——精确的说法是”逻辑上保留 14
天,物理上保留 14 天加上一个 compaction 周期”。
3.1 把开篇的两天延迟摆上时间轴
用一条时间轴把开篇故事里”行数第 15
天掉、字节数第三天才降”具体化,能看出
expiry.tolerance 配置项实际控制的是哪一段:
\[ \underbrace{t_{\text{insert}}}_{\text{第 0 天}} \;\longrightarrow\; \underbrace{t_{\text{insert}} + 1209600\text{s}}_{\text{第 14 天,逻辑过期}} \;\longrightarrow\; \underbrace{[\,t_{\text{过期}},\ t_{\text{过期}} + \Delta\,]}_{\Delta \le \text{expiry.tolerance 或默认 compaction 间隔}} \;\longrightarrow\; \underbrace{t_{\text{compaction 完成}}}_{\text{对象存储字节释放}} \]
第一段(0 到 14 天)的长度完全由
collection.ttl.seconds
决定,是产品语义上承诺的”保留多久”;第二段(\(\Delta\))的长度由
dataCoord.compaction.expiry.tolerance 或既有
compaction
调度间隔决定,是一段运维可调、但产品语义没有单独承诺具体数值的窗口。开篇故事里两天的观测延迟,落在的正是第二段——如果把
expiry.tolerance 显式设成一个较小的正整数(如
1
小时),可以让这段窗口更快触发,但不会改变第一段的 14
天逻辑保留期,也不会让 compaction 本身的重建耗时消失(第 10
篇已说明 compaction
的重建成本与参与合并的段大小相关)。把这两段分开理解,才能准确回答”我该等多久才能确认容量下降”这个问题,而不是笼统地说”等
24 小时”。
四、软删 bitset 的完整生命周期
把 delete、upsert(内部退化为 delete)、TTL 到期这三类触发源统一画成一张状态图:
stateDiagram-v2
[*] --> Active: insert 成功,行进入 Growing/Sealed
Active --> LogicallyGone: delete(按 pk 或 filter)
Active --> LogicallyGone: upsert override/merge 内部的 delete(old_pk)
Active --> LogicallyGone: TTL 到期(insert_ts + ttl_seconds)
LogicallyGone: 段内 bitset 标记为 1;搜索/查询跳过该行;字节仍在对象存储
LogicallyGone --> Purged: compaction 重写该段,物理剔除已标记行
Purged --> [*]: 对象存储字节释放
note right of LogicallyGone
result_bitset 掩码嵌在 Knowhere
索引遍历过程中生效(第 8、11 篇);
HNSW 中被删节点仍可作为
图遍历中继点,不是立刻从图中移除
end note
三种触发源(delete、upsert、TTL)在
LogicallyGone
这一步之后完全汇合成同一条路径——不管进入这个状态的原因是什么,退出路径都是段级
compaction
重写,没有针对某一类触发源的专用回收机制。
用一个具体实体的完整生命周期把这张状态图落到实处:假设
pk=101 的一条商品向量在第 0 天插入,第 5 天该商品做了一次
merge 模式的 upsert 改库存字段(内部产生
delete(pk=101) +
insert(pk=101 的新行)——注意这里的 pk 因为不是
autoID
集合而保持不变,新旧行短暂共存于同一段的不同版本),旧版本的
pk=101 立即进入 LogicallyGone;第 14
天,如果这条商品所在 Collection 配了
TTL,新版本的 pk=101(第 5
天插入,以自己的插入时间重新计算过期时间)还没到期,继续留在
Active;直到某次 compaction
扫过它所在的段,才会把第 5
天之前那个已标记删除的旧版本从对象存储上真正剔除。这个例子想说明的是:同一个主键在数据生命周期里可能对应多个物理版本,“删除”和”过期”都是按具体某一次插入产生的行独立计算的,不是绑定在主键这个逻辑概念上一次性算完。
五、常见误解
误解一:delete
成功返回后,那一行立刻从对象存储上消失。
第一节与第四节的状态图说明恰好相反:delete
只改变段内 bitset,真正把字节从对象存储上剔除依赖后续
compaction。TTL 到期同理,官方文档明确给出”通常 24
小时内”的窗口,不是瞬时的。
误解二:upsert override
就是原地修改同一条记录的向量值,主键不变。 2.1
节已经说明:override 模式内部是插入新行 +
删除旧行;autoID
集合下新行会拿到全新主键,旧主键只用来定位待替换的行,替换完成后旧主键对应的行已被逻辑删除。依赖旧主键做外部关联的业务逻辑需要显式处理这次主键轮换。
误解三:merge 模式的 upsert
只是”部分字段更新”,不涉及额外的读操作,延迟应该比 override
更低。 2.2 节的时序图说明恰好相反:merge
模式在写之前,先要用 Strong
一致性发起一次查询取回当前完整行,这次查询本身要等
Service_timestamp 追上(第 12
篇),是一次真实的同步等待,不是免费的。选 merge
模式换来的是”请求体更小、不用带全部字段”,不是”延迟更低”。
误解四:TTL 设了 14 天,容量规划就可以按 14
天精确估算对象存储占用字节数。
第三节已经说明:过期到物理回收之间还隔着 compaction 周期(受
dataCoord.compaction.expiry.tolerance
影响),“集合行数”与”对象体积”在 TTL
窗口边界附近会短期背离,监控两个指标时不能假设它们同步下降。
误解五:想更新一个实体的向量值,用 upsert 总比”先想办法原地改”更笨重,一定有更轻量的路径。 1.2 节已经说明:无论产品接口叫什么名字,只要向量值发生变化,索引层面都绕不开”旧节点失效、新节点重新参与构建”这一过程——IVF 的聚类归属、HNSW 的邻居边都是按当时的向量值确定的,不会因为你原地覆盖了数值就自动更新。upsert 把这个代价显式为 insert + delete,不是因为实现偷懒,而是因为图/聚类索引结构本身没有更便宜的替代路径(第六节 FreshDiskANN/IP-DiskANN 讨论的正是”能不能把这个代价降下来”)。
误解六:过滤删除和主键删除的请求处理时间应该差不多,因为最终都只是标记一个
bitset。 1.1
节之前的补充已经说明:主键删除可以直接命中主键索引定位到行,过滤删除要先把表达式变成
filter_bitset——这一步对没建标量索引的字段接近逐行扫描。两者最终”标记删除”这个动作代价相近,但算出该删哪些行这个前置步骤的代价可能相差很大,容易被忽略。
六、学术谱系、工程间隙、开放问题
6.1 谱系:软删是 LSM 家族的常见模式,但图索引的删除是另一道难题
软删 + 后台合并本身是 LSM / 列存里的经典模式(O’Neil, Cheng, Gawlick, O’Neil, The Log-Structured Merge-Tree (LSM-Tree), Acta Informatica 33(4), 1996;第 10 篇已引用其”前台可变、后台不可变整理”的结构)——这部分不是向量数据库特有的新问题。真正特有的是:删除掩码必须进入 ANN 图索引的遍历过程,否则图游走仍可能返回已删除的近邻(第 8、11 篇已确认 Knowhere 的做法:被删节点仍作为中继,只是不进候选堆)。
这正是 Singh, Subramanya, Krishnaswamy, Simhadri 在 FreshDiskANN: A Fast and Accurate Graph-Based ANN Index for Streaming Similarity Search(arXiv:2105.09613, 2021,未经 peer review 的预印本)里系统化处理的问题:论文指出图索引的”删除”天然比倒排表难——图是单向链接的,删除一个点后没有快速方法找到它的所有”入边邻居”。FreshDiskANN 的方案是惰性删除 + 周期性 StreamingMerge 整理:先在内存里标记删除,定期做一次一致性重排(消除到已删节点的边,同时保证图连通性/召回不退化),而不是每次删除都同步改图。
6.2 工程间隙:整段重建,不是增量合并
对照来看,Milvus 官方文档没有描述任何等价于 FreshDiskANN
StreamingMerge
的增量一致性重排算法。第 3、8、10
篇已确认的事实是:Milvus 的索引生命周期贴着 segment——每个
Sealed segment 有自己的一份完整索引,compaction
把多个段的数据合并进一个新段时,是在新段上重新
train/build
一份新索引,不是对旧索引做增量修边。也就是说:
- FreshDiskANN 的删除消除是图级别的局部手术(重排受影响节点的边),代价与”受影响的边数”相关;
- Milvus 的删除消除是段级别的整段重建(compaction 后新段重新建索引),代价与”参与合并的段大小”相关,不管这个段里删除比例是 1% 还是 50%。
对于高频 upsert 场景(每次 upsert 内部退化为一次 delete),这个差异会被放大:upsert 越频繁,被标记删除的行占比越高,触发 compaction 的频率也越高,但每次 compaction 的重建成本不会因为”只是删除了一点点”而降低——第 10 篇已指出这类问题会放大 WAL、compaction 与建索队列的压力。
可以借用 LSM 写放大(Write Amplification)的思路给这个放大效应一个粗略的量级感觉:设一个段有 \(N\) 行,其中 \(d\) 行被标记删除(\(d \ll N\)),一次 compaction 重建整个段的成本近似与 \(N\)(而非 \(d\))成正比。如果业务的 upsert 模式是”频繁小批量更新少数热点行”,\(d/N\) 会长期维持在一个不高的比例,但只要 \(d\) 触发了 compaction 的阈值,重建成本仍按 \(N\) 计——每次 compaction 花的力气与”删了多少”无关,只与”这个段有多大”有关。这与经典 leveled compaction 的 WA 分析(Dayan & Idreos, Monkey/Dostoevsky, SIGMOD 2017 系列工作已系统化这类权衡,第 10 篇已引用其结论)在思路上同构,但 Milvus 的对象是”整段重建”而不是”跨层归并”,具体的放大系数不能直接套用 LSM leveled/tiered 的公式,此处只借用其”重建成本看整理单元大小、不看变更比例”的直觉,不代入原始的层级放大系数。
6.3 开放问题
- 删除广播延迟与多副本历史段之间的上界是多少——第 14 篇会展开副本拓扑,广播路径本身官方文档只描述了”Query Delegator 负责广播”,没有给出延迟上界。
- Knowhere 的图索引是否值得引入类似 FreshDiskANN 惰性删除
+ 增量
StreamingMerge的机制,以避免高频 upsert 场景下反复触发整段重建?更进一步,IP-DiskANN(In-Place Updates of a Graph Index for Streaming Approximate Nearest Neighbor Search, arXiv:2502.13826, 2025,同样是未经 peer review 的预印本)尝试完全去掉批量整理这一步、逐条原地处理插入删除——这类工作是否有可能下沉到 Knowhere 这层,目前没有公开的官方计划可以引用,只能作为方向记录。 - upsert merge 模式的内部 Strong 一致性查询,在多字段部分更新与高并发写同一主键的场景下,是否存在”读到的旧行”和”最终插入的新行”之间的竟态窗口——官方文档描述了步骤顺序,但未显式讨论并发 upsert 到同一主键的行为边界。
- TTL 与湖仓侧的 snapshot 保留策略(如果向量数据存在跨系统的生命周期对齐需求)如何统一同一份业务日历,避免”向量库已经物理清理,湖仓侧快照还留着旧版本”的不一致认知。
七、小结
三句话小结:
- Delete、upsert(内部退化为 delete)、TTL 到期三类触发源都先落成同一件事——把段内 bitset 对应位标记为”跳过”,真正的字节回收统一等待后续 compaction,两者之间的时间差是设计使然,不是故障。
- Upsert 的 override 模式是”插入新行 + 删除旧行”,merge 模式在此之外还多了一步 Strong 一致性查询取回旧值再合并——merge 换来的是请求体更小,不是延迟更低。
- Milvus 用”段级整段重建”处理删除后的索引整理,FreshDiskANN 提出的是”图级局部增量重排”;两者是同一问题(图索引如何处理删除)在不同工程取舍下的答案,公开资料里没有证据表明前者已经借鉴后者的做法。
下一篇把视角切到读扩缩与故障恢复:副本、负载与故障恢复——本篇的删除广播、compaction 重建都要跑在一个可能随时失去某个 Query Node 的拓扑上。
参考资料
核心论文
- O’Neil, P., Cheng, E., Gawlick, D., O’Neil, E., The Log-Structured Merge-Tree (LSM-Tree), Acta Informatica 33(4), 1996(软删 + 后台整理的经典源头)。
- Singh, A., Subramanya, S. J., Krishnaswamy, R., Simhadri, H. V., FreshDiskANN: A Fast and Accurate Graph-Based ANN Index for Streaming Similarity Search, arXiv:2105.09613, 2021(预印本,未经 peer review;图索引惰性删除 + StreamingMerge)。
- (方向性引用,非结论依据)In-Place Updates of a Graph Index for Streaming Approximate Nearest Neighbor Search, arXiv:2502.13826, 2025(预印本,未经 peer review;原地删除算法 IP-DiskANN)。
文档
- Milvus Documentation v2.6.x, Delete Entities、Upsert Entities(override / merge 模式内部步骤、autoID 主键轮换)。
- Milvus Documentation v2.6.x, Set Collection
TTL(
collection.ttl.seconds、dataCoord.compaction.expiry.tolerance)。 - Milvus Documentation v2.6.x, Bitset、Streaming Service(Delete 广播)。
- 第 8、10、11、12 篇、系列 index。
返回 系列目录 | 上一篇:一致性模型 | 下一篇:副本与故障恢复
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【向量检索引擎】Knowhere:向量索引执行引擎与插件契约
按官方 Knowhere 文档说明其在 Milvus 中的位置、相对 Faiss 的扩展(bitset、SIMD 选择、二进制度量)、VecIndex 类层次与 IDMAP/IVF/HNSW 等类型,用插件注册、CPU/GPU 分发与 bitset 进查询三张图钉住工程契约,并与 db-frontier/08 的算法细节分工。
【向量检索引擎】Data Node:compaction 与 index build
说明 Milvus 2.6.x 中 Data Node 作为离线 Worker 如何承接 Coordinator 下发的建索引与 compaction,输入输出如何进出对象存储;用最小故事、常见误解与队列积压图说明索引堆积如何拖垮查询新鲜度与资源争用。
【向量检索引擎】混合检索与标量过滤:表达式、bitset 与选择度打穿归并
按 Milvus 2.6.x Bitset、Filter Templating 文档拆解表达式如何变成 bitset 再喂给 Knowhere;用最小故事、bitset 取反细节与选择度打穿 Top-k 归并的示意图,说明为何过滤会改变延迟而不只是改变结果数量;对读 db-frontier/09 的 ACORN、Filtered-DiskANN 争论。
【向量检索引擎】生产排障:召回、延迟、堆积、OOM
用症状到机制的决策树覆盖召回/延迟/堆积/OOM 四类故障,按可见性、段状态、过滤选择度、离线队列、对象存储与副本拓扑逐层定位;用一个跨软删广播与副本改派的最小故事说明为什么同一查询会得到不稳定结果,不含未跑的集群数字。