Elasticsearch倒排索引、文档值、存储和原文

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: 本文介绍Elasticsearch索引的基础概念,包括倒排索引(inverted index)、文档值(doc_values)、存储(store)和原文(source)。

倒排索引


Elasticsearch 使用一种称为倒排索引的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。


假设我们有两个文档,每个文档的正文字段包含如下内容:


  1. The quick brown fox jumped over the lazy dog
  2. Quick brown foxes leap over lazy dogs in summer


为了创建倒排索引,我们首先将每个文档的正文字段,拆分成单独的词(我们称它为词条或 Tokens),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。


结果如下所示:


词条

文档1

文档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



现在,如果我们想搜索 Quick brown,我们只需要查找包含每个词条的文档:


词条

文档1

文档2

brown

X

X

quick

X


匹配词条数量

2

1


两个文档都匹配,但是第一个文档比第二个匹配度更高。如果我们使用,仅计算匹配词条数量的简单相似性算法,那么我们可以说,对于我们查询的相关性来讲,第一个文档比第二个文档更佳。



但是,我们目前的倒排索引有一些问题:


·      Quick quick 以独立的词条出现,然而用户可能认为它们是相同的词。


·      fox foxes 非常相似,就像 dog dogs,他们有相同的词根。


·      jumped leap,尽管没有相同的词根,但他们是同义词。


使用前面的索引搜索 + Quick + fox 不会得到任何匹配文档。(记住,+ 前缀表明这个词必须存在)只有同时出现 Quick fox 的文档才满足这个查询条件,但是第一个文档包含 quick fox,第二个文档包含 Quick foxes


我们的用户可以合理的期望两个文档与查询匹配,我们可以做的更好。


如果我们将词条规范为标准模式,那么我们可以找到与用户搜索的词条不完全一致,但具有足够相关性的文档,例如:


·      Quick 可以小写化为 quick


·      foxes 可以词干提取 -- 变为词根的格式 -- 为 fox。类似的,dogs 可以为提取为 dog


·      jumped leap 是同义词,可以索引为相同的单词 jump



现在索引看上去像这样:


词条

文档1

文档2

brown

X

X

dog

X

X

fox

X

X

in


X

jump

X

X

lazy

X

X

over

X

X

quick

X

X

summer


X

the

X



这还远远不够。我们搜索 +Quick +fox 仍然会失败,因为在我们的索引中,已经没有 Quick 了。但是,如果我们对搜索的字符串,使用与正文字段相同的标准化规则,会变成查询 +quick +fox,这样两个文档都会匹配。


禁用索引


默认情况下,Elasticsearch 文档每个字段都会被索引。如果某些字段不需要支持查询,可以在映射中配置 "index": false ,减少存储空间占用,并且提升写入速度。


例如,文章的标题、正文、发布时间字段,需要创建索引,文章的 url 字段不需要被索引,创建索引映射时可以按以下方式禁用它:

PUTnews{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1  },
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word"      },
"content": {
"type": "text",
"analyzer": "ik_max_word"      },
"createtime": {
"type": "date"      },
"url": {
"type": "keyword",
"index": false      }
    }
  }
}


文档值  


Elasticsearch 中,Doc Values 是一种列式存储结构。Doc Values 默认对所有字段启用,除了 text 和annotated_text 类型字段。


Doc Values 是在索引时创建的,当字段索引时,Elasticsearch 为了能够快速检索,会把字段的值加入倒排索引中,同时它也会存储该字段的 Doc Values


Elasticsearch 中的 Doc Values 常被应用到以下场景


·      对一个字段进行排序


·      对一个字段进行聚合


·      某些过滤,比如地理位置过滤


·      某些与字段相关的脚本计算


·      使用 docvalue_fields 返回搜索结果部分字段值


因为文档值被序列化到磁盘,可以依靠操作系统的帮助来快速访问。当数据集远小于节点的可用内存,操作系统会自动将所有的 Doc Values 保存在内存中,使得其读写十分高速;当其远大于可用内存,操作系统会自动把 Doc Values 加载到系统的页缓存中,从而避免了 JVM 堆内存溢出异常。


列式存储的压缩


Doc Values 本质上是一个序列化的列式存储,适用于聚合、排序、脚本等操作。这种存储方式也非常便于压缩,特别是数字类型,这样可以节约磁盘空间,并且提高访问速度。


来看一组数字类型的 Doc Values,了解它如何压缩数据:


文档

词条

文档1

100

文档2

1000

文档3

1500

文档4

1200

文档5

300

文档6

