Elasticsearch 检索基础:倒排、深分页与读写分离
Published in:2026-05-25 | category: 搜推算法

本文是 搜推算法笔记 第 7 篇。索引见 搜推算法 · 知识索引

上一篇:Python 搜推技术栈

Elasticsearch 是工业界最常用的全文检索引擎,擅长倒排检索、BM25 打分与组合过滤。本文从搜推工程视角介绍 ES 的用法,与 ES 存储引擎系列(引擎原理)互补。

1. ES 在搜推中的角色

Elasticsearch 擅长:

  • 全文检索(倒排 + BM25)
  • 组合过滤(类目、价格、店铺、库存状态)
  • 排序与(有限度的)聚合
  • (可选)knn 向量检索

在架构里常见位置:

1
2
3
用户搜索 → ES 全文检索
→ 详情 DB
→ 推荐/语义召回可能在独立向量库

复杂排序通常交给业务侧 LTR 精排——ES 专注「强过滤 + 文本召回」,避免在 ES 内堆叠复杂 function_score。


2. 倒排索引与查询逻辑

1
2
正向:doc → terms
倒排:term → postings (doc_id, tf, positions, ...)

Postings List 结构:

1
2
term "蓝牙" → [ (doc_id=1, tf=2, pos=[3,15]), (doc_id=5, tf=1, pos=[0]), ... ]
↑ doc 频率 ↑ 词频 ↑ 可选:用于短语/邻近查询

大规模索引中 postings 按 doc_id 排序,配合 Skip List / 跳表 加速 AND 合并——两路 postings 求交集时,小 doc_id 侧可跳过不可能匹配的块,避免逐 doc 比较。

TF 存储: 不必存原始词频,可存 (\log(1+tf)) 或量化值以省空间;BM25 计算时再还原。

一次查询:

1
2
3
4
5
1. 分析器分析 Query → tokens
2. 取各 token 倒排链
3. 按 bool 合并(must / should / filter / must_not)
4. 对候选算 BM25
5. 按 score / 业务 sort 取 TopK

分析器意识

中文检索效果很大程度取决于分词——索引时怎么切、查询时怎么切、品牌词是否进词典。索引分析与查询分析不一致会导致「有文档却搜不到」。

工程上,分词策略调整往往是搜索效果优化的第一步。几个关键认知:

索引与查询分析器要一致。 索引时用 ik_max_word,查询时用 ik_smart,或者两边都用同一套——不一致会导致「库里有却搜不到」。

品牌/SKU 进自定义词典。 「华为手机」不能被切成「华为 / 手机 / 华 / 为」——否则精确匹配和 BM25 都会受影响。

多字段策略。 标题、SKU、规格、类目可能用不同 analyzer 或不同 boost 权重。SKU 字段通常需要更细粒度的分词。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"settings": {
"analysis": {
"analyzer": {
"sku_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word",
"filter": ["lowercase"]
}
}
}
},
"mappings": {
"properties": {
"title": { "type": "text", "analyzer": "ik_smart", "search_analyzer": "ik_smart" },
"sku_code": { "type": "text", "analyzer": "sku_analyzer" }
}
}
}

(具体配置因业务而异,关键是理解「分析器决定能不能搜到」。)


3. BM25 与业务排序

ES 默认相似度多为 BM25(公式见 Query 理解与多路召回)。它给出的是 文本相关分,不等于最终业务序。

业务排序常见做法:

  1. 纯 BM25:简单搜索
  2. function_score:相关分 × 业务因子(销量、新鲜度)——详见 ES 相关度评分
  3. 先 ES 召回,再业务 LTR 精排(更现代)

BM25 在 ES 中的计算

ES/Lucene 对每个 shard 独立算 BM25,再归一化合并(shard 间 IDF 用全局统计近似)。Query 侧 boost 可给不同 field 加权:

1
2
3
4
5
6
{
"multi_match": {
"query": "蓝牙耳机",
"fields": ["title^3", "sku_code^5", "description"]
}
}

title^3 表示 title 字段命中词的 TF 分量 ×3——field boost 是乘法,不是简单加固定分。

function_score 与 BM25 的关系:

[
\text{final_score} = \text{BM25}(q,d) \times f(\text{sales}, \text{freshness}, \ldots)
]

乘法模型:相关分接近 0 时,业务因子拉不动;相关分高时,业务因子放大差异。加法模型 (\text{BM25} + \alpha \cdot \text{sales}) 则可能让高销量低相关文档霸榜——搜推场景乘法更常见。


