ES 默认配置开箱可用,但生产流量下瓶颈各不相同:有人卡在 JVM GC,有人卡在磁盘 IO,有人卡在 segment 太多。本文沿写入与查询两条主线,拆解堆内存、系统缓存、段合并与查询缓存的调优要点,帮助你找准自己的瓶颈。
1. 性能模型与瓶颈定位
1.1 四类资源与典型瓶颈
| 资源 | 典型瓶颈信号 | 排查方向 |
|---|---|---|
| CPU | 查询/写入线程利用率高 | 分片热节点、合并风暴 |
| 内存 | GC 频繁、OOM | Heap 过小、fielddata |
| IO | 磁盘等待高、刷新慢 | 段合并、translog fsync |
| 网络 | 协调节点吞吐不足 | 分片过多、跨机房延迟 |
1.2 先定位再调优
curl -s 'http://localhost:9200/_cat/nodes?v&h=name,cpu,load_1m,heap.percent,ram.percent,disk.used_percent,uptime'
生产惯例是保持 5-10 分钟基线监控,观察 GC 次数、慢查询比例、段数量与磁盘 IO 后,再动手调参。不要照抄别人的参数,同样的集群在不同流量下最优配置完全不同。
1.3 单分片压测
# 以只读压测隔离查询瓶颈
curl -s 'http://localhost:9200/_nodes/stat?pretty' | jq '.nodes | to_entries[0].value.indices.query_cache'
先跑单索引单分片压测,确认单分片吞吐,再按分片数外推集群容量。这能区分是分片路由开销还是单分片本身慢。
2. JVM Heap 调优
2.1 Heap 大小的黄金法则
# jvm.options
-Xms8g
-Xmx8g
Heap 建议设为物理内存的一半,且不超过 32GB。超过 32GB 会触发 JVM 启用压缩指针失效的 OOP 重定位,内存利用反而下降。
| 物理内存 | 建议 Heap | 留给 Page Cache |
|---|---|---|
| 16GB | 8GB | 8GB |
| 32GB | 16GB | 16GB |
| 64GB | 31GB | 33GB |
2.2 Heap 用在哪
JVM Heap(默认一半内存)
├── 索引与查询工作缓冲
├── fielddata(聚合排序,已尽量移出)
├── segment 常驻结构(FST 词表等)
└── 请求队列与任务上下文
另一半留给 OS Page Cache
└── 缓存磁盘上的 term dictionary、doc values 等
ES 大量数据实际由 OS Page Cache 承载,Heap 不是越大越好。把 heap 撑满会导致 GC 长暂停,反而让查询卡顿。
2.3 GC 监控
curl -s 'http://localhost:9200/_nodes/stats/jvm?pretty' | jq '.nodes | to_entries[0].value.jvm.gc'
关注 young GC 频率与 old GC 耗时。生产调优手段包括:用 G1GC、控制并发合并线程、限制 fielddata、避免深分页大排序。若 old GC 频繁,通常不是 Heap 太小,而是请求把大量数据拉进堆内。
3. 操作系统与文件系统缓存
3.1 Page Cache 的作用
Lucene 索引文件通过 mmap 映射到 OS Page Cache,读请求优先命中缓存,二次查询远快于首次。因此保留足够空闲内存给 OS,是 ES 最划算的优化。
# 查看 page cache 命中情况
free -h
# 建议预留 50% 内存给 page cache,而非全部塞给 JVM
3.2 文件系统选择
| 文件系统 | 优势 | 劣势 |
|---|---|---|
| ext4 | 兼容性最好 | 碎片化 |
| xfs | 大文件友好、并发高 | 缩容困难 |
| 本地 SSD | 低延迟 | 容量小 |
生产经验:数据目录用独立磁盘,避免日志与数据争抢 IO;关闭 atime(noatime 挂载)减少写放大。
3.3 虚拟内存与交换
# 禁止 swap 或尽量降低 swappiness
sudo sysctl -w vm.swappiness=1
sudo sysctl -w vm.max_map_count=262144
swap 会让 ES 进程换页抖动,遇到 OOM killer。max_map_count 不足会导致 mmap 失败报 mlock 相关错误,ES 官方文档要求 262144。
4. 索引写入性能
4.1 写入链路
客户端批量请求
│
▼
主分片写入内存 buffer + translog
│ refresh(默认 1s)
▼
buffer 生成不可变段,可被查询
│ flush(translog fsync + 段落盘)
▼
磁盘段进入合并流程
4.2 refresh 与 translog 调优
| 参数 | 默认 | 调整场景 |
|---|---|---|
| refresh_interval | 1s | 日志写入拉长到 30s+ |
| translog.durability | request | 可降为 async |
| translog.sync_interval | 5s | 异步周期 |
| index.translog.flush_threshold_size | 512MB | 大内存提高 |
curl -X PUT 'http://localhost:9200/log_index/_settings?pretty' \
-H 'Content-Type: application/json' \
-d '{
"index": {
"refresh_interval": "30s",
"translog.durability": "async"
}
}'
注意 async translog 在节点宕机时最多丢 sync_interval 内的数据,日志类可接受,交易类必须保持 request。
4.3 批量写入与最佳大小
# 批量写 5000 条 × 每条 1KB
curl -X POST 'http://localhost:9200/log_index/_bulk?pretty' \
-H 'Content-Type: application/json' \
--data-binary @batch.ndjson
批量大小需要实测:单个 batch 控制在 5-15MB,过小浪费网络往返,过大会触发大段合并。经验公式是 1000-5000 条或 5-15MB,先小后大逐档测试。
4.4 写入并发与线程池
# 观察写入线程池队列
curl -s 'http://localhost:9200/_cat/thread_pool/write?v&h=name,active,queued,rejected'
write 线程池队列满会拒绝请求并返回 429。调优方向是控制客户端并发、增大批量、拉开 refresh,而不是盲目提高线程数。
5. 查询性能优化
5.1 查询缓存机制
| 缓存 | 缓存内容 | 失效时机 |
|---|---|---|
| Query Cache | filter 结果集位图 | 段变化 |
| Request Cache | 聚合结果 | 段变化 |
| Shard Request Cache | 命中的 filter 聚合 | 索引写入 |
curl -X PUT 'http://localhost:9200/blog/_settings?pretty' \
-H 'Content-Type: application/json' \
-d '{"index.requests.cache.enable": true}'
filter 子句天然走 Query Cache,把常用过滤条件从 must 挪到 filter 是立竿见影的优化,详见《Query DSL 与相关性打分》。
5.2 Doc Values 与 Fielddata
Doc Values 是面向列存的数值/排序数据,聚合、排序、脚本都依赖它,默认开启。text 字段无法用 doc values 做聚合,需要 keyword 子字段。
| 存储 | 用途 | 内存 |
|---|---|---|
| 倒排索引 | 全文检索 | 磁盘 + 少量内存 |
| Doc Values | 排序/聚合/脚本 | 磁盘(OS 缓存) |
| Fielddata | 旧版 text 聚合 | 堆内存(避免) |
生产规则:永远用 doc values 而非 fielddata;大字段聚合尽量拆分到独立索引。
5.3 慢查询诊断
curl -X PUT 'http://localhost:9200/blog/_settings?pretty' \
-H 'Content-Type: application/json' \
-d '{
"index.search.slowlog.threshold.query.warn": "2s",
"index.search.slowlog.threshold.fetch.warn": "1s",
"index.indexing.slowlog.threshold.index.warn": "5s"
}'
慢日志分级 warn/info/debug,记录查询与 fetch 两个阶段。定位慢分片后配合 _search profile 看是 filter 慢、collect 慢还是 fetch 大 _source 慢。
6. Segment 与 Merge 调优
6.1 段数量的代价
每个查询要遍历所有段,段越多查询越慢,同时 merge 占用 IO。refresh 频繁的索引段数量会飙升。
curl -s 'http://localhost:9200/_cat/segments/blog?v&h=shard,segment,docs.count,size.memory,committed,searchable'
6.2 Merge 策略调参
| 参数 | 默认 | 作用 |
|---|---|---|
| index.merge.scheduler.max_thread_count | 核数/2 | 合并并发 |
| index.merge.policy.max_merged_segment | 5GB | 最大段大小 |
| index.merge.policy.segments_per_tier | 10 | 每层段数 |
| index.merge.policy.deletes_pct_allowed | 33% | 删除占比触发合并 |
curl -X PUT 'http://localhost:9200/blog/_settings?pretty' \
-H 'Content-Type: application/json' \
-d '{"index.merge.scheduler.max_thread_count": 1}'
写入高峰时降低 merge 并发,避免合并风暴与写入抢 IO;低谷时恢复默认。
6.3 Force Merge 只读索引
# 对只读索引合并到单段
curl -X POST 'http://localhost:9200/blog_2026/_forcemerge?max_num_segments=1'
日志等不再写入的索引 force merge 到 1 段,查询提速明显。注意 force merge 会产生大段,后续少量更新会触发全段重写,因此只对真正只读的索引执行。
6.4 段合并与删除
频繁更新产生大量删除标记,deletes_pct_allowed 超过阈值会触发合并清理。索引设计上避免高频 update 同一文档,批量更新场景优先考虑 append 语义加时间版本。
7. 集群级调优与慢日志
7.1 分片大小与节点规格
| 部署规模 | 节点规格 | 分片策略 |
|---|---|---|
| 小业务 | 2 核 8G × 3 | 单分片起 |
| 中型 | 8 核 32G × 5 | 节点数 × 2 |
| 大型 | 16 核 64G × 10+ | 节点数 × 3-4 |
分片越小调度越灵活但聚合开销大,越大吞吐高但恢复慢,需要在基准测试中权衡。详情见《集群分片与高可用架构》的分片规划章节。
7.2 集群级动态设置
{
"persistent": {
"indices.breaker.total.limit": "60%",
"indices.query.bool.max_clause_count": 2048,
"cluster.routing.allocation.node_concurrent_recoveries": 4
}
}
熔断器 total.limit 防止查询把堆打爆;max_clause_count 限制 bool 子句数,防止超大查询拖垮节点。
7.3 压测与基线
# 使用 rally 或自研压测脚本,记录基线
# 观察指标:QPS、P95 延迟、GC 次数、合并队列
每轮调整只改一个参数,记录前后基线对比,避免多参数耦合导致无法归因。调优是渐进过程,参数表要随版本与数据量更新。
8. 总结
| 环节 | 要点 |
|---|---|
| 瓶颈定位 | 先监控 CPU/内存/IO/网络,再动手调参 |
| JVM Heap | 物理内存一半且不超 32GB,预留 Page Cache |
| OS 缓存 | 保留空闲内存给 mmap,禁 swap,noatime 挂载 |
| 写入链路 | refresh 拉长 + translog async + 批量 5-15MB |
| 查询优化 | filter 走缓存、Doc Values 聚合、慢日志定位 |
| Segment | 控制段数量,force merge 只读索引 |
| 集群参数 | 熔断器、并发恢复、bool 子句上限 |
| 压测基线 | 单分片外推,一次只调一个参数 |
性能调优没有银弹,核心是让数据尽量待在 OS Page Cache,让查询走缓存与 Doc Values,让段数量保持在合理区间。先建立基线再逐项优化,比照搬参数更有价值。底层倒排结构见《倒排索引与分词原理》,集群参数见《集群分片与高可用架构》。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。