文本字段存储在倒排索引中、数字字段和地理字段存储在BKD树(Bkd树是一种动态索引数据结构,能高效且可伸缩地索引大的多维点数据集。它有 (1) 极高的空间利用率和 (2) 优秀的查询、(3) 更新性能——且这三种属性在高强度更新下依旧成立。)中。由于按字段数据结构进行组合,并返回搜索结果的能力,使得Elasticsearch才能如此之快。
一、倒排索引概述
Elasticsearch 使用一种称为 倒排索引 的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。
- 正排索引:文档id到单词的关联关系
- 倒排索引:单词到文档id的关联关系
例如,假设我们有两个文档,每个文档的 content 域包含如下内容:
1、The quick brown fox jumped over the lazy dog
2、Quick brown foxes leap over lazy dogs in summer
为了创建倒排索引,我们首先将每个文档的 content 域拆分成单独的 词(我们称它为 词条 或 tokens ),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。结果如下所示:
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 |
------------------------
现在,如果我们想搜索 quick brown ,我们只需要查找包含每个词条的文档:
Term Doc_1 Doc_2
-------------------------
brown | X | X
quick | X |
------------------------
Total | 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
。
现在索引看上去像这样:
Term Doc_1 Doc_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 | X
------------------------
这还远远不够。我们搜索 +Quick +fox
仍然 会失败,因为在我们的索引中,已经没有 Quick
了。
但是,如果我们对搜索的字符串使用与 content 域相同的标准化规则,会变成查询 +quick +fox ,这样两个文档都会匹配!
Note:
这非常重要。你只能搜索在索引中出现的词条,
所以索引文本和查询字符串必须标准化为相同的格式。
分词和标准化的过程称为 分析 , 我们会在下个章节讨论。
二、倒排索引特点
2.1、倒排索引的结构
(1)包含这个关键词的document list
(2)包含这个关键词的所有document的数量:IDF(inverse document frequency)
(3)这个关键词在每个document中出现的次数:TF(term frequency)
(4)这个关键词在这个document中的次序
(5)每个document的长度:length norm
(6)包含这个关键词的所有document的平均长度
2.2、倒排索引不可变的好处
(1)不需要锁,提升并发能力,避免锁的问题
(2)数据不变,一直保存在os cache中,只要cache内存足够
(3)filter cache一直驻留在内存,因为数据不变
(4)可以压缩,节省cpu和io开销
2.3、倒排索引不可变的坏处
每次都要重新构建整个索引
2.4、删除与更新
段是不可变的,因此无法从旧的段中删除文档,也不能更新旧的段来反映文档的更新。相反,每个提交点 commit point 都包括一个 .del 文件,文件列出了哪个文档在哪个段中已经被删除了。
当文档被’删除’时,它实际上只是在 .del 文件中被标记为已删除。标记为已删除的文档仍然可以匹配查询,但在最终查询结果返回之前,它将从结果列表中删除。
文档更新也以类似的方式工作:
当文档更新时,旧版本文档被标记为已删除,新版本文档被索引到新的段中。也许文档的两个版本都可以匹配查询,但是在查询结果返回之前旧的标记删除版本的文档会被移除。
三、倒排索引结构详解
倒排索引主要包含两部分:
- 单词词典
- 倒排列表
3.1、单词词典(Term Dictionary)
单词词典(Term Dictionary):是倒排索引的重要组成,记录所有文档的单词,一般都比较大,记录单词到倒排列表的关联信息。
单词词典一般用 B+Trees 来实现:
3.2、倒排列表(Posting List)
倒排列表(Posting List):记录了单词对应的文档集合,由倒排索引项组成。
倒排索引项主要包含以下信息:
- 文档 ID,用来获取原始信息
- 单词频率(TF:Term Frequency),记录该单词在该文档中的出现次数,用于后续相关性算分
- 位置(Position),记录单词在文档中的分词位置(多个),用于做词语搜索
- 偏移(Offset),记录单词在文档的开始位置和结束位置,用于做高亮显示
以【搜索引擎】这个关键词为例,我们来看一下倒排列表:
2.3、单词字段和倒排列表整合在一起的结构
另外,es 存储的是一个 json 格式的文档,其中包含多个字段,每个字段都会有自己的倒排索引:
四、禁用倒排索引
对一个字段倒排索引也可以禁用,但是禁用之后不能被正常检索,示例如下:
PUT /my_index
{
"mappings": {
"properties": {
"gender": {
"type": "keyword",
"index": false
}
}
}
}