4. 过滤、聚合与性能直觉

queryfilter
目的算相关分是/否过滤
缓存不强调常可缓存 bitset
例子match「蓝牙耳机」term 有库存

能 filter 的别写成 query,通常更快更稳。

聚合(Aggregation)可做类目统计、价格直方图,但大数据量 + 高基数聚合很贵。榜单类需求常改为 预聚合写入,在线直接读。


5. 分页方案对比

方案原理深分页跳页数据一致性适用场景
from/size跳过前 N 条(越翻越慢)支持实时前 1~3 页
scroll快照上下文遍历不支持快照(非实时)数据导出/批处理
search_after游标取下一批不支持实时用户翻页推荐
PIT + search_after时间点快照 + 游标最好不支持快照窗口内一致深翻页 + 一致性要求高

更深入的 PIT 方案见 ES 深度分页:PIT 详解

from / size(浅分页)

1
2
page 1: from=0,  size=20
page 2: from=20, size=20

深分页为什么慢?

ES 是分布式的:每个分片先取 from+size 条,再在协调节点归并排序,丢掉前 from 条。

1
2
from=100000, size=10
→ 大量数据被取出只为扔掉 → CPU / 内存 / 网络都贵

适合前几页。不适合翻到很深。

scroll

快照式遍历/导出 设计,保留上下文。不适合普通用户翻页。

search_after(深翻页推荐)

用上一页最后一条的排序值当游标:

1
2
3
4
5
6
7
8
9
{
"size": 20,
"query": { "...": "..." },
"sort": [
{ "sales": "desc" },
{ "id": "asc" }
],
"search_after": [12345, "goods_999"]
}
from/sizesearch_after
深分页
随机跳到第 N 页容易不擅长
实时用户翻页浅页可用推荐

排序必须加唯一键(如 id asc),否则同分不稳定,游标可能重复/漏数据。


6. PageToken 与查询指纹

用户第一页用「销量排序」,翻到第 3 页时改成「价格排序」,若仍用旧游标 → 结果错乱。

做法:把查询条件 + 排序规则做成指纹(hash),写入 pageToken:

1
2
3
4
5
token = base64({
"fingerprint": hash(query + filters + sort),
"search_after": [...],
"page": 3
})

指纹一致 → 继续 search_after;指纹变了 → 旧 token 失效,从第一页重来。


7. 搜索走 ES、详情走 DB

路径存储原因
搜索/列表ES倒排检索、过滤、排序适合搜索引擎
详情MySQL 等强一致、字段完整、事务、复杂关联

ES 与 DB 之间存在同步延迟 → 短暂不一致。应对:MQ 近实时同步、离线对账修数据、关键字段以 DB 为准。


8. 同步一致性与版本

ES 近实时(默认 refresh 约 1s 量级),不是事务强一致。

Binlog/CDC 同步可能乱序——用 external version:仅当新版本 > 当前版本才写入,否则拒绝(409),避免旧数据覆盖新数据。

定期用 DB 权威数据对比 ES,修复漏同步/错同步。「只靠 MQ」或「只靠对账」都不够,通常两者互补。


9. 常见性能优化

mget 消除 N+1

1
2
坏:for id in ids: GET /index/_doc/id
好:POST /index/_mget bulk ids

只取需要的字段

_source filtering,避免大文档整包返回。

避免深度聚合与深度分页

榜单预计算;翻页 search_after。

向量 knn 与全文检索同集群?

要评估 CPU/内存/线程池争用。全文检索延迟敏感时,向量检索独立更稳(见 向量检索与 ANN)。

ES 三种排序路径对比

路径做法延迟灵活性适用
纯 BM25ES 默认 score 排序简单搜索
function_score相关分 × 业务因子销量/新鲜度加权
ES 召回 + LTR 精排ES 取 TopK → Python/Java 精排中~高现代搜推主流

全文检索走 ES 召回 + 过滤,复杂排序交给业务侧 LTR——ES 专注「强过滤 + 文本召回」,不在 ES 里堆复杂 function_score。


本篇收尾

这是搜推算法笔记工程线的最后一篇。建议从 知识索引 回顾整体脉络。

想深入 ES 引擎原理,可继续阅读 ES 存储引擎系列(段/Translog、相关度评分PIT 深分页)。

Prev:
我的 AI 编程工作流
Next:
我的 AI 模型选型对比