运维常把「写进 ES
了」等同于「立刻能搜到」或「已经落盘不怕宕机」——这是三件事。refresh
打开新的 Lucene Searcher,决定
近实时(NRT)可搜;flush
触发 Lucene commit(),把段
持久化到磁盘 并裁剪
translog;translog
记录尚未安全提交的索引操作,支撑
节点崩溃后的重放。三者正交,混谈会导致错误的
SLA 与错误的 refresh_interval 调参。
第 7
篇 在 单机 Lucene 上讲了
IndexWriter 缓冲 flush
成段、DirectoryReader.open(writer) 的 NRT
读者;第
11 篇 把段模型抬到 分片 +
副本复制。本篇锚定 Elasticsearch 8.x
Reference(Near real-time
search、Translog、Index
modules),给出 保证矩阵,说明
refresh_interval、?refresh=、wait_for
的语义差异,并用本机 ES 8.15.0 单节点
做一次可见性对照实验。
本文是「全文检索引擎」系列第 12 篇(共 18 篇)。→ 系列目录
版本锚定:Elasticsearch 8.15.0(Docker 单节点);Lucene 9.11.1。实验在 WSL2 上执行,下文输出经删减仅保留命中数。
一、写入路径:内存缓冲、translog 与复制
单分片上的索引操作(index /
bulk)在数据节点上大致经过:
- 解析与映射(见 第 14 篇)。
- 写入 内存 indexing buffer,同时 append translog(每个分片独立 translog 文件)。
- 按
index.translog.durability决定是否 fsync translog(request每次写入后;async按sync_interval批量)。 - 若该分片有副本,主分片把操作 复制 到副本分片(副本也有 translog)。
flowchart LR
W["Write request"]
IB["Indexing buffer<br/>in-memory segments"]
TL["Translog<br/>append + optional fsync"]
RF["refresh<br/>new Searcher"]
FL["flush<br/>Lucene commit"]
W --> IB
W --> TL
IB --> RF
IB --> FL
FL --> TL2["trim translog"]
关键点:在 refresh 之前,数据已在内存缓冲与 translog 中,但 默认 Searcher 看不到 新段;在 flush 之前,新段可能只在 OS page cache 或纯内存路径上,Lucene commit 点 未前进。
二、保证矩阵:可搜、落盘、可恢复
| 机制 | 对搜索可见 | 对 Lucene commit / 段文件 | 崩溃后未提交段 | 典型触发 |
|---|---|---|---|---|
| 仅写入 buffer | 否 | 无新 commit | 靠 translog 重放 | 每次 index |
| refresh | 是(新 Searcher) | 不一定 fsync 段文件 | 仍靠 translog | 定时 / 手动 _refresh |
| flush | 是 | commit + 段持久化 | translog 可裁剪 | translog 大小 / 定时 / _flush |
| translog fsync | 不影响可见性 | 不代替 commit | 重放索引操作 | durability=request |
表意对齐 Reference Near real-time search 与 Translog:refresh 解决「读路径」,flush 解决「Lucene 持久化点」,translog 解决「commit 之间的操作日志」。
与关系库对照:InnoDB redo log + doublewrite 也区分 脏页刷盘 与 日志重放;ES 把 可搜 提前到 refresh,换取默认约 1s 的 NRT 窗口(architecture/42 NRT 节 有场景化描述,本篇钉语义)。
2.1 refresh 做了什么
refresh 在分片上等价于:把内存缓冲 形成新
Segment(或暴露已有段)并 打开新的
IndexSearcher 供搜索线程使用。默认
index.refresh_interval 为
1s,即周期性 refresh。refresh
不 替代 flush:未 flush
的段在进程崩溃时仍可能丢失,除非 translog
重放后重新索引。
2.2 flush 做了什么
flush 调用 Lucene
IndexWriter.commit():把待提交段
固化到磁盘 并写入 commit
点(segments_N)。成功后 ES 可
截断或删除 已覆盖的 translog
区间。index.translog.flush_threshold_size(默认约
512mb)与 index.translog.sync_interval
等共同决定 flush 频率。
2.3 translog 与 durability
index.translog.durability:
request(默认):每次写入后 fsync translog;节点崩溃 时,未 flush 的操作可从 translog 恢复。仍 不 保证其他节点已复制(复制与 ack 见官方 Write consistency)。async:按sync_interval批量 fsync;提升写入吞吐,可能丢失最近窗口内的已 ack 写入——日志场景在 architecture/42 与 observability/08 中作为吞吐取舍出现,本篇只强调 语义降级。
三、近实时 vs 持久化 vs 崩溃恢复
用时间线理解三者 不互相替代:
sequenceDiagram
participant Client
participant Primary as Primary shard
participant Disk as Disk / translog
Client->>Primary: index (refresh=false)
Primary->>Disk: append translog
Note over Primary: not searchable yet
Primary->>Primary: refresh
Note over Primary: searchable (NRT)
Primary->>Disk: flush / commit
Note over Disk: segment durable; translog trim
| 读者关心 | 应看的 knob | 不应指望 |
|---|---|---|
| 写入后多久能搜到 | refresh_interval、?refresh=、wait_for |
flush 频率 |
| 单机宕机不丢已 ack 写 | translog.durability=request、副本
wait_for_active_shards |
refresh |
| 磁盘上段文件完整 | flush / ILM forcemerge 边界 | 仅 refresh |
| 跨节点同时可见 | 复制 + refresh 传播 | 单分片 translog |
强一致搜索在 ES 中并非默认模型:即使
refresh=wait_for,保证的是
参与刷新的分片 上
已提交索引操作 对搜索可见,而非关系库意义的
线性一致读(争论见 Lamport
风格可用性取舍;工程上 ES 选择 NRT + 复制
服务搜索 SLA)。
四、refresh_interval、请求级
refresh 与 wait_for
4.1 索引级
refresh_interval
PUT /logs-2026.07.15
{
"settings": {
"refresh_interval": "30s",
"number_of_shards": 1,
"number_of_replicas": 0
}
}- 正数:最短 refresh 周期;写入吞吐高、搜索延迟变大。
-1:禁用自动 refresh,仅手动POST /index/_refresh或关闭索引前批量导入——bulk 灌库常用(Reference Index modules —refresh_interval)。
4.2 请求级 refresh
参数
POST /idx/_doc?refresh=true:写后
立即 refresh
该分片相关范围(代价高,不适合高 QPS
流写)。refresh=false(默认)依赖周期
refresh。
refresh=wait_for:写线程阻塞直到
下一次 refresh
使本写入在搜索中可见(或超时)。适合
写后立刻读 的集成测试与低 QPS
业务;高并发下会把 refresh 压力集中到协调路径。
4.3
本机实验:默认 1s vs refresh_interval: -1
环境:Docker
elasticsearch:8.15.0,单节点,number_of_replicas: 0,WSL2
Linux。
# 索引 A:默认 refresh_interval(1s)
curl -s -X PUT 'http://127.0.0.1:19200/refresh_demo_default' \
-H 'Content-Type: application/json' \
-d '{"settings":{"number_of_shards":1,"number_of_replicas":0}}'
# 索引 B:禁用自动 refresh
curl -s -X PUT 'http://127.0.0.1:19200/refresh_demo_manual' \
-H 'Content-Type: application/json' \
-d '{"settings":{"number_of_shards":1,"number_of_replicas":0,"refresh_interval":"-1"}}'
DOC='{"title":"nrt visibility probe"}'
curl -s -X POST 'http://127.0.0.1:19200/refresh_demo_default/_doc?refresh=false' \
-H 'Content-Type: application/json' -d "$DOC"
curl -s -X POST 'http://127.0.0.1:19200/refresh_demo_manual/_doc?refresh=false' \
-H 'Content-Type: application/json' -d "$DOC"写入后立即搜索(未手动 refresh):
default hits: 0
manual hits: 0
对 manual 索引执行
POST /refresh_demo_manual/_refresh
后再搜:manual hits after refresh: 1。
等待 2s 后对 default
索引搜索:default hits after 2s: 1。
结论与 Reference 一致:不 refresh
则不可搜;默认 1s 周期下
亚秒级延迟内 可见;-1 必须显式
refresh 或接受不可搜窗口。本实验 未 测量
flush/translog 崩溃恢复——该路径需受控 kill 节点,留给
reproduce/ 脚本(不编造恢复耗时)。
五、主副本与 refresh 的可见性
副本分片 复制 indexing 操作 后,各自执行
refresh。搜索命中副本时,可见性取决于 该副本是否已
refresh。?refresh=wait_for 等待的是
所有参与刷新的活跃分片(含副本策略)达到一致可见点——具体等待集合以
8.x Reference Refresh 为准。
写入一致性常用:
PUT /idx/_settings
{
"index.write.wait_for_active_shards": "1"
}all 要求全部副本活跃才
ack——复制耐久 与 搜索可见
仍由 refresh/translog 分别保证,勿混为一谈。
六、学术谱系、争论与开放问题
谱系:Lucene
NRT(DirectoryReader.open(IndexWriter))→
Solr/ES 把 refresh 服务化 → translog 作为 commit 间
WAL(与 LSM WAL 类比见 RocksDB WAL)→
日志栈加大 refresh_interval 换吞吐(observability/08)。
争论:
- NRT 默认 1s 是否「够实时」:流式告警要毫秒级时常旁路 Kafka + 流处理;ES 优化的是 搜索索引 而非 事件总线(architecture/42 场景分工)。
asynctranslog 是否「可接受丢数据」:可接受性取决于业务 是否可重放(日志)vs 不可重放(订单);不是引擎自动判断。
开放问题:
- 统一 refresh 调度 与 分片级负载:高基线索引上频繁 refresh 的 CPU 开销与 第 17 篇 的「refresh 过频」排障仍缺公开、与硬件绑定的对照表(本篇不编造集群 QPS)。
- 可搜索持久化(searchable snapshots)把段放对象存储,refresh/flush 边界更模糊——自管集群读者需单独读 Elastic 8.x Searchable snapshots 文档。
七、常见误解
| 误解 | 事实 |
|---|---|
| 「写入成功 = 立即可搜」 | 默认最多约 1s refresh
窗口;refresh=false 时更久 |
| 「refresh = 落盘」 | refresh 只开 Searcher;flush 才 Lucene commit |
| 「flush 越勤越安全」 | 过频 flush 产生 小段、放大 merge 与 第 8 篇 代价 |
| 「关掉 translog 能提速」 | translog 是崩溃恢复与复制重放的 必要 WAL(除只读挂载等特殊场景) |
「wait_for = 强一致」 |
只保证 搜索可见点;不替代跨文档事务 |
| 「副本不需要 refresh」 | 每个分片独立 Searcher;副本也要 refresh 才能被搜到 |
八、小结
三句话小结:
- refresh 决定 NRT 可搜;flush 决定 Lucene commit 与段持久化;translog 在 commit 之间提供 崩溃重放,三者应用保证矩阵分开调参。
refresh_interval=-1与默认 1s 的可见性差异已在 ES 8.15 单节点 验证:不 refresh 命中为 0,显式 refresh 或等待周期后才为 1。- 写后读用
wait_for或refresh=true换延迟;高吞吐灌库用-1+ 批量结束再 refresh,并单独评估translog.durability。
下一篇沿 协调节点 拆解 scatter-gather 的 query 与 fetch 两阶段:查询路径。
参考资料
规范(A 级)
- Elasticsearch 8.x Reference, Near real-time search。
- Elasticsearch 8.x Reference,
Translog、
index.translog.durability。 - Elasticsearch 8.x Reference, Index modules —
refresh_interval、flush相关设置。 - Elasticsearch 8.x Reference, Document APIs —
refresh查询参数、wait_for。 - Apache Lucene 9.x, Near-real-time search(与 第 7 篇 一致)。
实验(A 级)
- 本机 Docker
elasticsearch:8.15.0,refresh_demo_default/refresh_demo_manual可见性对照(2026-07-15,WSL2)。
站内对读(B 级)
返回 系列目录 | 上一篇:Index · Shard · Replica | 下一篇:查询路径
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【全文检索引擎】IndexWriter 与 NRT:从缓冲到可打开的 Reader
拆解 Lucene IndexWriter 的 RAM 缓冲、flush 出段与 DirectoryReader.open(IndexWriter) 近实时语义;说明 Searcher 刷新与「刚写入即可搜」在库层与 ES refresh 层各自保证什么。
【全文检索引擎】生产排障:breaker、refresh、分片、慢查询
用症状到机制的决策树覆盖 circuit breaker、refresh 过频、大字段、分片过多与慢查询 explain,逐条回链本系列 Lucene 段与 ES NRT 篇章;不含未跑集群 benchmark。
【全文检索引擎】Lucene · BM25 · Segment · Elasticsearch NRT
补齐搜索架构叙事与 RAG/向量引擎之间的全文检索内核层:以 Lucene 9.x/10.x 拆解 Analyzer、FST、postings、BM25 与 IndexWriter,并以 Elasticsearch 8.x 拆解分片、refresh/translog 与查询路径;OpenSearch 对照与选型收束。
【全文检索引擎】全文引擎全景:架构叙事与 Lucene 内核之间的一层
定位 Lucene 9.x/10.x 库内核与 Elasticsearch 8.x 服务层相对 architecture/42、storage/29、向量引擎与 PG GIN 的分工;以最小索引故事建立坐标系,交代倒排索引学术谱系与 18 篇路线图。