连载中 6/20

近实时:为什么搜不到刚写进去的文档

2026-08-13 · 849 阅读 · 0 评论 · 0 赞

灵异的「闪现」订单

mapping 理顺后,老王的搜索总算能用了。新的怪事又来了:运营上架新品,商品列表接口(走 MySQL)立刻能看到,搜索接口(走 ES)却搜不到;过个一两秒再搜,商品又凭空出现了。运营以为自己眼花,老王以为集群抽风,翻完文档才知道:这是 ES 的设计,不是故障——Elasticsearch 是近实时(Near Real Time)的,写入到可搜索之间,默认隔一秒。

文档写入的完整链路

先抛结论:文档写进来,不是直接进倒排索引的。链路是这样:

写入请求
  -> 写内存 buffer(还没法被搜索)
  -> 写 translog(事务日志,防丢)
  -> 默认每隔 1 秒执行 refresh
  -> buffer 里的文档生成一个新的 segment(段)
  -> segment 打开,文档此刻才可被搜索

关键在 segment:Lucene 的倒排索引是一段一段的,每个段一旦生成就是不可变的。不可变意味着没有锁、不用更新、对缓存和压缩极其友好——这是 ES 快的根基。但代价是:想搜新文档,必须等它进了段。refresh 干的就是这件事,默认一秒一次,这就是「近实时」里那个「近」的来源。对比 MySQL 写完立刻可查的实时性,ES 用一秒的延迟换了写入吞吐,这笔账大多数搜索场景都划算。

数据不丢的底气:translog

有个问题冒出来了:buffer 在内存里,refresh 前断电了,这一秒的数据不就丢了?这就是 translog 存在的意义。每条写请求在进 buffer 的同时,也追加写进 translog,默认策略是每个请求都落盘(fsync)成功才返回

节点重启时,ES 重放 translog,把还没生成段的数据补回来——是不是有点眼熟?对,和 MySQL 的 redo log 一个思路:内存快,但易失;日志落盘,用顺序写换来不丢。而真正把内存段固化到磁盘的动作叫 flush:Lucene 做一次 commit,把段永久落盘,然后清空 translog。refresh 每秒一次很便宜,flush 则重得多,默认 translog 太大或每 30 分钟才触发一次。

删除和更新的真相

段不可变还带来两个反直觉的真相。删除文档并不真删:只是在删除文件(.del)里给对应文档打了个墓碑标记,搜索时会过滤掉,磁盘上那条数据还躺着。更新文档也不真更新:等于「标记删除旧版本 + 写入新版本」,同一个文档的多个版本可能散落在不同段里。

那墓碑和旧版本谁来清?段合并(merge)。ES 后台会把一堆小段合并成大段,合并时被标记删除的文档被真正丢弃。这也是为什么频繁删除更新的索引,磁盘占用会先涨后落——涨的部分是墓碑和新版本,落下来靠合并。老王的商品表天天改价格,磁盘曲线跟心电图似的,看到这就释然了。

一秒太久怎么办:refresh 调优

反过来,一秒的延迟在有的场景嫌久,在有的场景又嫌贵。refresh 是可以在索引级别调的:

PUT /product/_settings
{
  "refresh_interval": "30s"
}
批量导入前临时关刷新,导完手动刷一次再恢复
PUT /product/_settings
{ "refresh_interval": "-1" }
POST /product/_refresh
PUT /product/_settings
{ "refresh_interval": "30s" }

写多读少的日志类索引,把 refresh_interval 拉长到 30 秒甚至更久,写入吞吐立竿见影;同步进 ES 的搜索场景,一秒默认值够用;大批量灌数据时临时关刷新,是第 12 篇写入调优的常规操作。还有一个 API 值得记住:GET /product/_doc/1?realtime=true,get 接口可以绕过 refresh 直接读(必要时从 translog 里捞),所以按 ID 取文档是实时的,「搜不到」只发生在 search 这条走倒排的链路上。老王那件灵异事件,本质就是列表查 MySQL、搜索走倒排,两条链路可见时间不一致。

小结

写入链路一句话:进 buffer 加日志,refresh 出段才可搜,flush 落盘清日志,删除打墓碑,合并来打扫。段不可变是快之本,translog 是不丢之本,refresh_interval 是吞吐与实时的调节旋钮。

文档能搜到了,怎么搜又是门学问。下一篇进查询的世界:match、term、filter 与 bool 的分工——以及为什么对 text 字段用 term 查询是新手第一坑。

503

10 年全栈工程师 · 503咖啡馆主理人

#Elasticsearch#近实时#refresh#translog#段合并

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 5 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