平台工程师最常听到的抱怨是「ES
又红了」:有时是写入被拒绝,有时是搜索超时,有时是 heap
持续高位却查不到明显慢查询。第一反应往往是加机器或调
refresh_interval,但若没把症状对应到
breaker、Segment 数量、mapping
字段数、分片元数据开销
中的哪一层,调参容易南辕北辙。本文用一张
症状→机制
决策树把「感觉不对」翻译成「该看本系列哪一篇、哪个
API」,再展开五类高频故障的检查单。不含未跑的千节点集群数字。
本文是「全文检索引擎」系列第 17 篇(共 18 篇)。→ 系列目录
版本锚定:Elasticsearch 8.x Circuit breaker、Fix common cluster issues;机制结论回链本系列第 7–14 篇。
一、先分清四种「不好」
| 症状标签 | 可能机制 | 优先篇章 |
|---|---|---|
| 写入拒绝 / 429 | circuit breaker、translog 压力、磁盘 watermark | 12、本节二 |
| 搜索慢 / 超时 | 分片过多、大字段 fetch、聚合 fielddata、DFS | 10、11、13、本节五 |
| heap 高 / OOM | 段过多、fielddata、聚合超内存 breaker | 7–8、14、本节二 |
| 结果不对 / 不全 | refresh 窗口、软删未 merge、分片路由错误 | 7–8、12、11 |
四类可叠加:例如 refresh_interval
过短同时推高 CPU 与段数,表现为
又慢又占内存——决策树先定主因,再合并次要因素。
二、症状 → 机制决策树
flowchart TD
symptom["Observed symptom?"]
symptom -->|"write rejected / 429"| writeGate{"breaker type in logs?"}
writeGate -->|"parent / request"| aggIssue["Aggregation or huge query buffer<br/>见第 14 篇, 调小 size / 用 DV"]
writeGate -->|"fielddata"| fdIssue["text field used for agg/sort<br/>改 keyword + doc_values"]
writeGate -->|"no breaker, disk"| diskIssue["disk watermark / flood stage<br/>删索引或扩盘"]
symptom -->|"heap high steady"| heapGate{"many small segments?"}
heapGate -->|"yes"| refreshIssue["refresh too frequent or bulk too small<br/>见第 12、7 篇"]
heapGate -->|"no"| mappingGate{"field count exploding?"}
mappingGate -->|"yes"| mapIssue["dynamic mapping explosion<br/>见第 14 篇"]
mappingGate -->|"no"| shardGate{"shard count >> data size?"}
symptom -->|"search slow"| slowGate{"_search slow log shows fetch?"}
slowGate -->|"fetch dominates"| fetchIssue["stored fields / _source huge<br/>见第 10 篇, source filtering"]
slowGate -->|"query dominates"| queryIssue["too many shards or heavy agg<br/>见第 11、13 篇"]
slowGate -->|"only some queries"| explainPath["run profile + explain<br/>本节五"]
symptom -->|"missing recent docs"| nrtGate{"refresh_interval?"}
nrtGate -->|"default 1s ok but still miss"| replicaIssue["routing / wrong index / alias<br/>见第 11 篇"]
nrtGate -->|"set -1 manual refresh"| visIssue["expected NRT delay<br/>见第 12 篇"]
shardGate -->|"yes"| shardIssue["shard overhead > query parallelism gain<br/>见第 11 篇, architecture/42"]
图的作用是把排查 分层:heap 高且段很多时,先查 refresh 与 bulk 批次,而不是先调 BM25 参数(第 6 篇)。
三、circuit breaker:JVM 里的硬顶
Elasticsearch 用 circuit breaker 限制单次请求或子系统可申请的堆内存,防止 OOM 拖垮整个节点。官方文档列出的常见类型包括(名称以 8.x Reference 为准):
| Breaker | 典型触发场景 | 机制回链 |
|---|---|---|
| parent | 子 breaker 总和逼近上限 | 全局保险丝 |
| request | 单查询聚合桶过多、terms size 过大 |
第 14 篇聚合 |
| fielddata | 对 text 字段排序/聚合(旧路径) |
应用 keyword + doc_values |
| in_flight_requests | 并发搜索过多 | 客户端限流 |
检查单:
- 读节点日志中的
CircuitBreakingException与 breaker 名称。 - 对聚合查询检查
size、shard_size、是否composite分页(第 14 篇)。 - 禁止对 analyzed
text做聚合;mapping 审查用_field_caps或 index mapping API。 - 临时缓解 不等于 根因修复:调大 breaker 限额只推迟 OOM(Elastic 文档明确 breaker 目的是保护节点)。
sequenceDiagram
participant C as Client
participant N as Data node
participant CB as Circuit breaker
C->>N: search with huge terms agg
N->>CB: estimate heap for buckets
alt over limit
CB-->>C: 429 CircuitBreakingException
else ok
N-->>C: response
end
四、refresh 过频:可搜性与段爆炸的交换
refresh 使内存中新 Segment 对搜索可见(第 12
篇),不 等价于 fsync 持久化。默认
refresh_interval 为 1s 适合近实时搜索,但在
高写入 场景会导致:
- 大量 小 Segment(第 7 篇),merge 跟不上;
- 每个 refresh 触发 searcher 重建,CPU 抖动;
- 与第 15 篇混合索引叠加时,向量段同样频繁暴露。
| 场景 | 建议方向 | 风险 |
|---|---|---|
| 日志批量导入 | 写入期 refresh_interval=-1,结束后恢复 |
导入期不可搜 |
| 搜索 SLA 秒级 | 保持 1s 或 wait_for 有控刷新 |
段数上升 |
| 只读索引 | 停止 refresh,force merge 后封存 | 失去 NRT |
症状:merge 线程池队列长、段数监控持续升、查询延迟随写入波动。对照第 8 篇 merge 与第 12 篇三者矩阵,勿用缩短 refresh 来「解决」写入慢——往往适得其反。
五、大字段与 mapping 爆炸
5.1 大字段
_source/ stored 过大:query 阶段可能很快,fetch 阶段拖慢(第 13 篇 query+fetch);用_sourcefiltering、stored_fields控制返回列(第 10 篇)。- 高基数字符串:每个唯一值消耗词典与
doc_values 空间;日志里的
trace_id若映射成keyword且聚合,桶数爆炸(第 14 篇)。
5.2 动态 mapping 爆炸
动态模板在 JSON 日志场景下可为每个新键建字段——字段数可达千级,cluster state 与 每个段 的元数据成本上升。第 14 篇已述失效模式;排障时:
GET index/_mapping看字段数增长曲线。- 用
dynamic: false或strict、runtime field 替代随意建列。 - 区分 搜索字段 与
仅存储字段(
enabled: false对象)。
六、分片过多:并行度变负资产
每个分片是一个完整 Lucene 索引,自带:
- 段文件、词典常驻内存片段;
- 集群状态中的一份 shard routing 元数据;
- 查询时协调节点 fan-out(第 13 篇)。
architecture/42 讲过「shard 过多」的架构陷阱;机制层补充:单分片数据量过小 时,并行收益抵不过固定开销。经验性工程判断(非 benchmark):许多团队以 每分片 tens of GB 级 为规划起点,须按数据量、heap、查询并发 重算——具体数字随版本与硬件变化,本文不给虚假最优 shard 数。
检查:
_cat/shards看是否大量空分片或均匀性极差;- 搜索慢日志里 同一查询 是否扇出数百 shard;
- 重索引缩 shard 前评估 reindex 时间与双写窗口。
七、慢查询:profile 与 explain
7.1 工具分工
| API | 回答什么 |
|---|---|
profile: true |
时间花在哪个查询子句、哪个 collector |
explain |
某文档 BM25 分量(第 6 篇 IDF/TF/norm) |
| Slow search log | 生产长尾样本 |
教学示例(须在真实集群对 小索引 执行;以下为 DSL 形态,非伪造输出):
GET my-index/_search
{
"profile": true,
"query": { "match": { "body": "circuit breaker" } },
"size": 5
}GET my-index/_explain/doc-id
{
"query": { "match": { "body": "circuit breaker" } }
}读 profile:若 match
子句耗时高,回第 5、9 篇看 postings 与词项频率;若
fetch 高,回第 10 篇 _source;若
knn 高,外链 vector-engine
调参,不在此复述 HNSW。
7.2 与第 13 篇查询路径对照
协调节点时间 = 路由 + 分片并行 query + fetch +
reduce。DFS 全局词频(可选)增加一轮往返——高基数字段上 IDF
更准,但 慢查询日志 里会出现额外
dfs 阶段;仅在确有评分偏差时开启。
7.3 translog 与磁盘 watermark
写入拒绝有时 不是 breaker:
- flood stage watermark(磁盘用量超阈值)会阻止分片分配与写入;
- translog 所在盘满导致主分片失败(第 12 篇)。
日志里若无 CircuitBreakingException,查
_cluster/health、_cat/allocation
与节点磁盘指标。这与 architecture/42
的容量故事衔接,但 机制层 仍是 Lucene
IndexWriter 无法提交 commit。
7.4 bulk 批次与 refresh 联动
| 现象 | 可能原因 | 动作 |
|---|---|---|
| 写入 QPS 低但 CPU 高 | 过小 bulk + 默认 1s refresh | 增大 bulk、临时 -1 refresh |
merge 拒绝 / too many segments |
refresh 过频 + 写入不停 | 降 refresh、限并发写入 |
| 导入后搜索仍慢 | 未 force merge 且段极多 | 评估只读索引 merge(第 8 篇代价) |
八、常见误解
误解一:circuit breaker 触发说明 ES 坏了,应立刻调大限额。 Breaker 是 保护机制;应缩小聚合、改 mapping 或减并发,而非永久关闭。
误解二:refresh 越快数据越安全。 refresh 只影响 可搜;持久化靠 translog + flush(第 12 篇)。过快 refresh 损害 merge 与 heap。
误解三:慢查询就是 BM25 慢。
大量案例慢在 fetch 大
_source、terms 聚合
或 分片扇出,profile 一步可区分。
误解四:加副本能缓解写入 breaker。 副本分担读,但 写入仍经主分片;写入拒绝要从 bulk 批次、mapping、磁盘查。
误解五:OpenSearch 与 ES 排障完全不同。 第 16 篇:内核同源;breaker、refresh、段模型检查单 共用。
九、开放问题
- 统一可观测模板:将 segment 数、refresh 耗时、breaker 触发、慢查询 top-N 钉在同一 Grafana 行,并与 Lucene merge 线程池关联——社区方案多,缺乏与 ES 版本绑定的单一标准。
- 向量 + 全文混合索引的资源隔离:第 15 篇;同一节点 kNN 与重聚合争 heap 时,仅靠 breaker 是否足够,尚无公开容量公式。
- 自动识别「分片过多 vs 查询写法差」:需结合慢日志与 shard 统计的启发式规则,厂商文档多为手工清单。
十、小结
三句话小结:
- 先用 症状→机制决策树 把问题归到写入 breaker、refresh/段、mapping/大字段、分片扇出或 NRT 可见性,再打开对应篇章,而不是统一归咎于「ES 慢」。
- circuit breaker 与 refresh 过频 是 heap 与 CPU 两侧最常见的可修复根因;profile + explain 区分 query 与 fetch 与 BM25 分量。
- OpenSearch 与 Elasticsearch 排障机制同构(第 16 篇);日志专属场景另见 observability/08,不在此替代。
选型与系列收束见 第 18 篇。
参考资料
- Elasticsearch 8.x Reference, Circuit breaker settings、Fix common cluster issues、Profile API、Explain API.
- 本系列第 7–14 篇机制结论。
- architecture/42 搜索架构(分片规划叙事,机制回链)。
返回 系列目录 | 上一篇:OpenSearch 与 Solr | 下一篇:选型与阅读地图
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【全文检索引擎】全文引擎全景:架构叙事与 Lucene 内核之间的一层
定位 Lucene 9.x/10.x 库内核与 Elasticsearch 8.x 服务层相对 architecture/42、storage/29、向量引擎与 PG GIN 的分工;以最小索引故事建立坐标系,交代倒排索引学术谱系与 18 篇路线图。
【全文检索引擎】BM25 与 Similarity:公式如何落到 Lucene
从 Robertson & Zaragoza (2009) 的 BM25 公式出发,拆解 Lucene BM25Similarity 如何把 TF 饱和、文档长度归一与 IDF 落到 postings 与 norms;对照 TF-IDF 工程差异,并划定与学习排序的生产边界。
【全文检索引擎】IndexWriter 与 NRT:从缓冲到可打开的 Reader
拆解 Lucene IndexWriter 的 RAM 缓冲、flush 出段与 DirectoryReader.open(IndexWriter) 近实时语义;说明 Searcher 刷新与「刚写入即可搜」在库层与 ES refresh 层各自保证什么。
【全文检索引擎】DocValues 与 stored fields:排序聚合为何不走倒排
说明 Lucene DocValues 的列式访问模型如何服务排序、聚合与脚本,stored fields 的随机取原文代价,以及 Elasticsearch 聚合管线对 DocValues 的依赖——为第 14 篇预埋。