2656 字
约 8 分钟
4
Elasticsearch 检索接口性能优化:从定位瓶颈到大规模搜索优化

Elasticsearch 检索接口性能优化:从定位瓶颈到大规模搜索优化

当一个搜索接口响应越来越慢时,我通常不会第一时间去“加机器”,而是先把整个请求链路拆开:

用户请求
   ↓
服务端
   ↓
Embedding 模型
   ↓
Elasticsearch
   ↓
结果处理
   ↓
返回前端

然后分别判断:

到底是 Embedding 慢?
ES 查询慢?
代码处理慢?
还是高并发把系统拖慢了?

整体优化可以分成三层:

Elasticsearch 查询优化 → 服务端代码优化 → 缓存优化。


一、第一步:先找到到底哪里慢

假设一个搜索接口耗时:

总耗时:1200ms

先把时间拆开:

Embedding:600ms
ES Search:450ms
业务代码:100ms
网络:50ms

这样马上就知道:

真正值得优化的是:

Embedding
+
Elasticsearch

而不是盲目修改 Java 代码。

所以性能优化第一原则就是:

先定位,再优化。

实际项目中可以重点观察:

Embedding 耗时
ES took
接口总耗时
CPU
内存
GC
ES Query Slow Log
线程池

如果 ES 慢,还可以使用 Elasticsearch 的 Profile API 分析具体哪个查询阶段耗时。


二、Elasticsearch 查询优化

ES 往往是整个搜索链路最核心的一层。

如果 ES 查询本身需要 2 秒,那么上层业务代码优化几十毫秒意义并不大。


1. 优化索引 Mapping

首先检查 Mapping 是否合理。

比如:

该 keyword 的字段
却用了 text

不需要搜索的字段
却建立了索引

大量无用字段
都开启了 searchable

这些都会增加:

索引体积
内存压力
查询成本

因此应该遵循:

需要全文搜索
→ text

精确匹配 / 权限 / 状态
→ keyword

纯展示字段
→ 必要时关闭 index

例如权限字段:

{
  "org_id": {
    "type": "keyword"
  }
}

就没有必要使用全文检索。


2. 能用 filter 就不要用相关性 query

假设我们查询:

用户所属组织 = A
文档状态 = published

这两个条件只是:

满足 / 不满足

根本不需要计算 _score

因此应该放到:

bool.filter

而不是:

bool.must

例如:

{
  "bool": {
    "filter": [
      {
        "term": {
          "status": "published"
        }
      },
      {
        "term": {
          "org_id": "A"
        }
      }
    ]
  }
}

这样可以减少不必要的相关性计算,而且 filter 场景也更有利于缓存。

可以简单记成:

要不要算分?

需要算分
→ query

只判断条件
→ filter

3. 不要返回没用的字段

很多接口实际上只需要:

id
title
summary
score

结果却直接:

_source: *

把:

正文
Embedding Vector
附件信息
元数据

全部返回。

尤其 dense_vector 维度比较高时,没有必要每次把向量字段返回给应用层。

应该限制 _source

{
  "_source": [
    "id",
    "title",
    "summary"
  ]
}

减少:

磁盘读取
网络传输
JSON 序列化
内存使用

三、向量搜索优化

如果系统用了:

Embedding + kNN + HNSW

那么大规模数据下,向量搜索通常会成为重要性能瓶颈。


1. 不要无限扩大候选集

例如:

topK = 10

如果为了提高召回率直接:

num_candidates = 10000

虽然可能提高搜索质量,但 CPU 成本也会明显增加。

因为候选集越大:

距离计算越多
   ↓
CPU 越高
   ↓
查询越慢

因此本质上是在做一个平衡:

召回率
   ↑

性能
   ↓

实际项目应该根据业务数据测试合适的:

k
num_candidates
rescore window

而不是越大越好。


2. 先过滤,再做向量检索

比如整个知识库有:

1000 万篇文档

但当前用户实际上只能访问:

10 万篇

如果可以通过:

organization
permission
document_type
status

提前缩小搜索范围,就能够减少大量无效搜索。

所以:

权限过滤
+
向量检索

不仅是安全问题,同样也是性能问题。


四、Embedding 优化

很多人发现搜索慢以后只盯 Elasticsearch,但实际项目里:

Embedding API 本身可能比 ES 还慢。

例如:

Embedding:700ms

ES:150ms

这时候继续优化 ES 已经意义不大。


1. 缓存 Embedding

用户可能经常搜索:

如何申请年假?

如果每次都调用 Embedding Model:

Query
↓
Embedding API
↓
Vector

就是重复计算。

可以使用 Redis:

Key:
embedding:如何申请年假

Value:
[0.12, 0.25, -0.18 ...]

下一次:

Query
 ↓
Redis
 ↓
直接获取 Vector

这样就可以直接绕过 Embedding Model。


五、缓存搜索结果

如果某些查询非常热门,例如:

年假申请
报销流程
员工手册
VPN 使用方法

可以进一步缓存整个搜索结果。

流程从:

Request
 ↓
Embedding
 ↓
ES
 ↓
Result

优化成:

Request
 ↓
Redis
 ↓
Result

例如:

