土法炼钢兴趣小组的算法知识备份

【向量检索引擎】混合检索与标量过滤:表达式、bitset 与选择度打穿归并

文章导航

分类入口
databasestorage
标签入口
#milvus#hybrid-search#filter#bitset#selectivity#acorn#filtered-diskann#vector-engine

源码下载

本文相关源码已整理,共 1 个文件。

打开下载目录 →

目录

设想一个真实场景:一个多租户 RAG 系统里,用户发起一次 search,条件是 tenant_id = 42 AND status = 'published',向量距离要求 Top-5。这条查询在功能测试里跑得又快又准——测试租户的数据只有几百条,status = 'published' 几乎全选。上线后同样的查询模板套到另一个租户身上,那个租户有八十万条历史记录,published 只占 0.3%,同一套 ef_search 参数下响应时间从 20ms 涨到 800ms,个别请求甚至返回不满 5 条。运维第一反应是”向量索引变慢了”,但 Knowhere 的向量距离计算量根本没变——变的是满足过滤条件的候选点在哪、有多少db-frontier/09 已经从算法侧拆开 pre-filter / post-filter / in-filter 与 ACORN 的争论;本文只回答一个更窄的问题:在 Milvus 的段执行与 Knowhere 路径上,一条布尔表达式如何变成参与向量搜索的 bitset,选择度又是怎样打穿第 9 篇里”局部 Top-k 合并成全局 Top-k”的正确性假设

本文是「向量检索引擎」系列第 11 篇(共 19 篇)。→ 系列目录

版本锚定:Milvus v2.6.21 BitsetKnowhereFilter TemplatingData Processing;Knowhere v2.6.18


一、形式化问题(与 frontier/09 对齐)

在满足谓词 \(\varphi\) 的子集上求近邻:

\[ \mathrm{Topk\_filter}(q,k,\varphi)=\arg\min_{\substack{x\in X\\ \varphi(x)=\mathrm{true}}}\mathrm{dist}(q,x) \]

选择度 \(s=|\{x:\varphi(x)\}|/|X|\) 跨多个数量级时,同一套索引参数可以从”很快”变成”召回崩或延迟崩”——开篇的故事就是同一个 ef_search、同一份 HNSW 参数,在 \(s\) 从”接近 1”跌到 \(s \approx 0.003\) 时发生的事。

三种朴素求解策略,本质是在下面三个操作的顺序与位置上做不同选择(第三节会展开对应的引擎直觉与陷阱):

