1. 背景介绍
最近在使用ES搭建时序数据和日志的存储,阅读了一下ES的官方文档和博客,ES mapping里有很多设置选项,初看令人眼花缭乱,理不清楚之间的关系。这些选项设置不当,有的可能浪费存储空间,有的可能导致无法使用Aggregation,有的可能导致不能检索。
在这里综合他人的博客1和自己的经验,对照ElasticSearch v6.6记录一下,希望对你在学习ES的过程中有一点帮助。
2. ES存储–概述
众所周知,ES是以其全文检索功能而著名的,提到全文检索,大家的反应就是倒排索引;事实上,一个输入的文档会被ES以多种方式进行存储,从而使得ES在全文检索外还支持排序、聚合等功能。
ES的检索功能在底层使用的是Lucene,根据"Elasticsearch as a column store”2描述,Lucene的索引包含以下部分:
A Lucene index is made of several components: an inverted index, a bkd tree, a column store (doc values), a document store (stored fields) and term vectors, and these components can communicate thanks to these doc ids.
其中:
- inverted index: 倒排索引,不用多说。
- bkd tree: Block k-d tree,用于在高维空间内做索引,如地理坐标的索引。
- column store: 列式存储,可以充分利用操作系统的缓存,批量读取连续的数据以提高排序和聚合的效率。(说道列式存储,联想到了Cassandra)
- document store: 用于存储文档,功能和_source字段有重合,下文详细介绍区别。
- term vectors: 用于存储各个词在文档中出现的位置等信息。
在很多场合下,我们并不需要上述全部信息,因此可以通过设置mappings里面的属性来控制哪些是我们需要存储的,哪些不需要。
3. 配置项速查
这里列出了各个选项的名称、作用以及注意事项,仅供速查使用。详细解释请阅读下文。
配置项 | 作用 | 注意事项 | 默认值 |
---|---|---|---|
提供跨字段全文检索 | 会占用额外空间,把映射中的所有字段通过空格拼接起来做索引,在跨字段全文检索才需要打开;在v6.0+ [_all]被弃用,v7.0会正式移除,可以使用[copy_to]来自定义组合字段 | 关闭 | |
_source | 存储post json内容到ES的原始文档 | 会占用很多存储空间。数据压缩存储,读取会有额外解压开销,不需要读取原始字段内容可以考虑关闭,关闭后无法reindex | 开启 |
index | 是否加入倒排索引 | 关闭后无法对其进行搜索,字段仍会存储到_source和doc_values,字段可以被排序和聚合 | 开启 |
doc_values | 支持排序、聚合 | 会占用额外存储空间,与_source独立,同时开启doc_values和_source则会将该字段原始内容保存两份,数据在磁盘上采用列式存储,关闭后无法使用排序和聚合 | 开启 |
enabled | 是否对该字段进行处理 | 关闭后,只在_source中存储,类似index与doc_value的总开关 | 开启 |
store | 是否单独存储该field | 会占用额外存储空间,与_source独立,同时开启store和_source则会将该字段原始内容保存两份,字段单独存储,数据在磁盘上不连续,若读取多个字段需要seek多次,如需读取多个字段,需权衡比较_source与store效率 | 关闭 |
4. 配置项详解
在ES的mapping设置里,[_all], [_source]是mapping的云数据字段(Meta-Fields),[enabled], [index], [doc_values], [store]是mapping参数。
上述列表中的项目是在配置mappings时经常遇到的,其中有部分选项看似功能相似,实则不同,我们慢慢来分析:
_all
这个字段的作用是提供跨字段查询的支持。ES在查询的过程中,需要指定在哪一个field里面查询。例如下面的文档
{
“name”: “smith”,
“email”: "John@example.com"
}
用户在查询时,想查询叫做John的人,但是并不知道这个John出现在name字段中还是出现在email字段中,由于ES是为每一个字段单独建立索引,所以用户需要以John为关键词发起两次查询,分别查询name字段和email字段。
如果开启了_all字段,则ES会在索引过程中创建一个虚拟的字段_all,其值为文档中各个字段拼接起来所组成的一个很长的字符串,例如上面的例子,_all字段的内容为字符串”smith John@example.com”。随后,该字段将被分词打散,与其他字段一样被收入倒排索引中。由于该字段的内容都来自_source字段,因此默认情况下,该字段的内容并不会被保存,可以通过设置store属性来强制保存_all字段。
由于_all字段包含了所有字段的信息,因此可以实现跨字段的查询,即用户不用关心要查询的关键词在哪个字段中,ES可以将包含该关键词的文档全部检索出来,又用户进行下一步分析。
开启_all字段,会带来额外的CPU开销和存储,如果没有使用到,可以关闭_all字段。
_source
这个字段的作用是存储post到API接口的原始json文档。为什么要存储原始文档呢?因为ES采用倒排索引对文本进行搜索,而倒排索引无法存储原始输入文本。简单来说,一段文本交给ES后,首先会被分析器(analyzer)打散成单词,为了保证搜索的准确性,在打散的过程中,会去除文本中的标点符号,统一文本的大小写,甚至对于英文等主流语言,会把发生形式变化的单词恢复成原型或词根,然后再根据统一规整之后的单词建立倒排索引,经过如此一番处理,原文已经面目全非,因此需要有一个地方来存储原始的信息,以便在所搜到这篇文档的时候能够把原文返回给查询者。那么:
一定要存储原始文档吗?不一定! 如果只关心一篇文档是否存在,而不关心这个文档的内容,可以选择不保存_source字段。
可以只保存原始文档的一部分到_source里面吗? 可以,ES提供了过滤规则,可以只将一部分字段存入_source中。
如果不存储_source或仅存储部分内容,可以大量减小ES的存储占用量。但是,这样做有负面影响吗? 有!
不能获取到原文(废话)
无法reindex:如果存储了_source,当index发生损坏,或需要改变mapping结构时,由于存在原始数据,ES可以通过原始数据自动重建index,如果不存_source则无法实现
无法在查询中使用script:因为script需要访问_source中的字段。
index
这个属性用于控制一个字段是否需要被索引,默认情况下是开启的。如果关闭了index,则该字段的内容不会被analyze, 也不会存入倒排索引,即意味着该字段无法被搜索。
doc_values
倒排索引可以提供全文检索能力,但是无法提供对排序和数据聚合的支持。Doc_values 本质上是一个序列化的列式存储,这个结构非常适用于聚合(aggregations)、排序(Sorting)、脚本(scripts access to field)等操作。默认情况下,ES几乎会为所有类型的字段存储doc_value,为数不多的例外类型是analyzed类型,比如text类型字段就是analyzed类型。如果不需要对某个字段进行排序或者聚合,则可以关闭该字段的doc_value存储。
enabled
这是一个index和doc_value的总开关,如果enabled设置为false,则这个字段将会仅存在于_source中,其对应的index和doc_value都不会被创建。这意味着,该字段将不可以被搜索、排序或者聚合,但可以通过_source获取其原始值。
store
这个属性的作用是决定一个字段是否要被store,大家可能会有疑问,_source里面不是已经存储了原始的文档嘛,为什么还需要一个额外的store属性呢?原因如下:
如果禁用了_source保存,可以通过指定store属性来单独保存某个或某几个字段,而不是将整个输入文档保存到_source中。
如果_source中有长度很长的文本(如一篇文章)和较短的文本(如文章标题),当只需要取出标题时,如果使用_source字段,ES需要读取整个_source字段,然后返回其中的title,由此会引来额外的IO开销,降低效率。此时可以选择将title的store设置为true,在_source字段外单独存储一份。读取时不必在读取整个_source字段了。
但是需要注意,应该避免使用store查询多个字段,因为store的存储在磁盘上不连续,ES在读取不同的store值时,每个字段的读取均需要在磁盘上进行seek操作,而使用_source字段可以一次性连续读取多个字段3。
参考文献:
网友评论