Elasticsearch-36-深度解析document增删改原理及优化过程

document写入原理

在es底层,用的是Lucene,Lucene底层的index是分为多个segment的,每个segment都会存放部分数据
写入document

图中,客户端写入一个document的时候:

  1. 先写到了操作系统中的buffer缓存中
  2. 然后进行commit point
  3. buffer中的数据写入了新的index segment
  4. 然后写入操作系统的缓存中
  5. 缓存中的index segment被fsync强制刷新到磁盘上
  6. 同时新的index segment被打开,供搜索使用
  7. 将buffer缓存清空

更新删除原理

如果是更新操作,实际上是将现有的document标记为deleted,然后将新的document写入新的index segment中,下次search过来的时候,也许会匹配到一个document的多个版本,但是之前的版本已经被标记为deleted了,所以会被过滤掉,不会作为搜索结果返回,删除操作同理.

更新/删除

每次commit point时,会有一个.del文件,标记了哪些segment中的哪些document被标记为deleted了
搜索的时候回依次查询所有的segment,从旧的到新的,比如被修改过的document,在旧的segment中,会被标记为deleted,在新的segment中会有其新的数据

问题

如果按照上面的流程的话,每次都必须等待fsync将segment刷入磁盘,才能将segment打开供search使用,这样的话,从一个document写入,到它可以被搜索,可能会超过1分钟,这就不是近实时的搜索了, 主要瓶颈在于fsync实际发生磁盘IO写数据进磁盘是很耗时的.

流程改进

流程改进

  1. 数据写入buffer中
  2. 每隔一定的时间(默认是1s),buffer中的数据被写入新的segment文件,然后写入os cache中
  3. 只要segment写入到了os cache中了,那就直接打开index segment 供使用,不立即commit.
  4. 最后把buffer清空

数据写入os cache并被打开供搜索的过程,叫做refresh,默认是每隔1s refresh一次,就是说每隔一秒,就会将buffer中的数据写入一个新的index segment文件,先写入os cache中.所以es是近实时的,数据写入到可以搜索,默认是1秒.

我们也可以手动去设置refresh的间隔时间,比如时效性要求较低,写入数据一分钟后被搜索到就可以了

1
2
3
4
5
6
PUT /my_index
{
"settings": {
"refresh_interval": "60s"
}
}

问题

数据不及时写入到磁盘中,而是在缓存中,如果宕机的话,数据就会丢失,就不可靠了

再次优化写入流程

再次改进

  1. 写入document的时候,数据同时写入buffer缓冲和translog日志文件
  2. 每隔1秒中,buffer中的数据被写入新的segment file,并进入os cache中,此时segment被打开并供search使用
  3. buffer被清空
  4. 重复1-3,新的segment不断添加,buffer不断被清空,而translog中的数据不断累加
  5. 当translog长度达到一定的程度的时候,commit操作发生.
    5.1. buffer中所有数据写入一个新的segment中,并写入os cache 打开供使用.
    5.2. buffer被清空
    5.3. 一个commit point被写入磁盘,标明了所有的index segment
    5.4. os cache中的所有 数据被fsync强行刷到磁盘上去
    5.5. 现有的translog被清空,创建一个新的translog

基于translog和commit point进行数据恢复

磁盘上存储的是上次commit point为止,所有的segment file,那么translog中存储的就是上一次flush(commit point)知道现在最近的数据变更记录
如果说 os cache中已经囤积了一些数据,没有被刷到磁盘上,这个时候宕机了, 这时候机器重启,此时会将translog文件中的变更记录进行回放,重新执行之前的各种操作,等待下次commit即可

每次flush 会自动清空translog,默认每隔30分钟flush一次,或者当translog过大的时候,也会flush.
我们也可以手动flush,POST /my_index/_flush,一般来说别手动flush,让它自动执行就可以了

translog,也是先放在缓存中的,每隔5秒被fsync一次到磁盘上.一般是在一次增删改操作之后.

如果说在一次增删改操作的时候正好要fsync translog到磁盘上,那么会等待primary shard和replica shard都成功之后,这次增删改操作才会成功

但是这种在一次增删改时强行fsync translog可能会导致部分操作比较耗时
如果可以允许部分数据丢失,可以设置异步fsync translog

1
2
3
4
5
PUT /my_index/_settings
{
"index.translog.durability": "async",
"index.translog.sync_interval": "5s"
}

终极优化

上面说的,每秒生成一个segment文件,文件会越来越多,而且每次search都要搜索所有的segment,很耗时
es会默认在后台执行合并的操作,在merge的时候,被标记为deleted的document 也会被彻底物理删除

每次merge的流程是

  1. 选择一些有相似大小的segment,merge成一个大的segment
  2. 将新的segment flush到磁盘上去
  3. 写一个新的commit point,包括了新的segment,并排除旧的那些segment
  4. 将新的segment打开供搜索
  5. 将旧的segment删除

也可以通过 POST /my_index/_optimize?max_num_segments=1 来手动合并,但是尽量不要手动执行