Elasticsearch 写入性能调优笔记
ES 写入一旦抖动,整个搜索业务都会受影响。本文从 refresh interval、translog、bulk 调优等方面,整理一份能扛住大促的写入模板。
为什么 ES 写入是独立话题
很多团队用 ES 时把读和写混在一起调。结果是:当写入抖动、查询也跟着抖;或者反过来调高读参数,写入又出问题。ES 的写入和查询本质上走两条 pipeline,需要分开治理。
本篇就专注「写入」。我们关心的是:每秒写入量、写入延迟、磁盘 IO、refresh 行为、副本同步延迟。
refresh interval:不要让它来得太快
默认值是 1 秒,意味着每秒都会把 in-memory buffer 刷成新 segment。代价是:
- segment 数剧增,merge 压力大。
- 磁盘 IO 抖动。
- 查询路径上的 segment 太多,搜索变慢。
写入大流量场景下,我们通常把 refresh_interval 拉到 30 秒甚至 60 秒:
PUT /logs-2026.07
{
"settings": {
"refresh_interval": "30s",
"number_of_shards": 6,
"number_of_replicas": 1
}
}
代价是文档写入后到「可被搜到」有最多 30s 延迟。对日志、监控等业务完全可以接受。
translog:是 ES 写入稳定性的关键
translog 是 ES 写入的「wal」。配置项有几个关键参数:
translog.durability:async / request。request 模式下每次 write 都会 fsync,是安全但慢;async 模式批量 fsync,性能好但崩了会丢数据。translog.flush_threshold_size:默认 512mb,达到后强制 flush。index.translog.interval:translog sync 间隔。
在多副本与高写入场景下,async + 较大 flush threshold 通常更平衡。真正关键的写入走 request。
Bulk 调优:批大小不是越大越好
很多新手以为「bulk 越大越好」。但事实是:
- bulk 请求越大,server 端解析与内存压力越大。
- 某个 doc 失败会导致整批部分成功,需要客户端重试。
- 并发 bulk 太高,会把 heap 打爆。
经验值:每个 bulk 大小 5–15 MB,并发不超过 CPU 核数的两倍。
# Python 客户端示例:控制 batch 大小
from elasticsearch import Elasticsearch, helpers
es = Elasticsearch(hosts=[...])
actions = [
{"_index": "logs-2026.07", "_source": {"msg": f"log {i}", "t": time.time()}}
for i in range(5000)
]
helpers.bulk(es, actions, chunk_size=500, max_retries=2)
Shard 设计:写入视角的考量
主分片数量确定后基本不能改。对写入来说:
- 分片不要过多(> 1000 会显著影响 cluster state)。
- 单分片大小建议 10–50 GB。
- 写入热点要避免落在某些分片上。
我们曾经有过一个 800GB 的「超大索引」,对 cluster state 是负担。改成「按时间滚动 + ILM」,把老数据自动 shrink 到只读层。
Routing 和写入热点
默认按 doc id 路由到分片。如果业务有明显热点,可以让业务侧提供一个 routing key,让同主题 doc 集中到少数分片,对 schema 内聚合查询更友好:
POST /logs-2026.07/_doc?routing=user_42
{ "msg": "...", "user_id": 42 }
Mapping 设计:写入要尽量「扁」
几个写入友好原则:
- 关闭不需要的字段类型识别(dynamic strict)。
- 关掉不必要的
_source字段(不需要回读时)。 - 使用 keyword 而不是 text + keyword 除非要分词。
节点规划与 IO 模型
写入节点最好是「专用数据节点」,与查询节点分离。磁盘推荐 NVMe SSD,因为 translog fsync 与 segment merge 都对随机写有要求。
| 磁盘 | 适用 | 风险 |
|---|---|---|
| NVMe SSD | 写入密集 | 贵 |
| SATA SSD | 中等 | IO 抖动 |
| 云 EBS | 弹性场景 | 延迟方差大 |
写入限流与回压
当 ES 出现写入拒绝(HTTP 429),不要毫无防护地加重试。常见做法:
- 客户端检测 429,按指数退避重试。
- 服务端打开
index.write.wait_for_active_shards。 - 业务层做「写入削峰」,在高峰期降级部分写入。
ILM:把模板变成可持续的策略
把写入调优变成「按时间阶段」自动执行:
policy:
hot:
- rollover: 50GB or 7d
warm:
- forcemerge: 1
- shrink: 1
cold:
- freeze
delete:
- delete
通过 ILM 把「写入期 / 稳定期 / 归档期」按顺序切换,省得人手工操作。
监控与告警
indexing.index_total:每秒写入数。indices.store.size:磁盘占用。coordinating.write.rejected:被拒请求数。merges.current:当前 merge 数。translog.size:translog 体积。
把上面几个指标做成看板,告警阈值:写入延迟 P99 > 5s、内存使用 > 75%、拒绝 > 0。
ES 8 / OpenSearch 的几个差异
- ES 8 默认安全模式开启,需要先做 TLS / 用户配置。
- OpenSearch 提供更多企业级插件。
- 写入侧没大差异,但文档中心、ILM 行为有部分区别。
给团队的简短清单
- 写入与查询分离视角调优。
- refresh_interval 不要 1s。
- translog durability 按业务分层。
- bulk 控制在 5–15 MB。
- 分片不要过度拆分。
- ILM 自动化生命周期。
- 监控写入相关的 5 个核心指标。
ES 写入看上去是个配置问题,本质是对「写入路径」各环节开销的理解。把每一条链路看清楚,把它做成可持续的调优。这就是工程。