向量搜索过滤:在相关性、结果数量和延迟之间做取舍

向量搜索过滤:在相关性、结果数量和延迟之间做取舍

精确搜索与近似搜索对过滤的反应不同。理解前置过滤、后置过滤及文档级权限过滤,才能正确解释 HNSW 的遍历成本和返回结果。

精确检索、前置过滤、后置过滤、ACORN-1 与文档级权限过滤的差异示意。
过滤放在哪里,会改变什么。原创示意,非运行结果。

语义相似还不够

电商检索可以找到外观或语义相似的商品,但用户还会限定价格、品牌、库存或评分。向量距离解决“像不像”,过滤条件解决“是否符合要求”。只有两者一起工作,结果才真正可用。

这也意味着不能只测一次不带条件的近邻检索,就推断业务查询的延迟。过滤位置、选择性和索引类型都会改变工作量。

精确向量搜索:先减少需要比较的文档

原文介绍两种精确搜索路径:把 dense_vector 字段的索引类型设为 flat,使 kNN 采用精确搜索;或者使用 script_score 中的向量函数计算分数,后者可配合不同索引类型。

精确搜索会比较候选中的每一个向量。先过滤掉不需要的文档,就能减少距离计算,而不会遗漏过滤后集合中的近邻。条件越严格,剩余向量越少,精确搜索有时反而比近似搜索更合适。

作者给出的经验值是:过滤后少于约一万条文档时考虑精确搜索;BBQ 比较更快,可在少于约十万条时考虑。这里是文章中的经验界线,并不是 API 承诺,也不应覆盖硬件、维度、量化方式与版本差异。如果查询长期都高度受限,建索引时就可以评估 flat 类型,而不一定总选 HNSW。

近似搜索:过滤信息不在同一套结构里

HNSW 等近似检索结构,通过减少昂贵的向量比较,在大规模数据中寻找近邻。价格、类别或权限等属性,则可能存储在词典、倒排表和 doc values 等其他索引结构中。

因此,系统需要把普通过滤条件的结果与向量图搜索结合。最直观的选择有两个:先找近邻再过滤,或把过滤信息提前交给向量搜索。

后置过滤:检索过程不变,结果可能不够

后置过滤先取相似度最高的 k 个候选,再删除不满足条件的结果。好处是向量搜索过程不用知道过滤条件;代价是最后可能不足 k 条。把 k 调大能增加机会,但仍不能保证有足够结果。

下面保留原文的 knn 查询示例。注意 filter 是 bool 的成员,而不在 knn 内:

{
  "query": {
    "bool": {
      "must": {
        "knn": {
          "field": "image-vector",
          "query_vector": [54, 10, -2],
          "k": 5,
          "num_candidates": 50
        }
      },
      "filter": {
        "term": {"file-type": "png"}
      }
    }
  }
}

使用顶层 knn 搜索时,原文要求用显式的 post_filter:

{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, 2],
    "k": 5,
    "num_candidates": 50
  },
  "post_filter": {
    "term": {"file-type": "png"}
  }
}

不能把一个普通查询或过滤随意并列在顶层,然后认为它必然起到后置过滤作用。原文提醒,kNN 结果可能与其他查询组合,语义与显式后置过滤不同。

前置过滤:知道哪些结果有效,不代表只走有效节点

前置过滤先确定满足条件的文档,Lucene 用 BitSet 保存这组文档。HNSW 搜索在准备把候选放入结果集时,检查它是否属于有效集合。

但是,不匹配条件的节点仍可能需要探索和计算距离。因为它可能连接着最终要找的邻居,直接停止探索会破坏图上的可达路径。作者把它比作开车去加油站:没有加油站的道路仍可能是到达目的地的必经之路。

在原文描述的普通遍历中,前置过滤需要访问节点、检查资格,并丢弃无效候选,为凑够合格结果可能做更多工作。因此,“加过滤就一定更快”不适用于这类近似搜索。

顶层 kNN 的前置过滤写在 knn.filter 内:

{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, -2],
    "k": 5,
    "num_candidates": 50,
    "filter": {
      "term": {"file-type": "png"}
    }
  }
}

