转 Elasticsearch 内部数据结构深度解读
 2387 |  0 |  1
最近知识星球里几个问题都问到了 doc values、store field、fielddata 等的概念。
问题1:”群主有介绍 doc value, field data, store fields 比较好的文章么?一直感觉有点模糊“
问题2:“请教下星主关于ES存储相关的问题, 一个文档有如下几个地方可能会存储:
- 倒排索引。
- Source 字段。
- store 存储(如果开启)
- doc_values。
不知道我理解的是否正确?
如果这几个地方都存储, 那是不是可以理解为数据大致会膨胀了4倍?
死磕 Elasticsearch 知识星球(http://t.cn/RmwM3N9)
非常有必要好好梳理一下,于是就有了这篇文章。
Elasticsearch 数据结构的理解和合理使用,对深入理解 Elasticsearch大有裨益!
正如 Elastic 官方文档所说:
Elasticsearch 特点之一是:分布式文档存储。
Elasticsearch不会将信息存储为类似列数据库的行(row),而是存储为已序列化为JSON文档的复杂数据结构。
当集群中有多个Elasticsearch节点时,存储的文档会分布在整个集群中,并且可以从任何节点立即访问。
存储文档后,将在1秒钟内(默认刷新频率为1s)几乎实时地对其进行索引和完全搜索。
**如何做到快速索引和全文检索的呢? **
Elasticsearch使用倒排索引的数据结构,该结构支持非常快速的全文本搜索。
倒排索引列出了出现在任何文档中的每个唯一单词,并标识了每个单词出现的所有文档。
索引可以认为是文档的优化集合,每个文档都是字段的集合,这些字段是包含数据的键值对。

默认情况下,Elasticsearch 对每个字段中的所有数据建立索引,并且每个索引字段都具有专用的优化数据结构。
例如,文本字段存储在倒排索引中,数字字段和地理字段存储在BKD树中。
| 数据类型 | 数据结构 | 
|---|---|
| text/keyword | 倒排索引 | 
| 数字/地理位置 | BKD树 | 
不同字段具有属于自己字段类型的特定优化数据结构,并具备快速响应返回搜索结果的能力使得 Elasticsearch 搜索飞快!
面对海量内容,如何快速的找到包含用户查询词的内容,倒排索引扮演了关键角色。
倒排索引是单词到文档映射关系的最佳实现形式。
下图是:书的末页的索引结构,展示了核心关键词与书页码的对应关系。

试想一下,没有这个索引页,根据关键词从全书查找有多慢,就能直观体会出索引的妙处!
拿官方文档的示例:
假设我们有两个文档,每个文档的 content 域包含如下内容:
- 1、The quick brown fox jumped over the lazy dog
- 2、Quick brown foxes leap over lazy dogs in summer
对索引编制索引会受到标记化和标准化的处理analysis。
数据索引化制约因素:分词器 analyzer 的选型。
倒排索引(基于 默认Standard 标准分词器分词)如下所示:
| Term | Doc_1 | Doc_2 | 
|---|---|---|
| Quick | X | |
| The | X | |
| brown | X | X | 
| dog | X | |
| dogs | X | |
| fox | X | |
| foxes | X | |
| in | X | |
| jumped | X | |
| lazy | X | X | 
| leap | X | |
| over | X | X | 
| quick | X | |
| summer | X | |
| the | X | 
如上所示,对于文档中的每个词,都包含了其所在文档的列表。
在 Elasticsearch 中,Doc Values 就是一种列式存储结构,默认情况下每个字段的 Doc Values 都是激活的(除了 text 类型),Doc Values 是在索引时创建的,当字段索引时,Elasticsearch 为了能够快速检索,会把字段的值加入倒排索引中,同时它也会存储该字段的 Doc Values。
区别于倒排索引的定义,Doc Values 被定义为:“正排索引”。

仍然 以 1.2 文档为例,Doc Values 结构如下所示(仅做举例):
| Doc | Terms | 
|---|---|
| Doc_1 | brown, dog, fox, jumped, lazy, over, quick, the | 
| Doc_2 | brown, dogs, foxes, in, lazy, leap, over, quick, summer | 
Doc values 通过转置两者间的关系来解决适用倒排索引聚合效率低、难以扩展的问题。
对比可以看出:倒排索引将词项映射到包含它们的文档,doc values 将文档映射到它们包含的词项。
Elasticsearch 中的 Doc Values 常被应用到以下场景:
注意:
因为文档值被序列化到磁盘,我们可以依靠操作系统的帮助来快速访问。
对于不需要:排序、聚合、脚本计算、地理位置过滤的业务场景,可以考虑禁用:Doc Values,以节约存储。
PUT my_index
{
  "mappings": {
      "properties": {
        "title": {
          "type": "keyword",
          "doc_values": false 
        }
    }
  }
}
如前第1、2小结所述:
text 类型字段是不支持 Doc Values正排索引的,text字段使用是:查询时创建的基于的内存数据结构(query-time in-memory data structure) fielddata。
fielddata 将 text 字段用于聚合、排序或在脚本中使用时,将按需构建此数据结构。
实现机理:它是通过从磁盘读取每个段的整个反向索引,反转词项↔︎文档关系并将结果存储在JVM堆中的内存中来构建的。
严格意义讲,2.2 的示例,放到这里会更合适。
DELETE test_001
PUT test_001
{
  "mappings": {
    "properties": {
      "body":{
        "type":"text",
        "analyzer": "standard",
        "fielddata": true
      }
    }
  }
}
POST test_001/_bulk
{"index":{"_id":1}}
{"body":"The quick brown fox jumped over the lazy dog"}
{"index":{"_id":2}}
{"body":"Quick brown foxes leap over lazy dogs in summer"}
GET test_001/_search
{
  "size": 0,
  "query": {
    "match": {
      "body": "brown"
    }
  },
  "aggs": {
    "popular_terms": {
      "terms": {
        "field": "body"
      }
    }
  }
}
_source 字段包含在索引时间传递的原始JSON文档主体。
_source 字段本身未构建索引(因此不可搜索),但已存储该字段,以便在执行获取请求(如get或search)时可以将其返回。
第一:尽管非常方便,但是source字段确实会导致索引内的存储开销。因此,可以将其禁用。
PUT my-index-000001
{
  "mappings": {
    "_source": {
      "enabled": false
    }
  }
}
第二:禁用前要做好以下衡量 禁用 _source 后,如下操作将不可用:
所以,要在存储空间、业务场景之间权衡利弊后选型。
默认情况下,对字段值进行索引以使其可搜索(第1节的 倒排索引),但不存储它们。
这意味着可以查询该字段,但是无法检索原始字段值。
通常这无关紧要。该字段值已经是_source字段的一部分,默认情况下已存储。
但,某些特殊场景下,如果你只想检索单个字段或几个字段的值,而不是整个_source的值,则可以使用源过滤来实现。
这个时候, store 就派上用场了。
DELETE news-000001
PUT news-000001
{
  "mappings": {
    "_source": {
      "enabled": false
    },
    "properties": {
      "title": {
        "type": "text",
        "store": true
      },
      "date": {
        "type": "date",
        "store": true
      },
      "content": {
        "type": "text"
      }
    }
  }
}
PUT news-000001/_doc/1
{
  "title":   "Some short title",
  "date":    "2021-01-01",
  "content": "A very long content field..."
}
GET news-000001/_search
GET news-000001/_search
{
  "stored_fields": [ "title", "date" ] 
}
如 5.2 示例,在某些情况下,存储字段可能很有意义。例如,采集的新闻数据是:带有标题、日期和很大内容字段的文档,
则可能只想检索标题和日期,而不必从较大的_source字段中提取这些字段。
回到文章开头的两个问题:
对于不明白的问题,反复研读官方文档,拷贝到kibana Dev tool 去实践,直到弄明白为止。
文章尽量参考官方文档,尽管如此,难免表述纰漏,欢迎大家指正交流。
和你一起,死磕 Elasticsearch !
参考:
本文分享自微信公众号 - 铭毅天下(elastic999)。
如有侵权,请联系删除。
1

135****3683
14人已关注
 领课教育 32527
领课教育 32527
 10323
 update 47765
update 47765
 5155
 领课教育 18473
领课教育 18473
 husheng 21150
husheng 21150
 请更新代码 41836
请更新代码 41836
 凯哥Java 2425
凯哥Java 2425
 凯哥Java 2856
凯哥Java 2856
 凯哥Java 2152
凯哥Java 2152