日期: August 24, 2023

关于elasticsearch数据写入流向及在对应阶段的优化措施


ES数据写入流程

内存—refresh—>缓存(文档可被查到)—flush—>磁盘(数据不丢失)

What’s Refresh

指数据写入从内存到文件系统缓存的过程,每次refresh都会创建一个新的segment,此时新的segment为可读状态,即文档可被查到,但仍处于缓存中尚未落盘

相关参数

index.refresh_interval: 30s

默认情况下索引的refresh_interval为1秒,这意味着数据写1秒后就可以被搜索到

每次索引refresh会产生一个新的Lucene段,即segment,segment在到一定条件后会自动合并,refresh间隔过短会导致频繁的segment merge行为,如果不需要高的搜索实时性,应该降低索引refresh周期

我们可以手动refresh保证文档可立即被读到

  • 全局索引 POST /_refresh
  • 指定索引 POST /index/_refresh

What’s Flush

指触发lucene commit,将缓存中的segment写入到磁盘,写入一个包含所有段列表的提交点,并清空translog日志文件

Translog

事务日志,每一次es的变更操作都会写入translog,比如文档进入内存缓冲区时也会追加进translog,translog周期性刷盘,默认5s,或者达到固定大小(默认512MB)后刷盘,可类比MySQL的binlog

相关参数

index.translog.sync_interval

index.translog.durability: request

默认设置下,translog的持久化策略为:每个请求都flush

如果可以接受一定概率的数据丢失(例数据写入主分片但尚未复制到副分片时主机断电,数据既没有刷到Lucene,translog也没有刷盘,恢复时translog中没有这个数据则数据丢失)

可调整translog持久化策略为周期性和一定大小时flush

index.translog.durability: async

index.translog.flush_threshold_size: 512MB

index.translog.sync_interval: 120s

async表示translog的刷盘策略按sync_interval周期进行

translog刷盘间隔时间默认5s,不可低于100ms

Merge &Segment

上述提到

每次索引的refresh会产生一个新的Lucene段,即segment

Or

indexing buffer满时(比如大量的创建索引)也会生成新的段

什么是segment

一个索引包含多个分片——>每个分片都有一个Lucene index——>每个Lucene包含多个Segment(倒排索引)——>每个Segment最终落盘为一个个文件

Segment过多影响

  • 消耗资源:每一个段都会消耗文件句柄、内存和cpu
  • 搜索变慢:每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢

段合并目的

  • 提高搜索速度
  • 减少索引容量

es会在后台自动段合并,小段合成中段,中段合成大段

段合并做了什么

delete doc时只是标记删除,物理数据仍然存在,段合并时标记删除的doc不会拷贝到大段中,合并过程中并不会中断查询,但会导致磁盘io消耗和检索性能

相关参数

refresh_inteval影响段的生成数量

index.merge.scheduler.max_thread_count影响段合并的速度

Indexing buffer

indexing buffer在为doc建立索引时使用,当buffer满时会刷入磁盘,生成一个新的segment,这是除refresh_interval刷新索引外,另一个生成新segment的机会

每个shard有自己的indexing buffer

indices.memory.index_buffer_size 默认为堆空间的10%

indices.memory.min_index_buffer_size 默认48MB

indices.memory.max_index_buffer_size 默认无限制

在执行大量的索引操作时,indices.memory.index_buffer_size的默认设置可能不够,这和可用堆内存、单节点上的shard数量相关,可以考虑适当增大该值,增大该值,减少segment,就会减少merge

索引冻结/关闭

上述提到index buffer,每个索引都会占用堆内存,那么对于不需要查询或者查询频率低的索引我们可以通过关闭/冻结的形式释放这部分内存

关闭打开

  • post /index_name/_close
  • post /index_name/_open

冻结解冻

  • post /index_name/_freeze
  • post /index_name/_unfreeze

查询冻结的索引

加上参数 &ignore_throttled=false

  • GET /index_name/_search?&ignore_throttled=false

为什么需要冻结索引

对于旧数据而言,即使我们把这些索引放在冷节点上,它依然占用堆内存,当然我们可以选择直接close索引,但如果需要查的时候又要重新打开,很麻烦,所以如上所述,对于冻结的索引我们只需要查询时加上ignore_throttled=false将数据结构重新加载到内存中,此时因为没有了缓存,查询冻结索引速度会下降,当然在索引冻结前可以手动段合并为一个大段,降低性能开销

调整字段Mappings

减少字段数量

将不需要建立索引的字段index属性设置为not_analyzed或no

减少字段内容长度

禁用_all字段,可以明显降低对CPU和I/O的压力

写入速度优化

所以综上所述提升写入速度可从以下几方面入手:

  • 加大translog flush间隔,目的是降低iops、writeblock
  • 加大index refresh间隔,除了降低I/O,重要的是降低segment merge频率
  • 调整bulk请求
  • 优化磁盘间的任务均匀情况,将shard尽量均匀分布到主机的各个磁盘
  • 优化节点间的任务分布,将任务尽量均匀地发到各节点
  • 优化Lucene层建立索引的过程,降低CPU占用率及I/O,例如,禁用_all字段