Refresh / Flush / Merge 三兄弟的区别与调优
Refresh / Flush / Merge 三兄弟的区别与调优
"我写入了一条数据,为什么搜不到?"
这大概是 ES 新人被问得最多(也是自己在生产里踩得最多)的问题。答案藏在三个操作里:Refresh(刷新可见)→ Flush(刷到磁盘)→ Merge(合并段)。
这三个词英文长得像,中文翻译又相近,导致大量混淆。本篇把它们的区别、对读写的影响、以及调优实践一次讲清楚。
先理解基本单位:Segment
ES 底层是 Lucene。Lucene 把数据存进 segment(段)——一个不可变的倒排索引文件。
关键特性:
- 不可变:segment 一旦生成就不会被修改(更新 = 标记删除 + 写新 segment)
- 按需生成:不是每写一条就生成一个 segment,也不是写满一天才生成一个
- 越多越慢:segment 太多会拖慢搜索,因为搜索需要遍历所有 segment
Refresh:让文档可见(轻量)
作用:将内存中的文档刷到新的 segment,使其可被搜索。
内存 buffer → 新 segment(文件系统缓存)→ 文档变为可搜索
- 默认
refresh_interval: 1s——这就是为什么 ES 是"近实时"(NRT)而非"实时" - 每次 refresh 产生一个新的 segment,即使只有 1 条文档
- Refresh 不调用 fsync,数据还在文件系统缓存里,断电可能丢
// 手动触发
POST /orders/_refresh
// 关闭自动 refresh(大批量导入用)
PUT /orders/_settings
{ "index": { "refresh_interval": "-1" } }
// 导入完成后手动刷一次
POST /orders/_refresh
调优原则:搜索实时性要求高 → 保持默认 1s;批量导入场景 → 暂时关闭,导完恢复。
Flush:把数据写入磁盘(重量)
作用:将 segment 从文件系统缓存 fsync 到磁盘,并清空 translog。
translog 累积到阈值 → fsync segment → 清空旧 translog → 写入新 translog 检查点
- 默认
index.translog.flush_threshold_size: 512MB——translog 超过这个值自动 flush - 也受
index.translog.flush_threshold_period: 30m控制——最长 30 分钟自动 flush - Flush 会触发一次 refresh(旧行为)或持久化已有 segment(新行为)
调优原则:写入吞吐优先 → 增大 translog 阈值 + 改为 async;数据安全优先 → 保持 request 模式。
Merge:合并小段(后台异步)
作用:将多个小 segment 合并成一个大 segment,减少搜索时需要遍历的 segment 数量。
[seg1] [seg2] [seg3] [seg4] [seg5] → merge → [seg_merged_1] [seg_merged_2]
- 自动在后台执行,由 Lucene 的 merge 策略控制(tiered merge policy)
- Merge 是 IO 密集操作,merge 期间 IO 飙升是正常现象
forcemerge强制合并,但不要在高峰期用
// 强制合并到 1 个 segment(只读索引优化)
POST /orders/_forcemerge?max_num_segments=1
// 限制 merge 线程数,减轻 IO 压力
PUT /_cluster/settings
{
"transient": {
"indices.store.throttle.max_bytes_per_sec": "50mb"
}
}
三兄弟对比一览
| Refresh | Flush | Merge | |
|---|---|---|---|
| 触发条件 | 每 1s 自动,或手动 | translog 满 512MB,或 30min | 后台自动,或手动 forcemerge |
| 做什么 | buffer → segment,可搜索 | segment fsync 到磁盘 | 小 segment 合并为大 segment |
| 数据安全性 | 内存→缓存,断电丢失 | 缓存→磁盘,持久化 | 不改变数据,只改变组织方式 |
| 对搜索的影响 | 新数据变为可见 | 几乎无影响 | 减少 segment 数,搜索更快 |
| 对写入的影响 | 轻微 | 较大(IO 抖动) | 大(消耗磁盘 IO) |
| 可关闭吗 | refresh_interval=-1 | 不推荐(会丢数据) | 可限速 |
文档从写入到持久化的完整旅程
1. 客户端发送 Bulk 请求
↓
2. 文档写入内存 buffer(不可搜索)
↓
3. 同时写入 translog(WAL,预写日志)
↓
4. Refresh(默认每 1s):buffer → segment(文件系统缓存)
此时文档变为可搜索,但不在磁盘上
↓
5. Flush(默认 translog 512MB / 30min):segment fsync → 磁盘
此时数据真正持久化,translog 清空
↓
6. Merge(后台自动):小 segment 合并成大 segment
减少搜索开销
如果 ES 在步骤 4 之后、步骤 5 之前崩溃——内存 buffer 已经通过 translog 记录了完整操作,重启后从 translog 回放,数据不会丢。
如果 ES 在步骤 4 之前崩溃——文档还在内存 buffer,translog 里也没有完整记录 → 丢失。
这就是 translog 存在的意义:ES 用 translog 在"写入性能"和"数据不丢"之间做平衡。
生产环境调优三件套
场景 1:大批量导入(离线 ETL)
PUT /orders/_settings
{
"index": {
"refresh_interval": "-1",
"number_of_replicas": 0,
"translog.durability": "async",
"translog.sync_interval": "60s",
"translog.flush_threshold_size": "1024mb"
}
}
// 导完记得恢复
场景 2:实时索引(低延迟搜索)
// 保持默认 refresh_interval=1s,可以降为 500ms
{ "index": { "refresh_interval": "500ms" } }
场景 3:只读索引(归档优化)
POST /archive_2024/_forcemerge?max_num_segments=1
总结
| 口诀 | 解释 |
|---|---|
| Refresh 管可见 | 文档写入后 1 秒内可搜索,就是这个在起作用 |
| Flush 管持久化 | translog 写满或到时间,数据刷到磁盘 |
| Merge 管性能 | 用后台 IO 换搜索速度,别在高峰期 forcemerge |
| 导入时关 refresh 和副本 | 最快的优化手段,没有之一 |