\[ \text{Post-filter:}\quad \{x \in \mathrm{ANN}_{k'}(q) : \varphi(x)\} \quad\text{(先用 ANN 求近似 Top-}k'\text{,再筛 }\varphi\text{)} \]

\[ \text{Pre-filter:}\quad \mathrm{ANN}_{k}\big(q,\ X_\varphi\big),\quad X_\varphi=\{x\in X:\varphi(x)\} \quad\text{(先缩小候选全集,再在子集上求近邻)} \]

\[ \text{In-filter:}\quad \mathrm{ANN}_k\big(q,\ X,\ \text{可见性谓词}=\varphi\big) \quad\text{(检索本身感知 }\varphi\text{,遍历时只接受满足条件的节点进候选堆)} \]

三者在 \(s\to 1\) 时退化为同一个操作(全集近邻),差异只在 \(s\) 较小时才显现——这也是开篇故事里”测试环境测不出问题”的数学原因:测试租户的 \(s\) 接近 1,三种策略的结果几乎无差别。


二、引擎落点:表达式如何变成一个 bitset

2.1 官方 bitset 语义:不是”谁满足条件标 1”这么简单

Bitset 文档用一个 8 个实体的段举例:4 个实体在 ts=100 插入,4 个在 ts=200 插入,其中 2 个在 ts=300 被删除;假设只有主键 [1,3,5,7] 满足某个属性过滤条件。文档给出的组合步骤是:

  1. 先算出 filter_bitset:满足条件的位置标 1,得到 [1,0,1,0,1,0,1,0]
  2. filter_bitset 取反,得到 [0,1,0,1,0,1,0,1]——因为最终喂给搜索阶段的 bitset,语义是”这一位为 1 就跳过”,而 filter_bitset 原始语义是”这一位为 1 就是满足条件、要保留”,两者刚好相反。
  3. 与删除位图 del_bitsetOR:只要某一位在”不满足过滤”或”已删除”任一 bitset 里为 1,最终 result_bitset 该位就是 1(跳过)。
  4. result_bitset 交给 Knowhere 的索引查询接口。

这个”先取反再 OR”的顺序不是实现细节的随手一提——它直接决定了过滤条件与删除标记能不能用同一套位运算叠加。跳过第 2 步、直接把 filter_bitsetdel_bitsetOR,会把”满足过滤”的行也标记成”跳过”,结果是过滤条件被读反。

2.1.1 源码钉点:ConvertPredicateToFilteredBitset(milvus v2.6.21

Segcore 执行算子 PhyFilterBitsNode 在拿到谓词位图后,调用同文件内的转换函数,把「1=满足谓词」翻成「1=排除」——与文档步骤 2 同构,并额外把 NULL/UNKNOWN 并入排除集:

// milvus-io/milvus v2.6.21
// internal/core/src/exec/operator/FilterBitsNode.cpp
ConvertPredicateToFilteredBitset(TargetBitmapView data,
                                 TargetBitmapView valid,
                                 const size_t size) {
    // FilterBitsNode outputs a filtered-row bitset: 1 means excluded.
    // UNKNOWN/NULL must be excluded together with FALSE.
    if (valid.all()) {
        data.flip();
        return true;
    }
    data.flip();
    TargetBitmap invalid(valid);
    invalid.flip();
    data.inplace_or(invalid, size);
    valid.set();
    return false;
}

删除侧与 Growing 的叠加入口见第 7 篇:SegmentGrowingImpl::mask_with_delete。Knowhere 侧接收掩码的虚接口是 IndexNode::Search(..., const BitsetView&)(第 8 篇)。

2.2 把例子摆成一张表:删除恰好命中了一条满足过滤的行

光看步骤描述容易漏掉一个真实存在的交互:如果被删除的行本身满足过滤条件,会发生什么?用官方场景的设定(4 个实体 ts=100 插入、4 个 ts=200 插入,主键 [1,3,5,7] 满足过滤条件)补一组具体删除位置,把整条链路的每一步都摆出来(下表的删除位置是教学取值,用来让例子覆盖”删除行与过滤行重叠”这种更容易出错的情况,不是官方文档的原始数值):

pk 插入时刻 是否满足 φ filter_bitset 取反后 是否已删除 del_bitset result_bitset(1=跳过)
1 ts=100 1 0 (ts=300 删除) 1 1
2 ts=100 0 1 0 1
3 ts=100 1 0 0 0
4 ts=100 0 1 0 1
5 ts=200 1 0 0 0
6 ts=200 0 1 (ts=300 删除) 1 1
7 ts=200 1 0 0 0
8 ts=200 0 1 0 1

最后一列只剩 pk 3、5、7 对应的位是 0(参与向量计算),pk 1 虽然满足过滤条件,但因为已被删除,result_bitset 该位仍是 1(跳过)。这正是”先取反、再与删除位图 OR“这一步顺序存在的意义:过滤条件和删除标记各自独立产生一份 1,只要任一份为 1,这一行就必须在向量计算里被跳过——两个互不相关的”跳过理由”共享同一份最终位图,检索侧不需要分别处理”因为不满足过滤跳过”和”因为已删除跳过”两套逻辑。

2.3 引擎骨架

综合 BitsetKnowhere 文档(第 8 篇已引 Knowhere 的软删细节),混合检索在引擎里的骨架是:

flowchart LR
  expr["Boolean expression<br/>tenant_id = 42 AND status = 'published'"]
  ast["Parse to AST<br/>(Filter Templating for huge IN-lists)"]
  scan["Scalar index / row scan per segment"]
  fbit["filter_bitset<br/>1 = satisfies φ"]
  flip["Invert bits<br/>1 = does NOT satisfy φ"]
  delbit["del_bitset<br/>1 = soft-deleted"]
  orop["OR"]
  result["result_bitset<br/>1 = skip during ANN traversal"]
  kw["Knowhere IndexNode::Search(..., BitsetView)"]
  merge["Segment / shard / Proxy merge"]
  expr --> ast --> scan --> fbit --> flip --> orop
  delbit --> orop
  orop --> result --> kw --> merge

Filter Templating 文档补充了 AST 之前的一层成本:超大 IN 列表、非 ASCII(如大量 CJK)字面量会显著增加解析开销,官方提供占位符 + filter_params 的模板化机制缓解。这是解析层的字符串与 AST 成本,与图中 result_bitset 之后的 ANN 侧选择度问题正交——排障时要先区分”卡在解析”还是”卡在过滤后候选不够”,两者的修复手段完全不同(模板化 vs 调整策略)。


三、选择度如何打穿 Top-k 归并

3.1 第 9 篇的假设在哪里成立

第 9 篇证明过:若各分区两两不交且覆盖全集,各分区局部 Top-\(k\) 的并集再取 Top-\(k\),等于直接对全集取 Top-\(k\)。这个证明依赖一个隐含前提——参与排序的候选集合,就是最终要在其上求 Top-k 的那个集合,中间不能插入一次”先排序、后过滤”的操作。

过滤恰恰是插在排序之后的那个操作。ANN 索引(HNSW 的图游走、IVF 的 posting list 扫描)在检索时不知道 \(\varphi\),它按距离给出的局部候选顺序,与”该候选是否满足 \(\varphi\)“完全无关。如果某个 segment 里满足 \(\varphi\) 的实体恰好都排在按距离算的第 50 名之后,而这个 segment 只取了局部 Top-\(k'=10\) 的候选去做过滤,那么这些满足 \(\varphi\) 的真实近邻根本没有出现在候选列表里,之后再怎么合并也补不回来。

下面是一个教学示意(数值为构造出的最小反例,非实测):设 \(k=2\),两个 segment 各自按距离由近到远给出前若干候选,括号标注是否满足 \(\varphi\)

flowchart TB
  subgraph SegA["Segment A:ANN 局部候选(按距离升序)"]
    a1["c1 dist=0.10 φ=false"]
    a2["c2 dist=0.12 φ=false"]
    a3["c3 dist=0.30 φ=true"]
  end
  subgraph SegB["Segment B:ANN 局部候选(按距离升序)"]
    b1["c4 dist=0.20 φ=true"]
    b2["c5 dist=0.22 φ=false"]
  end
  a3 --> keepA["Segment A 过滤后只剩 c3(dist=0.30)"]
  b1 --> keepB["Segment B 过滤后只剩 c4(dist=0.20)"]
  keepA --> merge["Proxy 合并 k=2:{c4, c3}"]
  keepB --> merge
  merge --> caveat["若局部 top-k' 只取了 2 个候选(c1、c2),\nc3 根本不会出现在候选列表里——\n合并结果会缺 c3,而不是排序错误"]

这张图想说明的是两种不同的失败模式,容易被混为一谈:

3.2 局部候选数 \(k'\) 该取多大:一个工程下界估计

开篇故事里”同一套 ef_search,延迟从 20ms 涨到 800ms”不是凭空发生的,可以用一个粗略但可核对的模型估计量级。做一个简化假设:候选是否满足 \(\varphi\) 与它在 ANN 检索中的距离排名互相独立(即过滤字段与向量语义无关)。在这个假设下,“从局部候选流中找到 \(k\) 个满足 \(\varphi\) 的候选”近似一个负二项过程——每看一个候选,它满足 \(\varphi\) 的概率是 \(s\),要连续凑够 \(k\) 个”成功”,期望需要看的候选数是:

\[ E[k'] \approx \frac{k}{s} \]

代入开篇故事的数字:\(k=5\),测试租户 \(s\approx 1\)(几乎全选)时 \(E[k']\approx 5\),局部候选和最终结果几乎重合,索引正常工作;生产租户 \(s\approx 0.003\)\(E[k']\approx 1667\)——如果引擎仍按测试环境调好的 \(k'\)(例如几十)去检索,大概率filter 之后剩不到 \(k\) 个,这正是”个别请求返回不满 5 条”的来源。把 \(k'\) 调到 1667 量级能修复”凑不够”的问题,但图游走或 posting list 扫描的候选数从几十涨到近两千,检索本身的开销随之上涨——这就是延迟从 20ms 涨到 800ms 的量级来源之一(这是一个工程下界估计,不是本文的实测数字,也不是 Milvus 官方给出的容量公式)。

这个模型的局限必须显式指出:真实业务里过滤字段常常和向量语义相关(例如”未下架商品”的向量分布本身就和”已下架商品”不同),独立性假设不成立时,\(E[k']\approx k/s\) 可能严重低估或高估所需候选数——这也是”选择度”这个单一标量,永远不能完全刻画混合检索难度的原因:同样的 \(s=0.003\),如果满足 \(\varphi\) 的点在向量空间里恰好聚成一簇,图游走可能很快找到入口后就稳定命中;如果这些点在空间里随机散布,则大概率要遍历远超 \(k/s\) 的候选。

3.3 三种朴素策略在引擎里的直觉

策略 引擎直觉 选择度陷阱
Post-filter 先 ANN 取较大 \(k'\),再套 bitset/表达式 \(s\) 很小时局部 \(\mathrm{Top}\text{-}k'\) 被滤空,全局归并凑不满 \(k\)(3.1 节的图)
Pre-filter 先得满足 \(\varphi\) 的行集,再在子集上搜或扫 \(s\) 很大时子集仍巨大,暴力不可行
In-filter / 图感知 检索游走时感知谓词(ACORN 等) 实现与索引类型相关;不是所有 index_type 等价

db-frontier/09 给出的选择度分段(\(s<10^{-4}\) pre-filter、\(10^{-2}\le s<0.3\) in-filter、\(s\ge 0.3\) post-filter)是教学示意的经验分段,不是 Milvus 的实测阈值——本篇不重复该分段的数值,只强调结论:Milvus 段路径上,Knowhere 带 bitset 的查询更接近”在索引遍历中跳过无效行”,具体是”跳过后继续游走”还是”先物化候选再排序”随索引类型变化(第 8 篇已说明 HNSW 走的是前者)。不要假设”加了 filter 只是结果少一点、延迟不变”。

3.4 本机微基准:post-filter 放大 \(k'\)(非 Milvus bitset 计时)

hnswlib 模拟「先取 \(k'=\min(N,\max(\lceil k/s\rceil,k))\),再按随机谓词留下命中」的 post-filter 路径。脚本:reproduce/run_exp_11.sh。环境:Linux 6.8、2 核、Python 3.12.3、numpy 1.26.4、hnswlib;\(N=10^4\)\(d=128\)\(n_q=50\)\(k=10\)\(M=16\)ef_search=200,5 轮中位。

\(s\) \(k'\) 中位延迟 (ms) 平均命中数(≤\(k\) 凑不满 \(k\) 的查询比例
1.0 10 7.92 10.00 0.00
0.1 100 8.43 8.10 0.60
0.01 1000 33.67 8.40 0.58
0.001 10000 209.25 10.00 0.00

读法:\(s\) 下降时,为凑候选必须放大 \(k'\),延迟随之上升;在 \(s=0.1/0.01\) 时即使放大后仍有大量查询凑不满 \(k\)(随机谓词 + 有限图召回)。\(s=0.001\)\(k'=N\),退化为近似全量扫描,延迟跳到约 209 ms,但命中数被拉满——对应「用扫描换凑齐 Top-\(k\)」的代价。这不是 FilterBitsNode/Knowhere 的墙钟,只验证第三节的选择度直觉可在本机复现。


四、工程实践:没有官方选择度估计器时怎么自测

官方文档没有公开”引擎自动估计 \(s\) 并切换 pre/post/in-filter”这类机制(第三节已明确 Milvus 段路径的行为更接近固定策略的 post-filter 式跳过,不是自适应切换)。这意味着,当开篇故事发生时,排查者需要自己动手确认问题出在哪一层,而不是靠猜。一个可复现、不依赖大规模集群的最小验证流程:

  1. 单独测选择度:对同一个过滤表达式,先用一次纯标量 query(不带向量距离)统计满足条件的行数,除以 collection 总行数得到 \(s\) 的实测值——这一步不涉及 ANN,纯粹是标量过滤链路(第二节的 filter_bitset 生成),可以确认”选择度是不是真的很低”,排除”过滤表达式写错、误伤了大量行”这类更简单的问题。
  2. 对照 Flat 基线:用同样的过滤条件在小样本上跑一次 Flat(暴力)搜索,记录延迟与召回,作为”不依赖图结构剪枝效果”的参照——如果 Flat 也慢,说明瓶颈是标量扫描或候选数量本身很大,不是 HNSW 图遍历的问题(对照第 2、8 篇 Flat 作金标准的角色)。
  3. 单变量扫描 \(k'\)(如果引擎暴露该参数):固定 \(\varphi\)、固定 \(k\),只改变局部候选数或等价的 ef_search,观察延迟与”合并结果是否凑够 \(k\) 条”两个指标的变化曲线——曲线开始”凑够 \(k\) 条”的那个 \(k'\) 附近的值,可以和 3.2 节的 \(E[k']\approx k/s\) 下界互相印证:如果实测所需 \(k'\) 远高于 \(k/s\),说明过滤字段与向量语义相关(3.2 节末尾指出的独立性假设失效场景),需要重新审视过滤字段的分布,不只是调大参数。
  4. 对比不同选择度租户:如果条件允许,选一个高 \(s\) 租户和一个低 \(s\) 租户跑同一套参数,横向对比延迟——这一步能直接确认”延迟差异是否随 \(s\) 变化”,而不是其它无关因素(如索引是否已建好,第 3、10 篇)造成的巧合。

这四步的共同原则是:先用最简单的手段(纯标量统计、Flat 基线)把选择度问题和其它问题(索引未就位、表达式写错、一致性水位)分开,再决定要不要动 \(k'\) / ef_search 这类参数。跳过前两步直接调参数,容易把”过滤字段选得不好”误判成”索引参数不对”。


五、与 ACORN、Filtered-DiskANN 的衔接(不重写算法)

第 8 篇已经指出 Knowhere 的软删掩码是”嵌在图遍历过程中判断可见性,被删节点仍可作为中继继续游走”——这正是过滤感知图索引要解决的核心难题:只允许遍历满足条件的节点,会不会把图切成互不连通的碎片

把两条路径的假设与代价并排摆开,能看出它们各自把复杂度转移到了哪一步:

维度 ACORN Filtered-DiskANN
优化目标 任意谓词下都不严重掉召回,牺牲一些索引密度 已知谓词分组下延迟/召回最优,节省索引密度
构建期假设 不需要预先知道谓词分布,稠密化对所有谓词一视同仁 需要在构建时枚举可能的 label 组合,用于 RobustPrune 选边
依赖的传统组件 HNSW 图结构 + 两跳剪枝,不依赖额外倒排 HNSW 图结构 + label-aware 边选择,隐含一份 label→节点的倒排
已知局限 \(\gamma\) 越大索引膨胀越明显,论文未覆盖谓词分布随时间漂移的场景 谓词集合在线新增(如新租户)时,旧图的边选择未必适配新分组,可能需要重建
与 Partition 的相似度 无直接对应;不依赖预声明分区 假设可枚举,思路上接近”给每个分区单独优化边”,但作用在图边而非物理切分

两者的分叉点,本质是”谓词感知这件事应该发生在构建期还是运行期、应该覆盖已知谓词还是任意谓词”这个设计选择的两种答案——ACORN 选择”运行期任意谓词都尽量兼顾”,代价是索引密度;Filtered-DiskANN 选择”构建期只优化已知谓词”,代价是新谓词出现时的适配成本。

Milvus 官方 Knowhere 文档并未描述”删除掩码之外,还对过滤谓词做图稠密化或 label-aware 剪枝”这类机制——第 8 篇确认的只是”bitset 掩码嵌在遍历过程中,被跳过的节点仍可作为中继”。这意味着:Knowhere 的 HNSW 在高选择度低通过率场景下,是否会遇到 ACORN 论文里描述的”图碎片化、召回骤降”问题,取决于默认构建参数下图的连通冗余度,官方文档没有给出等价于 ACORN 稠密化的保证。本系列不假设 Milvus 已经内建了这层保护——这是第七节的开放问题之一,也是”读文档不能替代读源码”的一个具体例子。

分区(Partition)能缓解一部分问题,但只对预先声明、按业务字段切分的等值类过滤有效(如 tenant_id):它让查询少扫一些 segment,相当于粗粒度的 pre-filter,不能替代任意布尔表达式的 in-filter 能力。


六、常见误解

误解一:过滤只会让结果变少,不会影响延迟。 第二节的 bitset 掩码是在索引遍历过程中逐候选判断的,不是查询完成后再筛一遍列表;对图索引而言,被跳过的节点仍要占用一次遍历/距离计算。第三节进一步说明:选择度低时,延迟涨在”要遍历多少节点才能凑够满足条件的候选”,不是”多算一步过滤”这么轻量。

误解二:filter_bitset 里标 1 的行,就是最终会参与向量计算的行。 第 2.1 节的官方例子说明恰好相反:filter_bitset1 表示”满足过滤条件”,但喂给 Knowhere 的 result_bitset 语义是”1 就跳过”,中间必须经过一次取反再与删除位图做 OR。读 Bitset 文档示例时如果跳过这步取反,很容易把过滤条件理解反。

误解三:只要把局部候选数 \(k'\) 调得足够大,选择度问题就解决了。 3.1 节的图已经说明,扩大 \(k'\) 只解决”候选量不足”,不解决”各 segment 过滤后剩余候选数不均衡导致合并偏向某个 segment”的顺序失真问题;而且 \(k'\) 越大,每层归并的序列化与内存开销越大(第 9 篇的归并放大链),不是没有代价的旋钮。

误解四:选择度 \(s\) 是一个只取决于过滤条件本身的固定数字,同一个 \(s\) 在不同场景下的检索代价应该差不多。 3.2 节的 \(E[k']\approx k/s\) 模型依赖”候选是否满足 \(\varphi\) 与距离排名独立”这一假设,而这个假设在真实业务里经常不成立。同样 \(s=0.003\),如果满足条件的点在向量空间里聚簇,图游走可能很快稳定命中;如果这些点随机散布在空间各处,代价会远超按独立假设估出的下界。把选择度当成唯一的难度指标、忽略过滤字段与向量语义的相关性,是过滤性能预测里最容易踩的第二个坑。


七、学术谱系、工程间隙与开放问题

7.1 谱系

主题 代表 work 与 Milvus 的关系
Predicate-agnostic 图索引 Patel et al., ACORN, SIGMOD 2024 图稠密化 + 两跳剪枝;Knowhere 文档未描述等价机制
谓词分组图索引 Gollapudi et al., Filtered-DiskANN, WWW 2023 label-aware RobustPrune;假设谓词可枚举,与 Partition 的适用边界类似
系统落地 Wang et al., Milvus, SIGMOD 2021 bitset + 段级路由是本篇讨论的工程基座

三者的分叉点在于”谁负责让过滤后的图仍然连通”:ACORN 在构建时对任意谓词都稠密化;Filtered-DiskANN 在构建时按已知谓词分组重排边;Milvus 官方文档目前只公开了”运行时掩码 + 遍历时跳过”这一层,没有公开构建时的谓词感知优化。

7.2 工程间隙

7.3 开放问题

  1. Knowhere 默认构建参数下的 HNSW,在高选择度、低通过率谓词下是否会出现 ACORN 论文描述的图碎片化——这是一个可以通过构造对抗性谓词实测召回来验证的问题,官方文档未给出定量保证。
  2. 段级自适应:何时该从 post-filter 自动切到 pre-filter 或扩大 \(k'\),切换阈值是否应该按 segment 实时估计的选择度动态调整,而不是全局静态配置?
  3. 多过滤器 AND/OR 组合下,filter_bitset 的物化顺序与 SIMD 友好布局如何优化——这属于第 2.1 节位运算链的性能细节,公开资料未展开。
  4. 与 RAG 权限过滤叠加时:强过滤(如按用户 ACL 过滤文档)是否应该默认绑定 Strong 一致性,以避免第 3.1 节候选顺序失真与一致性陈旧同时发生、故障定位更困难?
  5. 3.2 节的 \(E[k']\approx k/s\) 只是独立性假设下的下界,能否找到一种运行期低成本的信号(例如统计过滤字段与已知簇标签的相关性)来判断”这次查询的独立性假设有多不成立”,从而给出比固定下界更贴近真实代价的 \(k'\) 建议——这类”选择度感知的候选数自适应”目前没有公开的 Milvus 实现可以引用,只能作为工程方向记录。

八、小结

三句话小结:

  1. 表达式过滤在引擎里落成一个 result_bitset——它是 filter_bitset 取反后与删除位图 OR 的结果,语义是”这一位为 1 就在向量遍历时跳过”,方向弄反是最容易踩的坑。
  2. 选择度打穿的不是”结果变少”这么简单,而是”先排序、后过滤”的假设在过滤介入时失效:局部 Top-\(k'\) 可能根本没检索到满足条件的候选,合并结果会系统性缺失或偏向候选剩余多的 segment。
  3. ACORN、Filtered-DiskANN 分别用构建期稠密化与谓词分组回答”过滤后图还连通吗”,Milvus 公开文档目前只描述了运行时掩码这一层,两者之间的差距是本篇留下的开放问题,不是已解决的既有事实。

下一篇专门收束可见性本身:一致性模型——过滤解决”哪些行参与计算”,一致性解决”这次计算能看到哪个时间点之前的数据”,两者经常在 RAG 权限场景里同时踩坑。


参考资料

核心论文

  1. Patel, L., Kraft, P., Guestrin, C., Zaharia, M., ACORN: Performant and Predicate-Agnostic Search Over Vector Embeddings and Structured Data, SIGMOD 2024(图稠密化 + 两跳剪枝;本篇仅引用其问题定义与量级方向,不代入 Milvus 具体实现)。
  2. Gollapudi, S. et al., Filtered-DiskANN: Graph Algorithms for Approximate Nearest Neighbor Search with Filters, WWW 2023(label-aware RobustPrune,与 Partition 的适用边界对照)。
  3. Wang, J., Yi, X., Guo, R., et al., Milvus: A Purpose-Built Vector Data Management System, SIGMOD 2021。

文档与源码

  1. Milvus Documentation v2.6.x, Bitset(filter_bitset 取反、del_bitset OR 组合的完整示例)。
  2. Milvus Documentation v2.6.x, KnowhereFilter TemplatingData Processing
  3. milvus-io/milvus v2.6.21internal/core/src/exec/operator/FilterBitsNode.cppConvertPredicateToFilteredBitsetPhyFilterBitsNode);SegmentGrowingImpl::mask_with_delete
  4. zilliztech/knowhere v2.6.18IndexNode::Search(..., BitsetView)
  5. 本机微基准:reproduce/run_exp_11.sh(post-filter 选择度模型;非 Milvus 计时)。
  6. db-frontier/09 混合过滤检索(pre/post/in-filter 选择度分段、ACORN 技术细节)。
  7. 第 7、8、9 篇系列 index

返回 系列目录 | 上一篇:Data Node | 下一篇:一致性模型

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-07-12 · database / storage

【向量检索引擎】Knowhere:向量索引执行引擎与插件契约

按官方 Knowhere 文档说明其在 Milvus 中的位置、相对 Faiss 的扩展(bitset、SIMD 选择、二进制度量)、VecIndex 类层次与 IDMAP/IVF/HNSW 等类型,用插件注册、CPU/GPU 分发与 bitset 进查询三张图钉住工程契约,并与 db-frontier/08 的算法细节分工。


By .