查询形式也支持内嵌过滤:

{
  "query": {
    "knn": {
      "field": "image-vector",
      "query_vector": [-5, 9, -12],
      "k": 5,
      "filter": {
        "term": {"file-type": "png"}
      }
    }
  }
}

这些是 DSL 结构示例,不包含建索引与数据准备。真实字段的向量维度、相似度及映射必须匹配;示例的三维向量不能直接查询任意高维 embedding 字段。

两类前置过滤优化

第一类优化是及时转向精确搜索。如果过滤后只剩很少的向量,与其在图中绕行,不如把剩余向量全部比较。原文说明 Lucene 与 Elasticsearch 会自动采用这种优化。

第二类是 ACORN-1。它减少对不满足条件的向量本身进行比较,同时进一步探索相关邻接关系,避免简单跳过节点造成路径中断。关键在于改变探索方式,而不是假设可以安全删除所有不匹配节点后继续按原算法行走。

原文展示的基准中,当只有 2% 的向量通过过滤,引入 ACORN-1 后查询耗时约为此前的 55%。这个结果属于作者引用的特定 benchmark,没有在本稿环境复测;不能推广成任意数据上都降低 45% 延迟。

原文还链接到 Elastic Benchmark Analytics 的历史延迟图。图表序列名含 nightly-so_vector-knn-search-10-50-css-filter%2-force-merge-latency,以毫秒展示 p50、p90、p99 和 p100 延迟的时间序列;图中日期覆盖 2025 年 5 月至 8 月,并标出了 2025-06-16 的 ACORN 修复事件。它是作者链接的基准历史图,不是本稿运行或复测结果;只解读为来源图表,不把它当作受控的同条件 A/B 实验。

Elastic Benchmark Analytics 原始延迟时间序列图:nightly-so vector kNN 10-50,CSS filter 2,force merge,显示毫秒单位的 p50、p90、p99 和 p100 分位延迟,日期约从 2025 年 5 月至 8 月。
原文链接的 Elastic 基准历史图,显示 nightly vector-kNN 过滤延迟的四个分位数。来源:Elastic Benchmark Analytics dashboard;图像直链 原图。© Elasticsearch B.V. All Rights Reserved。来源未标开放许可;依据另行取得的许可使用。历史监控图不是本稿性能测试。

DLS 也会参与过滤

文档级安全(Document Level Security,DLS)通过角色关联的查询,限定用户能读取哪些文档。匹配结果可缓存成 BitSet,并用于包装底层 Lucene reader,使查询只把用户有权访问的文档视为可见。

对近似 kNN 来说,这组授权文档会与已有前置过滤条件共同约束候选,因此具有前置过滤相同的性能取舍和优化机会。对精确搜索来说,权限范围越小,需要比较的向量也可能越少。

业务端的 file-type 过滤不自动变成可靠的权限机制;真正的授权边界应由受控的权限配置建立,而不是让调用者自由选择是否带上一个 term 条件。

按目标结果选择方案

如果需要在符合条件的集合中寻找近邻,前置过滤更贴近目标,但还要测召回和耗时。如果可以接受对已有候选继续筛选,后置过滤的过程更直接,却要接受结果不足。过滤非常严格时,精确搜索值得纳入比较。

本稿仅静态核对全文和 JSON 结构,没有建立索引、写入向量或运行性能测试。评估应覆盖实际维度、过滤选择性、k、候选数、权限范围与目标版本。

来源、署名与版本说明

原作者:Carlos Delgado。原文由 Elastic Search Labs 发布。本文为中文翻译与技术整理,保留原作者和来源;编辑补充与版本边界说明已在文中标明。

原文发表于 2025-09-03。原文页末标注 © Elasticsearch B.V. All Rights Reserved;页面未声明适用于全文的开放许可。本文不将原文表述为开放授权作品,原作权利仍归权利人所有。

核对日期:2026-10-08。原文未固定完整 Elasticsearch 或 Lucene 发行号。向量维度、相似度、索引类型、查询 DSL 和权限配置均须与目标版本核对;过滤阈值与基准数字仅为原文经验和特定测试结果,不作保证。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容