search:年假申请:user_permission_hash

直接缓存:

Top 10 Document IDs

这样热点请求甚至可以完全绕开 Elasticsearch。


为什么缓存要带权限信息?

这是企业搜索里非常重要的一点。

不能简单写:

search:年假申请

否则:

用户 A 搜索
↓
缓存了 A 能看的结果

用户 B 搜索
↓
直接拿到了 A 的缓存

可能产生权限泄露。

因此缓存 Key 通常需要考虑:

Query
+
Organization
+
Permission Version
+
Search Parameters

例如:

search:
年假申请:
org_100:
permission_v3:
top10

六、服务端代码优化

ES 和 Embedding 优化完以后,再看业务代码。

重点检查:

有没有串行调用?
有没有重复请求?
有没有 N+1 查询?
有没有重复 JSON 转换?
有没有查询大量无用数据?

例如:

Embedding
↓
用户权限服务
↓
文档信息服务
↓
ES

如果几个互不依赖的请求全部串行:

200ms + 200ms + 300ms
= 700ms

如果能够并行:

max(200, 200, 300)
≈ 300ms

接口耗时可能直接下降一大截。


七、大规模 ES 会遇到什么瓶颈?

数据量从:

10 万
↓
100 万
↓
1000 万
↓
1 亿

以后,问题就不仅仅是查询 DSL 写得好不好了。

主要会遇到几个瓶颈。


1. 内存瓶颈

HNSW 向量索引需要维护较大的图结构和向量数据。

随着:

向量数量 ↑
维度 ↑
HNSW 参数 ↑

索引体积以及内存、文件系统缓存压力都会不断增加。

例如:

100 万条
×
768 维

和:

1 亿条
×
1536 维

完全不是同一个数量级。

因此大型向量搜索系统必须关注:

索引大小
Page Cache
节点内存
Vector Dimension
Shard Size

2. CPU 瓶颈

kNN 本身属于计算密集型任务。

一次查询可能需要进行大量:

Vector Distance Calculation

例如计算:

Cosine Similarity
Dot Product
L2 Distance

当:

QPS ↑
num_candidates ↑
向量维度 ↑

CPU 消耗就会明显增加。

最后表现为:

CPU 100%
   ↓
查询排队
   ↓
P99 延迟上涨
   ↓
搜索接口越来越慢

所以高并发向量搜索中:

CPU 往往比普通 BM25 搜索更加关键。


3. 分片瓶颈

很多人遇到 ES 性能问题以后第一反应:

多建几个 shard

但 shard 并不是越多越好。

因为一次搜索可能需要:

Query
 ↓
多个 Shard
 ↓
每个 Shard 搜索
 ↓
Coordinator 合并结果

Shard 过多以后:

线程切换增加
网络通信增加
结果合并增加
管理成本增加

反而可能让查询更慢。

所以分片设计本质是在:

单 Shard 数据量

和

Shard 数量

之间做平衡。


八、一套完整的优化思路

以后遇到:

检索接口很慢,你会怎么优化?

不要直接回答:

加 Redis。

可以按照下面的顺序讲:

第一步
先做性能监控
↓
定位到底慢在哪里


第二步
看 Elasticsearch
↓
Mapping
Query DSL
filter
_source
Shard
kNN 参数


第三步
看 Embedding
↓
模型耗时
Embedding Cache


第四步
看业务代码
↓
串并行
重复调用
无效数据


第五步
做缓存
↓
Embedding Cache
Search Result Cache


第六步
看系统资源
↓
CPU
内存
Page Cache
GC
磁盘 IO
线程池

这个顺序非常重要:

性能优化不是“想到什么优化什么”,而是“测量 → 定位 → 优化 → 再测量”。


九、面试时怎么回答?

如果面试官问:

检索接口响应非常慢,你怎么优化?

可以概括成:

我一般先把搜索链路拆开,分别记录
Embedding、ES 查询和业务代码的耗时,
先确认真正的瓶颈在哪里。

如果是 ES 慢,我会先检查 Mapping、
Query DSL、filter、返回字段、分片设计以及
kNN 的 k 和 num_candidates 是否合理。

如果 Embedding 比较慢,会对高频 Query 的
Embedding Vector 做缓存。

对于热点搜索,还可以缓存最终搜索结果,
让部分请求直接绕过 Embedding 和 ES。

在更大规模下,我会进一步关注向量索引带来的
内存和 Page Cache 压力,以及 kNN 距离计算产生的
CPU 压力,再根据 QPS、P99 延迟和召回效果继续调参。

总结

搜索性能优化最终可以记成一句话:

先找到慢在哪里,
再减少没有必要的计算。

整个优化方向可以浓缩成:

ES 查询
→ 少搜

向量搜索
→ 少算

返回数据
→ 少传

Embedding
→ 少调用

热点请求
→ 多缓存

大规模场景
→ 控制 CPU、内存和分片

这就是一个比较完整的 Elasticsearch + 向量搜索性能优化思路。

Elasticsearch 检索接口性能优化:从定位瓶颈到大规模搜索优化
http://www.clxhxhhr.top/posts/638/
作者
clxstart
发布于
2026-09-16
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。