高 困难
ES性能调优#
一句话答案#
ES 调优三板斧:写入优化(bulk 批量 + refresh_interval 调大 + 关闭副本)、查询优化(filter 缓存 + routing 避免跨分片 + search_after 替代深分页)、集群优化(分片规划 + 冷热分离 + JVM 堆 ≤ 31GB)。
核心要点
写入优化#
| 手段 | 效果 | 场景 |
|---|---|---|
| Bulk 批量写入 | 减少网络开销 | 所有写入场景 |
| refresh_interval: ”30s” | 减少 segment 生成频率 | 写入量大时 |
| number_of_replicas: 0 | 写入时不同步副本 | 全量导入阶段 |
| translog.durability: async | 异步刷盘 | 允许少量丢失 |
| 合理 Mapping | 不需要搜索的字段设 index:false | 所有场景 |
// 全量导入时的临时设置
PUT /my_index/_settings
{
"refresh_interval": "-1",
"number_of_replicas": 0
}
// 导入完成后恢复
PUT /my_index/_settings
{
"refresh_interval": "1s",
"number_of_replicas": 1
}json查询优化#
1. Filter 替代 Must(不需要评分的条件)
→ filter 结果缓存为 Bitset,重复查询直接命中
2. Routing 路由(避免全分片扫描)
GET /orders/_search?routing=user_123
→ 只查询 user_123 所在的那个分片
3. 深分页优化
❌ from=10000, size=10 (每个分片返回 10010 条再合并;默认 index.max_result_window=10000,from+size 超过会直接报错)
✅ search_after + PIT (Point in Time)
4. 字段裁剪
"_source": ["title", "price"] // 只返回需要的字段
5. 预计算
提前聚合结果存入 summary 索引,查询时直接取plaintext深分页问题#
from + size 的问题:
from=10000, size=10
→ 每个 Shard 取 Top 10010
→ Coordinating 节点合并 5×10010=50050 条(假设 5 个分片)
→ 取第 10001-10010 条
→ 内存爆炸 + 响应慢
解决方案:
方案1: search_after (推荐)
{"search_after": [上一页最后一条的 sort value], "size": 10}
→ 每个 Shard 只取 10 条
方案2: Scroll API(官方已不推荐用于深分页,改用 search_after + PIT;scroll 未正式废弃)
适合全量导出,不适合实时搜索plaintext集群级优化#
JVM 设置:
-Xms 和 -Xmx 设为相同(避免 resize)
堆内存不超过 Compressed OOP 阈值(常说 31GB;官方说 26GB 在多数系统安全、部分系统可到 30GB,以启动日志确认)
堆不超过物理内存 50%,剩下留给 OS Page Cache 缓存 Lucene 文件
7.11 起默认按节点角色和内存自动设置堆,官方建议生产多数情况用默认值
分片规划:
单 Shard: 10-50GB
每节点默认最多 1000 个非 frozen Shard(cluster.max_shards_per_node);旧的「每 GB heap 20 个」经验值新版文档已不再给出
避免超大 Shard(恢复慢)和超多小 Shard(Master 压力大)
冷热分离:
热数据 → SSD 节点(7.10 起用 data tier 角色:node.roles: [data_hot])
冷数据 → HDD 节点(node.roles: [data_cold];更早版本用自定义属性 node.attr.box_type)
通过 ILM (Index Lifecycle Management) 自动迁移(按 _tier_preference 分配)plaintextSegment Merge 优化#
问题:大量小 Segment 影响查询性能(每次查询需要遍历所有 Segment)
策略:
- max_num_segments: force merge 到指定数量(写入完成后执行)
- merge.scheduler.max_thread_count: 控制合并线程数
- 不在高峰期做 force merge
POST /my_index/_forcemerge?max_num_segments=1plaintext面试回答(2分钟版)
ES 调优分写入和查询两方面。写入优化:用 bulk 批量提交减少网络开销,全量导入时把 refresh_interval 设为 -1 且副本设为 0,导完再恢复,吞吐量可以提升数倍。查询优化:不需要评分的过滤条件放 filter(有 Bitset 缓存),按 routing 路由避免全分片扫描,深分页必须用 search_after 替代 from+size(否则每个分片取 from+size 条再合并,内存爆炸)。集群层面:JVM 堆不超过 Compressed OOP 阈值(约 26~31GB,7.11 起默认自动设置堆),给 OS 留一半内存用于 Page Cache 缓存 Lucene 文件。冷热分离用 ILM 自动管理——热数据在 SSD,超过 7 天移到 HDD。Segment 合并在低峰期 force merge 减少 segment 数量提升查询效率。
追问与易错
追问方向:
- “为什么堆不要超过约 30GB?”→ JVM Compressed OOP 临界点(官方口径:26GB 大多安全、部分系统可到 30GB,以启动日志 compressed ordinary object pointers [true] 为准),超过后指针从 4 字节变 8 字节,等效容量反而下降
- “深分页为什么慢?”→ 每个 Shard 都要取 from+size 条做全局排序,Shard 越多越慢
- “怎么监控 ES 性能?”→ _cat/nodes, _cat/shards, _nodes/stats + Prometheus/Grafana
- “force merge 有什么风险?”→ IO 密集,高峰期做会影响查询,且不可中断
易错点:
- ❌ “堆内存越大越好”——超过 Compressed OOP 临界点(约 30GB)性能反而下降(失去 Compressed OOP)
- ❌ “search_after 能跳页”——只能逐页向后翻,不支持跳到第 N 页
- ❌ “refresh_interval=0 就是实时”——0 不是关闭,-1 才是关闭;0 表示每次写入都 refresh(性能极差)