ElasticSearch 索引的存储机制推演

ES读写原理深入理解

ElasticSearch 作为开源的搜索引擎,需要依赖的一个重要数据结构就是 inverted index(倒排索引)。inverted index 通常庞大、且建立过程相当耗时,于是,如何存储 inverted index 就变成了一件极为要紧的事情。显然,inverted index 不能简单地被放在 memory 中,它还必须做对应的持久化,让这些已经建立的 inverted index 可以被复用。ElasticSearch 是基于 Lucene 来构建的,在 Lucene 的世界里, inverted index 就是存放于 disk 的一块 immutable 的 segment。于是,关于 ElasticSearch inverted index 的存放问题,就转换为了「如何能够高效地存储 disk 文件 segment」。既然是 disk file,那么最直观的优化方式便是使用 memory 来做批量堆积,通过累积 data batch 来节省 disk I/O overhead。于是,ElasticSearch 引入了 in-memory buffer 来暂存每个 doc 对应的 inverted index。当这些 doc inverted index 累积到一定量后,就可以从 in-memory buffer 刷到 disk 了。

另一方面,在 Lucene 的世界里,一切「搜索」行为,都依赖于 disk segment,没有 disk segment 就没有对应的「可搜索」。

如此,如上图所示,「索引的创建」和「索引的可搜索」之间出现了 time gap(这是 ElasticSearch被称为 near realtime search engine 的原因)。显然,我们的下一个目标,就是要尽可能地缩短这个 time gap。

一个立刻能被联想到的工具是 OS(operating system)提供的 disk page cache。disk page cache 也是 OS 为了优化 disk I/O 所做出的努力,其指导思想同 ElasticSearch引入 in-memory buffer 是一样的,都是希望通过 data 的批量累积,来尽可能地减少 disk I/O。

但 disk page cache 不同与用户自己创建的 memory buffer 的地方是,一旦 data 被放入了 disk page cache,它对整个 OS 来讲,从“概念上”就可以被当做是一个真正的 disk file 了(虽然它实际不是,还在 memory 中)。于是,放入 disk page cache 的 inverted index,自然就可以被当做是 disk segment,对于 ElasticSearch来讲,它就是「可搜索」的!

并且,由于 disk page cache 毕竟是在 memory 中,从 in-memory buffer 到 disk page cache 所需要的时间,将会远远少于从 in-memory buffer 到 disk segment 的时间。

于是,通过引入 disk page cache,我们缩短了 ElasticSearch从「创建索引」到「索引可搜索」的时间,也即是让它更接近于 realtime。引入了 disk page cache 之后的两段 data pipeline,分别对应了 ElasticSearch的两个重要 API:

  • refresh_interval,这个参数需要特别注意。 从 in-memory buffer 到 disk page cache 的过程,说白了就是内存到内存,相对来说此操作算是轻量级操作。对应 ElasticSearch的 refresh() API,默认 1s 触发一次;
  • 从 disk page cache 到 disk 的过程,则对应 ElasticSearch的 flush() API

    es的各个shard会默认每个30分钟进行一次flush操作

    当translog的数据达到某个上限的时候会进行一次flush操作。

    index.translog.flush_threshold_ops:当发生多少次操作时进行一次flush。默认是无限制。

    index.translog.flush_threshold_size:当translog的大小达到此值时会进行一次flush操作。默认是512mb,可以改为1024mb。

    index.translog.sync_interval:多少时间间隔内会检查一次translog,来进行一次flush操作。默认是5s,可以改为100s。

    index.translog.durability:flush的方式,改为async异步刷盘,写入耗时更低。此参数是否在每次写数据或者修改数据就触发一次fsync。es的默认是index.translog.durability=request。也就是每次都触发fsync。

在这样的结构下,我们不禁要问,如果 disk page cache 的内容还未被 flush 到 disk,而此时所在的 server 出现了 power off 等极端异常情况,那么这部分保存于 disk page cache 的 data 是否会丢失?!

当然是会的!

于是,ElasticSearch又引入了 disk page cache 的一个临时 disk backup:translog,用于持久化当前 disk page cache 中的内容。

可以看到,虽然 translog 的本意是将 disk page cache 中的 inverted index 做持久化备份,但它自己作为 OS 的 file,同样需要经历 disk page cache(translog 的 disk page cache,而不是 inverted index 的 disk page cache)到 disk 的过程。默认 translog 自己从 disk page cache 到 disk 的持久化,是 5s 一次。index.translog.sync_interval参数是可以更改刷盘频率的。但是调大此参数相应的也会增加数据丢失的风险。

由于 translog 仅仅是 inverted index 在 disk page cache 的临时备份,当 ElasticSearch触发了 flush() API 时,对应的 translog 就会被清空。因为它临时部分的 data 在此时已经被真正持久化到了 disk,于是就不再需要这个临时的 disk backup 了。

如此,ElasticSearch就将 data lost 减低到 translog 的 5s,即:最多可能会丢失 5s 的数据。

幸运的是,ElasticSearch还有 replica node 这样的 data backup,即便是在某个 node 上丢失了 5s 的数据,还能够从其它的 replica node 上将数据取回来。于是这就进一步降低了 data lost 的概率(当然,理论上还是存在 data lost 的可能性,但只要降低到工程上可接纳的概率之下就可以了。工程上永远没有百分之百的保障,只有可接受的精度范围)。

如此,我们便将整个 ElasticSearch的机制给传串起来了。整个机制围绕着如何高效地存储 disk segment 来展开:

  • 为了降低 disk I/O 的 overhead,引入了 in-memory
  • 为了降低「创建索引」和「索引可搜索」的 time gap,而引入了 disk page cache。
  • 为了对 disk page cache 做临时备份,而引入了 translog


ES 写入原理总图如下: