近实时搜索(Near-real-Time Search)
一条记录从调用 Index API 到最终持久化,会经历如下过程:
- index 阶段,先在 document buffer 积聚(此时还不是 searchable ),然后 Index API 请求被响应成功.
- refresh 阶段,被动(Refresh API)或主动(每隔1s一次)将 document buffer 中的文档生成 segment(此时 searchable),这一过程称为refresh,此时 segment 存在于 file system cache
周期性refresh只针对最近30秒收到过搜索过请求的索引: By default, Elasticsearch periodically refreshes indices every second, but only on indices that have received one search request or more in the last 30 seconds.
- segment merge, 由于 segment 的生成很频繁,导致搜索时要遍历过多小的 segment 影响性能,所以ES会定期地将多个小的segment合并为一个大的.
- flush 阶段,被动(调用Flush API)或主动(取决于配置
index.translog.durability
)将 file system cache 中的 segment 刷盘,此时真正的持久化.
“Near-real-Time Search”的意思是指在“第一步完成”到“第二步完成之前”这个时间段中,无法通过 Search API 查到 Index API 插入的记录, 因为这时记录还没被索引到 lucene segment 中.
段合并(segment merge)
InnoDB 中表的索引是可变的,CRUD 都作用于索引页,页空间不足以容纳记录时产生页分裂,页中有效利用空间不足MERGE_THRESHOLD
(默认 50%)时做页合并.
ES shard 是一个 Lucene index,由若干个segment组成,segment是不可变的,所以增删改在refresh时总是会产生新的 segment,而refresh默认1s一次,这会导致lucene index 的segment过多. 另一方面,查询的时候需要遍历segment,过多的小segment会导致性能降低.
基于以上原因,ES会周期性的做段合并,在合并完成后,新的段被打开,旧的段被删除.
合并操作由后台线程执行,线程数量可通过index.merge.scheduler.max_thread_count
设置.
持久化(Translog)
Translog的产生背景
在 InnoDB 中,如果直接持久化缓冲页,会带来大量随机 IO (因为事务造成的修改通常发生在多个不连续的页),并且即使只改了页中一个字节,也要产生 16KB 的IO. 对此问题,InnoDB的解决方案是 redolog,其有体积小,且连续IO写入的特点,在保证 Durability 的同时,也尽可能提升了性能.
ES 中,持久化增删改需要执行一次“Lucene commit”,这个操作用官方的形容是: ”which is a relatively expensive operation“,可以合理推测,ES 面临着 InnoDB 一样的问题————持久化与性能之间的矛盾,类似地,ES也引入了类似redolog的概念————tanslog.
Translog的回收策略
InnoDB 中用一个 ring buffer 来存储redolog,随着增删改操作的执行,redolog 会越积越多,因此在适当的时候也需要将脏页刷盘,这样就可以回收相应的redolog,这一过程称为checkpoint,它让ring buffer的头指针向后移动,也就释放了空间,ring就这样“转动”起来了.
ES 中,可通过 index.translog.flush_threshold_size
控制,当translog积累多少后,执行“lucene commit”将 file system cache中的segment刷盘,以回收相应的translog. 这个过程也类似 InnoDB 中的 checkpoint.
Durability 的几种级别
人们对性能的追求是不会停止的
- 有时,性能大于持久性,允许丢失最近的数据
- 有时,持久性大于性能,只要一个操作被“acknowledged”,那么之后即使crash,数据也不能丢失.
对于MySql,”acknowledged“意味着commit操作返回成功,对于ES,这意味着Index/Update/Delete API返回了成功.
对此,InnoDB 与 ES 都给出了对应的配置,让用户根据需要定义合适的持久化策略
InnoDB中,提供了 innodb_flush_log_at_trx_commit
选项,可选值如下:
- 0,异步持久化redolog,
acknowledged
前不立即刷盘redolog,发生crash会丢失最近数据 - 1(默认值),同步持久化redolog,
acknowledged
前刷盘redolog, - 2,同步写到 file system cache,如果只是mysql进程挂了,那么cache数据会由OS负责刷盘; 如果OS也挂了,那么数据就没了.
在ES中,提供了 index.translog.durability
选项,可选值如下:
- request(默认值),同步持久化,
acknowledged
之前,translog已经被持久化 - async,异步持久化,后台线程定期持久化(取决于
index.translog.sync_interval
),crash会丢失最近数据.
基于Translog的实时CRUD
InnoDB 对页的修改直接作用于buffer pool中的缓冲页(没有document buffer),后续的读写也是优先先走缓冲页,所以任何写操作都是实时可见的.
ES 用 translog 被用来提供实时 CRUD ,对于 GET API、Update API、Delete API,共同特点是都提供了文档 ID,ES 首先通过 ID 去检查 translog 中有无最近的变更(由此可以合理推测,ES 的 translog 在数据组织上是用文档 ID 索引的),这使得增删改操作对之后基于文档 ID 的操作来说是立即可见的.
而对于 Search API,由于其依赖 lucene segment 去实现各种复杂的索引方式,所以只能等 document buffer 被 refresh 到 segment. 因此 Search API 是 “near-real-time” 的.