Milvus 标量与向量混合查询:Scalar Filtering 的执行计划分析
在生产级分布式向量数据库 Milvus 中,真实的业务查询几乎从来不是纯粹的孤立向量近邻搜索。在绝大多数企业级 RAG 与多租户场景下,每一次向量检索都伴随着严格的标量过滤表达式(Scalar Filtering Expression):
- 例如:“在
tenant_id == "corp_88"且created_at >= 1735689600且is_deleted == false的数据范围内,搜索与当前提问最相似的 Top-5 向量”。
很多开发者在编写这类查询时,常常遭遇严重的性能两极分化陷阱:
- 有些查询耗时仅需3ms;
- 而有些看起来非常相似的查询,耗时却突然暴涨到180ms 以上,QueryNode 节点的 CPU 瞬间飙到 100%。
为什么标量过滤会在向量检索中带来如此剧烈的性能波动?Milvus 底层的**执行计划(Query Execution Plan)**是如何在前置过滤(Pre-filtering)、迭代过滤(Iterative Filtering)与后置过滤(Post-filtering)之间进行代价模型评估与动态选路的?
标量过滤的三大底层执行引擎机制深度剖析
[ 用户混合查询: Vector_Search + Expr (如 department == 'tech') ] | v +----------------------- Milvus 查询协调与代价优化器 (Optimizer) -----------------------+ | 评估标量过滤选择率 (Selectivity = 符合标量条件的文档数 / 总文档数 N) | +---------------------------------------+-----------------------------------------------+ | +------------------------------+------------------------------+ | (低选择率: Selectivity < 0.5% | | (高选择率: Selectivity >= 20% | 符合条件的行极少, 如 < 5000条)| | 大部分数据都符合条件) v | v [ 引擎 1: 严格前置过滤 Pre-filtering ] | [ 引擎 2: 迭代式图遍历 Iterative Filtering ] - 标量倒排索引生成匹配 ID 位图 (Bitmap) | - 直接进入 HNSW 向量索引多层图搜索 - 仅在位图标记为 1 的小集合内做暴力点积 | - 每次贪心探查邻居节点时,实时计算标量 Expr - 耗时: 极快 (仅需扫描极小位图) | - 耗时: 极快 (充分利用 HNSW 图跳表加速) | v (中等病态选择率: Selectivity 在 0.5% ~ 5% 之间) [ 引擎 3: 暴力扫描回退 (Fallback Brute-force) ] - HNSW 沿图跳跃几十层找不到符合标量的邻居,图遍历彻底断裂! - 引擎被迫退化为全内存暴力扫描,耗时从 3ms 暴涨至 180ms!核心物理瓶颈:为什么中等选择率会导致 HNSW 图断裂?
在 HNSW 图索引中,每个节点维护着 $M$ 个双向邻居指针。图的快速路由极度依赖**“邻居在空间上的密集连通性”**。
当标量过滤条件过滤掉了98% 的数据(仅剩 2% 数据合法)时:
- HNSW 顶层图中的绝大部分邻居节点在标量校验时全被判定为“非法”;
- 贪心路由算法在探测了几步后,发现周围的所有邻居都不符合标量条件,搜索队列被卡死在死胡同(Graph Disconnection);
- Milvus 底层为了保证 Recall 不归零,不得不强行触发底层的段内暴力全表扫描(Segment Brute-force Scan),导致 CPU 算力被消耗殆尽。
1000 万规模下的过滤执行计划实测对照表
测试环境:1000 万条 768 维 HNSW 索引($M=16, efC=200$),测试不同标量选择率下的检索表现:
| 标量过滤条件与命中量 | 实际选择率 (Selectivity) | 底层自动命中的执行计划 | 单次检索 P99 延迟 | CPU 开销 |
|---|---|---|---|---|
| 无过滤条件 (纯向量) | 100.0% | 标准 HNSW 图遍历 | 3.8 ms | 极低 |
| 宽泛过滤 (符合条件 > 500万条) | 50.0% | 迭代式图过滤 (Iterative) | 4.5 ms | 低 |
| 极端精准过滤 (符合条件 500 条) | 0.005% | 严格前置位图过滤 (Pre-filter) | 1.8 ms (极快!) | 极低 |
| 病态中等过滤 (符合条件 10万条) | 1.0% | 图断裂触发暴力回退 | 185.0 ms (性能塌陷!) | 极其高 (CPU 打满) |
生产级避坑与调优三大军规
1. 军规一:多租户场景坚决采用Partition物理分区,而非单一标量过滤
如果你的系统是 SaaS 多租户架构,租户之间数据天然隔离:
- 错误姿势:在同一个巨型 Collection 里使用
expr="tenant_id == 'corp_88'"(落入中等选择率陷阱); - 正确姿势:为每个大客户创建独立的物理 Partition!查询时直接指定
partitions=["corp_88"],Milvus 在物理文件层面直接隔离扫描,单次查询稳稳保持在 2ms 极速。
2. 军规二:必须为高频过滤标量显式建立INVERTED倒排索引
# 生产建表必须显式建标量索引! collection.create_index( field_name="department_id", index_params={"index_type": "INVERTED"} )未建倒排索引的标量过滤,每次都要在磁盘上读取原始字段值,会直接摧毁前置过滤的位图生成性能。
3. 军规三:利用AUTOINDEX与代价优化器参数
在 Milvus 2.4+ 中,推荐使用AUTOINDEX,并在 Proxy 配置文件中将queryNode.iterativeFilterRatio调优为0.2,使得选择率低于 20% 时果断优先采用倒排位图前置过滤,彻底避开 HNSW 图断裂区。
总结
向量检索与关系型标量过滤的结合,是分布式数据库领域最精妙的博弈场。“理解选择率的物理临界点,超小租户用 Partition 物理隔离,高频标量建倒排位图,避开 1% 中等选择率的图断裂陷阱”,才能让你的海量混合检索集群在任意复杂的布尔条件约束下,始终保持如丝般顺滑的巅峰性能。