向量分页搜索

最近更新时间:2026-03-02 16:22:42

我的收藏
本文介绍分页搜索的基础实现方法。关于分页搜索的更多详情可以参阅此文档

分页搜索(实时)

search_after是 ES 提供的一种高效、实时的深度分页解决方案。与传统的 from+ size分页在处理大量数据时性能急剧下降不同,也不同于主要用于离线导出的 scroll API,search_after通过使用上一页最后一个结果的排序值作为“游标”来获取下一页,从而实现高性能的深度翻页。
原理:
首次查询时,必须指定一个或多个唯一或高区分度的字段进行排序(例如 _id)。
在返回的结果中,获取最后一个文档的排序值。
下一次查询时,将这些排序值作为 search_after参数传入,ES 会直接从该“游标”之后开始检索。
优势:
高性能:避免了 from分页时对前 N 条结果的重复排序和遍历,查询时间基本恒定,与页码深度无关。
实时性:查询总是基于最新的索引数据,无需像 scroll那样维持一个数据快照上下文。
资源友好:服务端不长期占用资源,查询完成后即释放。
适用于深分页:是解决成千上万页以后数据检索的标准方法。
另外,下面的案例,分页结果可能不稳定,如果要确保分页过程的一致性和稳定性,search_after必须与 Point-in-Time (PIT) API 结合使用。PIT 会创建一个数据视图的快照,在分页期间保持索引状态不变,防止因数据变更导致翻页结果错乱或丢失,具体可以参阅此文档
//Search_after
GET /book-index/_search
{
"knn": {
"field": "title_vector",
"query_vector": [0.1, 0.2, 0.3 ...],
"k": 2, // 指定要返回的结果数量
"num_candidates": 2 // 限制搜索节点返回的候选结果数量
},
"sort": [
{"id": "asc"},
{"price": "asc"}
]
}

GET /book-index/_search
{
"knn": {
"field": "title_vector",
"query_vector": [0.1, 0.2, 0.3],
"k": 2,
"num_candidates": 2
},
"search_after": ["1002","20"], // 输入上一步sort的结果值
"sort": [
{"id": "asc"},
{"price": "desc"}
]
}

分页搜索(离线)

Scroll API 是 ES 为一次性检索大量数据(例如全量导出、批量离线处理)而设计的深度分页机制。它的核心思想是创建一个数据快照,然后通过一个可滚动的“游标”(scroll_id)分批获取数据,直到遍历完所有结果。
核心特点与适用场景:
非实时:Scroll 在首次查询时创建索引的快照,后续翻页都基于这个快照,期间的数据变更不会影响滚动结果。
顺序遍历:主要用于从头到尾的顺序遍历,不支持跳页。
资源占用:需要在集群中维护搜索上下文直到超时,会消耗内存和计算资源。
典型场景:数据迁移、全量备份、离线分析、需要处理索引中全部或大量符合条件文档的任务。
以下是一个完整的 Scroll 翻页流程示例
步骤 1:初始化 Scroll 查询
//Search Scroll
POST book-index/_search?scroll=1m
{
"size": 20, // 每页文档数量
"knn": {
"field": "title_vector",
"query_vector": [0.1, 0.2, 0.3],
"k": 100, // 需要覆盖您想滚动获取的总文档数
"num_candidates": 500
}
}
scroll=5m:设置搜索上下文的存活时间为 5 分钟。这个时间需要足够您处理完一批数据并获取下一批。
size:指定每次滚动返回的文档数量(每批的大小)。
响应:除了返回第一批 hits外,还会在响应体中包含一个 _scroll_id。这是后续翻页的关键。
步骤 2:使用 scroll_id获取后续批次
使用上一步返回的 _scroll_id来获取下一批结果。
//输入上一步获取到的 scroll id
POST /_search/scroll
{
"scroll" : "1m",
"scroll_id" : "XXXXXXXXXX" // scroll_id换成上一步返回的值
}
每次调用都会返回下一批 size个文档,并返回一个新的 _scroll_id(即使相同,也应使用最新的)。
重复此步骤,直到返回的 hits数组为空。
步骤 3:清理 Scroll 上下文(非常重要)
处理完成后,必须主动释放资源。
DELETE /_search/scroll
{
"scroll_id": "XXXXXXXXXX" // scroll_id换成上一步返回的值
}