1900

文档7

4200


按列布局意味着我们有一个连续的数据块:[100,1000,1500,1200,300,1900,4200]


因为我们已经知道他们都是数字,而不是像文档或行中看到的异构集合,所以我们可以使用统一的偏移来将他们紧紧排列。


而且,针对这样的数字有很多种压缩技巧。你会注意到这里每个数字都是 100 的倍数,Doc Values 会检测一个段里面的所有数值,并使用一个最大公约数,方便做进一步的数据压缩。


如果我们保存100 作为此段的除数,我们可以对每个数字都除以100,然后得到: [1,10,15,12,3,19,42]


现在这些数字变小了,只需要很少的位就可以存储下,也减少了磁盘存放的大小。Doc Values 在压缩过程中使用如下技巧,它会按依次检测以下压缩模式:


1.    如果所有的数值各不相同或缺失,设置一个标记并记录这些值


2.    如果这些值小于 256,将使用一个简单的编码表


3.    如果这些值大于 256,检测是否存在一个最大公约数


4.    如果没有存在最大公约数,从最小的数值开始,统一计算偏移量进行编码


你会发现这些压缩模式不是传统的通用压缩算法,比如 DEFLATE 或者 LZ4。因为列式存储的结构是严格且良好定义的,我们可以通过使用专门的模式来达到,比通用压缩算法(如 LZ4)更高的压缩效果。



禁用 Doc Values


Doc Values 默认对所有字段启用,除了 text 和 annotated_text 类型字段。也就是说所有的数字、地理坐标、日期、IP 和 keyword 类型都会默认开启。


Text 类型字段不能使用 Doc Values,文本经过分析流程生成很多 Token,使得 Doc Values 不能高效运行。


因为 Doc Values 默认启用,你可以选择对你数据集里面的大多数字段,进行聚合和排序操作。如果你知道你永远也不会对某些字段进行聚合、排序或是使用脚本操作,你可以通过禁用特定字段的 Doc Values。这样不仅节省磁盘空间,也会提升索引的速度。


要禁用 Doc Values,在字段的映射 (mapping) 设置 doc_values: false 即可。例如,这里我们创建了一个新的索引,字段 "session_id" 禁用了 Doc Values


PUTmy_index{
"mappings": {
"properties": {
"session_id": {
"type": "keyword",
"doc_values": false      }
    }
  }
}


通过设置 doc_values: false,这个字段将不能被用于聚合、排序以及脚本操作。


反过来也是可以进行配置的:让一个字段可以被聚合,通过禁用倒排索引,使它不能被正常搜索,例如:


PUTmy_index{
"mappings": {
"properties": {
"customer_token": {
"type": "keyword",
"doc_values": true,
"index": false      }
    }
  }
}


通过设置 doc_values: true 和 index: false,我们得到一个只能被用于聚合/排序/脚本的字段。无可否认,这是一个非常少见的情况,但很有用。



存储


默认情况下,字段原始值会被索引用于查询,但是不会被存储。为了展示文档内容,通过一个叫  _source  的字段用于存储整个文档的原始值。


在字段的映射 (mapping) 设置 store: true,可以使索引单独保存这个字段。通常情况下,如果文档本身十分庞大,而一些字段又会经常单独使用,那么这样的字段,就可以设置为单独存储,然后可以使用 stored_fields 单独检索这些字段。


例如,如果你的文档包含标题、时间和一个很大的正文字段,你可能只需要检索标题、时间字段,没必要从很大的 _source 原文中解析出这些字段:


#创建索引,指定常用字段store属性PUT/my-index-000001{
"mappings": {
"properties": {
"title": {
"type": "text",
"store": true      },
"date": {
"type": "date",
"store": true      },
"content": {
"type": "text"      }
    }
  }
}
#插入记录PUT/my-index-000001/_doc/1{
"title":   "短文本标题",
"date":    "2021-05-01",
"content": "很长很长很长的正文字段..."}
#查询结果返回stored_fields指定字段GET/my-index-000001/_search{
"stored_fields": [ "title", "date" ] 
}


注意:stored_fields 返回结果是数组格式。如果你需要获取原始文档,可以通过_source字段替代。



原文


_source 字段包含索引时发送的原始 JSON 文档。_source 字段本身不建索引,但是存储原始文档,以便在执行查询请求时,可以将其返回。可以通过设置,禁用原文字段,或者只存储特定字段。


_source 在 Lucene 中是映射为一个特殊的字段:


Field

Index

IndexType

Analyzer

DocValues

Store

_source

No

No

