elasticsearch的硬盘存储真的惨不忍睹吗?其实没那么糟糕!

333 阅读14分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

ElasticSearch在当今NOSQL中也算独树一帜的存在了,借助于强大的全文搜索能力以及本身具备的多维度搜索以及聚合能力,再加上母公司Elastic公司的良好运作以及ELK体系的普及,使得ElasticSearch在很多场景下都是NOSQL不二的选择。

但是所谓人红是非多,再加上NOSQL数据库都有自己的优缺点和适用场景,没办法做到One Stack to Rule Them All,所以ElasticSearch的很多缺点也同样会被大家拿到台面上审视和批评。在这其中除了跨表无法join查询之外,最大的问题可能就是占用资源过多,而其中硬盘利用率低以及磁盘占用量过大的问题被讨论的尤其多。

今天这篇文章就来一起通过细节分析来对ElasticSearch的硬盘存储表现做一个系统的分析。首先看一下下面两个问题:

  • 什么原因导致ElasticSearch的硬盘存储表现不好?
  • 怎么做能拯救下ElasticSearch的硬盘存储表现,让它尽量表现的好一些?

搞清楚这两个问题,就能了解ElasticSearch的硬盘存储到底表现如何,以及在使用ElasticSearch的场景中如何优化ElasticSearch的硬盘使用问题。下面就从这两个问题的解答来展开本文。

什么原因导致ElasticSearch的硬盘存储表现不好

首先先说明一个问题,ElasticSearch的硬盘存储确实是表现很差劲的地方,尤其是开箱即用场景下的ElasticSearch。这里面涉及到的问题既有ElasticSearch本身的设计特点,也就是说ElasticSearch天生就不是省硬盘存储的数据库;又有使用方面的用法问题,导致了很多磁盘存储以及性能的浪费。前者无法改变,而后者则可以通过各种方式进行优化。下面就从这两方面来分析下ElasticSearch硬盘存储的相关知识。

天生缺陷

天生缺陷是ElasticSearch为了完成更重要的设计目标而在硬盘存储上的妥协设计,这些设计导致ElasticSearch在硬盘存储上表现很差,但是作为使用者我们无能为力。下面就来看看这些问题:

多种存储格式

为了支持多种设计目标和功能场景,ElasticSearch在硬盘存储上分为四类:

  • 正排索引:行存数据,存储doc_id -> value的对应关系,ElasticSearch中的硬盘占用大户即原始数据_source字段就存在这里。
  • 倒排索引:行存数据,存储term -> doc_id的对应关系。针对全文索引场景,还会生成倒排索引链文件,用以记录全文索引中相关的特有属性和信息,用以支持全文匹配以及评分计算等。
  • 聚合数据:列存数据,主要用来支持ElasticSearch中相关的聚合以及排序场景。另外这里的聚合以及排序的数据是针对docValues类型的数据,针对不需要进行分词的字符串即not_analyzed的string,ElasticSearch也可以进行聚合和排序,但是这个排序是通过FieldData实现的,而FieldData是在查询的时候在JVM的Heap中构建的,不存储在硬盘上,所以不再讨论范围内。
  • 元数据:这个比较好理解,就是支撑和组织ElasticSearch硬盘存储的各种元数据,数据量比较小。

上面介绍的四大类数据,下面来看看细分的文件类型:

名称文件后缀描述
Segments commit pointsegments_N存储segment最新的commit point文件,没更新一次,N加1
write lockwrite.lock防止多个ElasticSearch进程同时写入一个目录
Segment Info.sisegment的元数据文件
Compound File.cfs, .cfe一个segment包含了如下表的各个文件,为减少打开文件的数量,在segment小的时候,segment的所有文件内容都保存在cfs文件中,cfe文件保存了lucene各文件在cfs文件的位置信息
Fields.fnm保存了fields的相关信息
Field Index.fdx正排存储文件的元数据信
Field Data.fdt存储了正排存储数据,写入的原文存储在这
Term Dictionary.tim倒排索引的元数据信息
Term Index.tip倒排索引文件,存储了所有的倒排索引数据
Frequencies.doc保存了每个term的doc id列表和term在doc中的词频
Positions.posStores position information about where a term occurs in the index,全文索引的字段,会有该文件,保存了term在doc中的位置
Payloads.payStores additional per-position metadata information such as character offsets and user payloads,全文索引的字段,使用了一些像payloads的高级特性会有该文件,保存了term在doc中的一些高级特性
Norms.nvd, .nvm文件保存索引字段加权数据
Per-Document Values.dvd, .dvmlucene的docvalues文件,即数据的列式存储,用作聚合和排序
Term Vector Data.tvx, .tvd, .tvfStores offset into the document data file,保存索引字段的矢量信息,用在对term进行高亮,计算文本相关性中使用
Live Documents.liv记录了segment中删除的doc

