Refresh / Flush / Merge 三兄弟的区别与调优

22 May 2026 – wusfe · 4 min read

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 和副本 最快的优化手段,没有之一