No

No

Yes


Elasticsearch 中 _source 字段的主要目的,是通过 doc_id 读取该文档的原始内容,所以只需要存储 Store 即可。


Elasticsearch 中使用 _source 字段可以实现以下功能:


Update:


部分更新时,需要从读取文档保存在 _source 字段中的原文,然后和请求中的部分字段合并为一个完整文档。如果没有 _source,则不能完成部分字段的 Update 操作。


Reindex:


可以通过 Reindex API 完成索引重建,过程中不需要从其他系统导入全量数据,而是从当前文档的 _source 中读取。如果没有 _source,则不能使用 Reindex API。


Script:


不管是 Index 还是 Search 的 Script,都可能用到存储在 Store 中的原始内容,如果禁用了 _source,则这部分功能不再可用。


Summary:


摘要信息也是来源于 _source 字段。



禁用 _source


尽管使用非常方便,但是 _source 字段会导致占用更多的存储空间。如果业务上不需要存储原始文档,可以按以下方式禁用它:


PUTmy-index-000001{
"mappings": {
"_source": {
"enabled": false    }
  }
}


注意:禁用 _source 会导致更新、重建索引、摘要功能不可用,生产环境慎用。考虑节省存储空间,可以通过修改索引设置 index.codec 提高压缩效率。



包含/排除部分字段


包含/排除 _source 部分字段可以按以下方式设置它:


PUTlogs{
"mappings": {
"_source": {
"includes": [
"*.count",
"meta.*"      ],
"excludes": [
"meta.description",
"meta.other.*"      ]
    }
  }
}
PUTlogs/_doc/1{
"requests": {
"count": 10,
"foo": "bar"  },
"meta": {
"name": "Some metric",
"description": "Some metric description", 
"other": {
"foo": "one", 
"baz": "two"    }
  }
}
GETlogs/_search{
"query": {
"match": {
"meta.other.foo": "one"    }
  }
}


相关实践学习
利用Elasticsearch实现地理位置查询
本实验将分别介绍如何使用Elasticsearch7.10版本进行全文检索、多语言检索和地理位置查询三个Elasticsearch基础检索子场景的实现。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
相关文章
|
2月前
|
存储 算法 搜索推荐
Elasticsearch 的倒排索引
Elasticsearch 的倒排索引
39 2
|
9天前
|
存储 索引
Elasticsearch中父子文档的关联:利用Join类型赋予文档的层级关系
Elasticsearch中父子文档的关联:利用Join类型赋予文档的层级关系
|
9天前
|
存储 JSON 自然语言处理
技术经验分享:Elasticsearch倒排索引结构
技术经验分享:Elasticsearch倒排索引结构
|
10天前
|
存储 自然语言处理 NoSQL
深入解析Elasticsearch的内部数据结构和机制:行存储、列存储与倒排索引之倒排索引(三)
深入解析Elasticsearch的内部数据结构和机制:行存储、列存储与倒排索引之倒排索引(三)
|
10天前
|
存储 自然语言处理 NoSQL
深入解析Elasticsearch的内部数据结构和机制:行存储、列存储与倒排索引之列存(二)
深入解析Elasticsearch的内部数据结构和机制:行存储、列存储与倒排索引之列存(二)
|
10天前
|
存储 JSON NoSQL
深入解析Elasticsearch的内部数据结构和机制:行存储、列存储与倒排索引之行存(一)
深入解析Elasticsearch的内部数据结构和机制:行存储、列存储与倒排索引之行存(一)
|
2月前
|
存储 数据处理 索引
Elasticsearch 8.X 小技巧:使用存储脚本优化数据索引与转换过程
Elasticsearch 8.X 小技巧:使用存储脚本优化数据索引与转换过程
57 6
|
2月前
|
自然语言处理 测试技术 网络安全
ElasticSearch7最新实战文档-附带logstash同步方案
ElasticSearch7最新实战文档-附带logstash同步方案
31 0
|
2月前
|
存储 缓存 搜索推荐
深入理解Elasticsearch倒排索引原理与优化策略
总之,Elasticsearch的倒排索引是其高效全文搜索的核心。为了提高性能和可伸缩性,Elasticsearch采用了多种优化策略,包括压缩、分片、合并、位集合和近实时搜索等。这些策略使Elasticsearch成为处理大规模文本数据的强大工具。
156 0
|
2月前
|
存储 安全 数据处理
Elasticsearch 为什么会产生文档版本冲突?如何避免?
Elasticsearch 为什么会产生文档版本冲突?如何避免?
54 0