元数据

lucene数据元信息文件

文件名为:segments_N

该文件为lucene数据文件的元信息文件,记录所有segment的元数据信息。每更新一次N就加1。

该文件会在index buffer refresh后更新最新的commit point相关的信息。主要记录了目前有多少segment,每个segment有一些基本信息,更新这些信息定位到每个segment的元信息文件。

segment的元信息文件

文件后缀:.si

每个segment都有一个.si文件,记录了该segment的元信息。

segment元信息文件中记录了segment的文档数量,segment对应的文件列表等信息。

fields信息文件

文件后缀:.fnm

该文件存储了fields的基本信息。

fields信息中包括field的数量,field的类型,以及IndexOpetions,包括是否存储、是否索引,是否分词,是否需要列存等等。

正排索引

数据存储文件

文件后缀:.fdx, .fdt

索引文件为.fdx,数据文件为.fdt。

数据文件的功能为根据自动的文档id,得到文档的内容,搜索引擎的术语习惯称之为正排索引,即doc_id -> value,es的_source数据就存在这,也是ElasticSearch的存储大户。

索引文件记录了快速定位文档数据的索引信息,数据文件记录了所有文档id的具体内容。

所有的GET请求在磁盘上就是通过这个接口进行的查询(translog文件部分另算)。

倒排索引

倒排索引文件

索引后缀:.tip,.tim

.tip为索引文件,.tim为数据文件。

索引文件包含了每个字段的索引元信息,数据文件有具体的索引内容。

5.5.0版本的倒排索引实现为FST tree,FST tree的最大优势就是内存空间占用非常低,用来加载到内存中增加search查询的效率。

所有的Search请求在磁盘上就是通过这个接口进行的查询(translog文件部分另算)。

倒排索引链文件

文件后缀:.doc, .pos, .pay

.doc保存了每个term的doc id列表和term在doc中的词频,用于ElasticSearch中的BM25算法的打分依据。

全文索引的字段会有.pos文件,保存了term在doc中的位置

全文索引的字段使用了一些像payloads的高级特性才会有.pay文件,保存了term在doc中的一些高级特性

聚合数据

列存文件(docvalues)

文件后缀:.dvm, .dvd

索引文件为.dvm,数据文件为.dvd。

lucene实现的docvalues有如下类型:

  1. NONE 不开启docvalue时的状态
  2. NUMERIC 单个数值类型的docvalue主要包括(int,long,float,double)
  3. BINARY 二进制类型值对应不同的codes最大值可能超过32766字节,
  4. SORTED 有序增量字节存储,仅仅存储不同部分的值和偏移量指针,值必须小于等于32766字节
  5. SORTED_NUMERIC 存储数值类型的有序数组列表
  6. SORTED_SET 可以存储多值域的docvalue值,但返回时,仅仅只能返回多值域的第一个docvalue

对应not_anaylized的string字段,使用的是SORTED_SET类型,number的类型是SORTED_NUMERIC类型

其中SORTED_SET 的 SORTED_SINGLE_VALUED类型包括了两类数据 : binary + numeric, binary是按order排序的term的列表,numeric是doc到order的映射。

这里再多说一句,其实ElasticSearch中的列存数据除了docvalues之外,还有fielddata。这里没有对fielddata做说明是因为fielddata是在查询时构建并存放在内存中,在硬盘上并没有相关的存储,所以不在本文讨论范围之内。

集群状态数据

state文件夹下.st后缀的文件,用以记录整个集群状态信息,定时与master同步更新,由于文件大小很小且必须,非本文讲解重点,所以此处不过多展开了

translog数据

translog文件夹下的文件,用来记录还在内存中未持久化的数据的日志,在节点宕机重启后用来恢复数据,由于该文件有固定的滚动策略,大小总体固定,非本文讲解重点,所以此处不过多展开了

压缩算法的均衡

除了存储的文件多种多样外,为了保证存储性能与压缩与解压缩时的性能与CPU消耗,ElasticSearch并没有选择压缩性能最好的算法,反而选择了LZ4算法。

LZ4是目前效率最高的压缩算法,更加侧重压缩解压速度,而不是压缩比。这就使得原本硬盘存储表现并不是很好的ElasticSearch雪上加霜。

人为原因

除了上述ElasticSearch天生的缺陷外,更重要的原因还是人为原因造成的。

开箱即用是把双刃剑

在说这个问题之前,我先举个例子类比下。最近我在看房子装修,由于自己没有精力管理装修,于是考虑找装修公司接手。

