Elasticsearch中search_after工作原理及单唯一字段场景使用问题
一、现有查询能否直接适配search_after功能?
你当前编写的是search_after首次分页请求的基础结构,但无法直接完成完整分页流程,还需要调整两处:
- 确认
id字段的属性:如果你用的是业务侧写入的自定义id字段,且该字段开启了doc value,排序逻辑是可用的;如果该字段是直接映射ES内置的_id字段,会存在性能问题,不建议直接使用。 - 后续分页请求需要追加参数:每次拉取下一页时,需要在查询体中加入
search_after参数,取值为上一页返回结果中最后一条文档的sort字段返回值。
二、仅单个唯一字段的场景如何调整?
首先澄清一个误解:官方文档要求的是排序字段的组合取值全局唯一,并非必须使用多个唯一字段。你当前仅id为唯一字段的场景,完全可以正常使用search_after,仅需做以下调整:
- 如果你的
id是业务自定义、已开启doc value的唯一字段:无需调整排序规则,直接在后续分页请求中追加search_after参数即可。
示例第二页查询代码(假设上一页最后一条文档的id为10):{ "size": 10, "query": { "match" : { "title" : "elasticsearch" } }, "sort": [ {"id": "asc"} ], "search_after": [10] } - 如果你的
id是直接映射ES内置的_id字段:需要新增一个存储id值的自定义字段,比如命名为doc_id,写入文档时将_id的值同步写入doc_id,将doc_id设置为开启doc value的keyword类型,后续排序使用doc_id字段即可。你可以通过 ingest 管道配置自动完成值复制,无需修改业务写入逻辑。
三、tie-breaker相关原理解释
tie-breaker的作用
tie-breaker是为了解决排序值重复导致的顺序不稳定问题。举个通俗的例子:你给学生按考试分数排序,两个学生都考了90分,仅按分数排序的话,这两个学生的前后顺序每次查询都可能变化,分页时就会出现重复或者丢失数据。这时候加一个全局唯一的学号作为tie-breaker,分数相同的按学号升序排,整个排序规则就固定了,不会出现顺序波动。
单个唯一值作为tie-breaker有没有问题?
单个全局唯一值作为tie-breaker完全没有问题,甚至你可以直接只用这一个字段作为排序字段,就像你当前按id升序排序的逻辑——因为id全局唯一,每个文档的排序值都不重复,不会出现顺序不稳定的问题。
你在官方文档看到的问题,是直接使用ES内置_id字段作为tie-breaker的特有问题,并非单个唯一值的通用问题:
排序规则中需使用每个文档取值唯一的字段作为tiebreaker,否则排序值相同的文档排序顺序不确定,可能导致查询结果缺失或重复。_id字段虽然每个文档取值唯一,但不推荐直接作为tiebreaker使用:需要注意的是search_after会寻找完全或部分匹配所提供的tiebreaker值的第一个文档,因此如果某文档的tiebreaker值为"654323",而你传入的search_after值为"654",仍然会匹配到该文档并返回其之后的结果。此外_id字段默认关闭doc value,对其排序需要在内存中加载大量数据。因此建议(在客户端或通过set ingest处理器)将_id字段的内容复制到另一个开启了doc value的字段中,使用该新字段作为排序的tiebreaker。
简单来说内置_id的问题只有两个:
- 默认关闭doc value,排序时需要把全量
_id加载到内存,数据量大时性能极差,甚至引发内存溢出。 - ES对
_id有特殊的前缀匹配逻辑,可能导致search_after参数匹配到前缀相同的无关文档,出现结果偏移。
只要你用的是自定义的开启了doc value的唯一id字段,就不会有上述问题。
内容的提问来源于stack exchange,提问作者user_12

