本文的优化基于 ES 7.6.0 ,索引 主要集中在 几个 TB级索引的查询写入优化

es数据压缩率_数据

1. segment force merge

es 数据分层 大致是 index -> shard -> segment

由于分布式体系结构中的硬件限制,数据被划分为更小的块segment ,并分布在不同的节点 shard 上。这些小片段称为分片。

segment 的产生来源自于 Lucene倒排索引实时更新策略

  • Buffer 为了索引增长的数据能被实时的搜索到,Lucene 将新增的数据存在 内存 Buffer 中,这时数据无法被搜索到。
  • translog + refresh 根据一定的策略,数据将会被 refresh 到磁盘缓存中filesystem cache(不是直接写入磁盘),并产生 translog ,这里说反了,应该是,先记录 数据 进入 内存 Buffer 中 产生 translog,然后 进行 refresh 操作,刷入到 filesystem cache ,这符合 WAL 的 数据库操作思想,然后被 searched 到 ,这里搜索到的数据来源是 translog 中存储的记录。
  • flush 在数据进入 filesystem cache 之后,会进行 flush ,将数据写入磁盘,并且删除 translog

如果在 flush 完成之前,系统发生故障,意外重启, 数据和操作将会从 translog 中恢复和继续,

所以,当一个索引被大量写入更新时,就会产生大量的 segment 文件,

es数据压缩率_搜索_02


写入一段时间后,segment 数量大量增长

es数据压缩率_es数据压缩率_03


es数据压缩率_elasticsearch_04

由于业务上频繁写入 ,所以,refresh 建议被设置到 30s 或者-1 (这里设置为 -1 ,并不代表用于都不去刷新了)这样自然的: segments 并不会很迅速的增长,但是还是在不断增长,用不了多久就会有很多的segment。

segment会消耗系统的文件句柄,内存,CPU时钟。最重要的是,每一次请求都会依次检查所有的segment。segment越多,检索就会越慢,对于不再有写入的更新的index,才建议 force merge ,不然反而会让搜索的性能更差。所以,可以等待业务空闲期,使用以下的 api 进行合并

注意,对非大量写入的index 使用这个命令。

GET _cat/segments/precedent?v

# 期望合并到 15 个 segments 
POST precedent/_forcemerge?max_num_segments=15

注意!合并带来的 segments.memory 减少是很少的,能显著带来减少的,主要还是对老旧索引的 close 和 delete ,_forcemerge 更应该被称作 压榨 。 对于大部分无时无刻在更新的索引,es默认的合并机制就是最优解。

合并的策略

在没有更新的时候进行合并_forcemerge 的操作, 理想的情况下,合并成1个当然是最好,建议将 segments 单个大小大概合并到 1-5G 左右即可。

再来回顾一下 es 倒排索引的实时更新策略

  • 倒排索引
    lucene 使用倒排索引让数据可以被搜索到。用 token 来标记含有这些 token 的文档 以提供查询
  • 分片
    分片是 Lucene 的一个实例。就其本身而言它是一个功能完善的搜索引擎。一个 Index 也许会包含一个 shard ,更多情况下会包含更多的 shards ,这让 index 增长,并分布在不通的节点机器上。
    主分片 primary 是文档的入口,副本分片 replica 是主分片的复制,这个机制保证了主分片 down 掉的时候,能够由 replica 提供读取服务

    一个分片包含多个 段 ,一个段是一个倒排索引。一个查询进入一个shard,将会依次查询这个shard中的每个段,当轮询结束时候,再合并这些结果到 shard 。
    如我们在上面说到的 es倒排索引生成策略 ,当你index 产生一个文档的时候,es 收集他们到内存中,并执行 translog + refresh ,将新的小段写入filesystem cache,以提供实时的索引。
    这使得段中的数据得以被搜索到,search - true 阶段。但是这个时候,数据还没有写入磁盘,所以还是有丢失数据的风险,还好,es 将会按照一定策略来执行 flush 操作,将数据最终写入 disk ,也就是 commit - true 阶段,然后删除 translog
    segment 越多,查询越慢 ,es 会自己在后台合并分段,将小分段合并成一个大的,段是不可变的,合并分段的过程中,将删除那些 old version 的文档
2. 分片的最佳平均大小应该设计为多少?
  • 分片的平均大小应在 几GB到几十GB 之间。
  • 确定用例的最佳大小的最佳方法是使用您自己的数据和查询进行测试。
  • 此外,节点数 和 分片数目 应该设计成 比例,这样有利于 分片 平均的 负载在 各个节点上,这样查询时产生的节点消耗将 均衡

es数据压缩率_倒排索引_05

3. 副本的最佳数量是多少?

副本的基础功能,容灾,从这个角度来看,索引至少要有 一个副本 冗余,索引写入的数据会被复制到副本上,所以,当一个索引有着大量频繁的写入时,数据复制 会导致资源增加

4. 索引刷新间隔修改

在执行大量的跑数据,或者频繁 scroll ,search 之前调整 refresh_interval 的 值 为 30s (默认为1s)或者更多 ,从而 减少索引更新带来的开销,提升写入/读取的速度,而不是实时搜索,例如上面的 索引,在实际业务中有很频繁的更新,但是不需要很实时的被查询到,所以我们的索引 refresh_interval 被设置到了 30s 。

# 注意这里的 s 单位 

# 调整刷新时间为 30s 刷新一次
PUT / index_name
{
  "settings": {
    "refresh_interval": "30s" 
  }
}

# 调整刷新时间为 不主动刷新 (缓冲区满,30s定时器检查刷新)
PUT / index_name
{
  "settings": {
    "refresh_interval": "-1s" 
  }
}
5. 使用最小的足够用的数值类型

我们在 mysql 中 经常会提起 char varchar ,int bigint 在设计表时,带来的存储空间的差异,同样 es 中 也会有这样的问题

byte,short,integer,long
half_float,float,double
6. 设置 fielddata 缓存占用jvm内存的 30%或者更小

当我们进行大量读操作之后。查询可能会出现这样的问题,Elasticsearch exception [type=circuit_breaking_exception, reason=[parent] Data too large, data , 这是因为分配给es 的堆内存不够用,不足以支持查询。

# 报错原因
#indices.breaker.fielddata.limit:此参数设置Fielddata断路器限制大小(公式:预计算内存 + 现有内存 <= 断路器设置内存限制),默认是60%JVM堆内存,当查询尝试加载更多数据到内存时会抛异常(以此来阻止JVM OOM发生)
PUT _cluster/settings
{
  "persistent": {
    "indices": {
      "breaker": {
        "fielddata.limit": "30%"
      }
    }
  }
}

# 清空集群缓存
POST /_cache/clear?pretty

#清空 index 缓存
POST /index/_cache/clear?pretty

#清空 index fielddata 缓存 (推荐,需要等待)
POST /precedent/_cache/clear?fielddata=true
7. 不要自己创建文档ID

ES 默认生成ID类似这种, AM95-K98832JJ ,如果我们指定ID ,生成时,会查询整个分片是否有该ID,在TB级索引中,是一个消耗比较大的动作