在接触的装修公司中,有一个公司报价15万左右,另一个报价10万左右,在看完报价单上的主材以及轻工辅料后,感觉差别并不大。这时候我就纳闷了,同样的材料为什么价格差这么多呢?如果正常情况下如果大家对价格比较敏感的话,可能就会直接选择10万那家了。

但是接下来的剧情发展既在我的意料之外,又在我的意料之中。随着沟通的深入,我发现15万这家的方案中几乎包含了正常家装的所有项,甚至很多项属于优化,还可以剪除,属于开箱即用的版本;而10万那家的方案仅包含必要的套餐,如果想达到居住的标准,需要额外增加一些项目,属于基础套餐+优化增项的模式。而在同样的标准和交付下,两者的价格差距就很小了,远没有当初看的那么悬殊。

上面的例子说明了当前的两种模式,并没有好坏一说,只有适合不合适。而ElasticSearch明显属于前者。开箱即用再给予客户低使用门槛的同时,也增加了很多成本,存储成本即是如此。

试想下,如果你的场景中就没有全文检索或者分词的需求,那么在生成文件时是否可以通过某些设置减少一部分文件的生成?同样如果一个数值字段你只想精确匹配,而不想进行大小比较,是不是可以通过设置成keyword类型而减少很多索引的生成?

相对高昂的优化成本

这个问题可能是所有数据库的通病,也是不可避免的成本。毕竟一千个场景有一千种配置,很难一套配置应用于所有的数据库,尤其是NOSQL数据库。

这方面ElasticSearch表现的更为突出,更多的功能,更多的可配置项以及更多的版本,导致配置优化工作是个门槛极高的工作,需要在长期的使用中不断的优化自身的配置,尤其是低延迟、高并发高吞吐量的场景。

怎么做能拯救下ElasticSearch的硬盘存储表现,让它尽量表现的好一些

上面说了很多ElasticSearch在硬盘存储上的问题及缺点,那么有办法拯救吗?虽然困难但是显然是有的。

调整压缩算法

_source和设置为"store": true的字段占用磁盘空间都比较多。默认情况下,它们都是被压缩存储的。默认的压缩算法为LZ4,可以通过使用best_compression来执行压缩比更高的算法:DEFLATE。但这会占用更多的CPU资源。

PUT index
{
       "settings": {
              "index": {
                     "codec""best_compression"
              }
       }
}

压缩比表格数据

数据按需存储

禁用对你来说不需要的特性

这个主要是在创建index时设置mapping来控制的,将不需要的属性去掉,可以减少掉很多文件以及数据的生成,内容较多,类似上文的描述:

如果你的场景中就没有全文检索或者分词的需求,那么在生成文件时是否可以通过某些设置减少一部分文件的生成?同样如果一个数值字段你只想精确匹配,而不想进行大小比较,是不是可以通过设置成keyword类型而减少很多索引的生成?

网上的资料很多,大家感兴趣的可以在网上找找,如果想要完整版的给以给我留言,我在后续的文章中整理一下。

禁用doc values

所有支持doc value的字段都默认启用了doc value。如果确定不需要对字段进行排序或聚合,或者从脚本访问字段值,则可以禁用doc value以节省磁盘空间。

谨慎使用默认的dynamic字符串映射

默认的动态字符串映射会把字符串类型的字段同时索引为 text 和keyword。如果只需要其中之一,则显然是一种浪费。通常,id字段只需作为 keyword类型进行索引,而content字段只需作为text类型进行索引。

要禁用默认的动态字符串映射,则可以显式地指定字段类型,或者在动态模板中指定将字符串映射为text或keyword。

定期管理数据

ElasticSearch中存储的数据应该进行数据管理,这样技能提升资源的使用率也能提升数据的使用效率。主要有两方面:

  • 通过ElasticSearch模板按照日期建索引,然后根据时间属性进行数据的迁移或者删除
  • 通过高版本ElasticSearch提供的ILM来管理ElasticSearch的冷热温数据

前者网上的资料很多,这里就不详细展开了,大家感兴趣的可以参考ElasticSearch的curator的实现;后者的话,可以参考我之前的博文:

统计战果

通过上面的一顿折腾,是时候看看战果如何了。

笔者没有自己测试,找了一些公开的测试结果,效果总结如下:

  • 禁用_source,空间占用量下降30%左右;
  • 禁用doc values,空间占用量下降10%左右;
  • 压缩算法将LZ4改为Deflate,空间占用量可以下降15%~25%。

本文的测试结果可作一定参考,但是实际业务最好使用自己的样本数据进行压力测试以获取准确的结果。

文章到这里就结束了,最后路漫漫其修远兮,大数据之路还很漫长。如果想一起大数据的小伙伴,欢迎点赞转发加关注,下次学习不迷路,我们在大数据的路上共同前进!