Elasticsearch 倒排索引

底层原理

正排索引(doc values )VS 倒排索引:

概念:从广义来说,doc values 本质上是一个序列化的 列式存储 。列式存储 适用于聚合、排序、脚本等操作,所有的数字、地理坐标、日期、IP 和不分析( not_analyzed )字符类型都会默认开启。

特点:倒排索引的优势 在于查找包含某个项的文档,相反,如果用它确定哪些项是否存在单个文档里。

总结:全文搜索需要用倒排索引,而排序和聚合则需要使用 正排索引。

在Mappings中有两个相关配置

1
2
3
4
5
doc_values:true/false 
为该字段创建正排索引,默认true,不支持text类型

index:true/false
为该字段创建倒排索引,默认为true
1
2
3
4
5
6
7
8
9
10
11
12
PUT /product
{
"mappings": {
"properties": {
"tags": {
"type": "text",
"index": "true"
//"doc_values": "true" text类型不支持
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
//当使用es自带的keyword时,它字段值是一个整体的精确匹配,并不会对字段值的内容进行分词
GET /product/_search
{
"aggs": {
"tags_group": {
"terms": {
"field": "tags.keyword"
}
}
}
}

doc_values正排索引不支持text字段,那text字段怎么进行聚合操作呢?

1
2
3
4
5
6
7
8
9
10
11
//当直接使用tags进行聚合操作,想要聚合tags中的分词后的terms词项,会报错
GET /product/_search
{
"aggs": {
"tags_group": {
"terms": {
"field": "tags"
}
}
}
}
1
2
3
4
Text fields are not optimised for operations that require per-document field data like aggregations and sorting, 
so these operations are disabled by default. Please use a keyword field instead.
Alternatively, set fielddata=true on [tags] in order to load field data by uninverting the inverted index.
Note that this can use significant memory.

大概的意思是,必须要打开fielddata=true,然后将正排索引数据加载到内存中,才可以对分词的field执行聚合操作,而且会消耗很大的内存。

1
2
3
4
5
6
7
8
9
10
//修改Mapping结构:开启tags字段 在使用聚合操作时使用 正排索引进行计算
PUT /product/_mapping
{
"properties": {
"tags": {
"type": "text",
"fielddata": true
}
}
}

这时候再次执行上文的tags的聚合操作,就不会报错了,那么fielddatadoc_values都是开启正排索引,他们之间有什么区别呢?

维度 doc_values fielddata
创建时间 index时创建 使用时动态创建
创建位置 磁盘 内存(jvm heap)
优点 不占用内存空间 不占用磁盘空间
缺点 索引速度稍低 文档很多时,动态创建开销比较大,而且占内存
默认值 true false

doc_values速度稍低这个是相对于fielddata方案的,其实仔细想想也可以理解。拿排序举例,相对于一个在磁盘排序,一个在内存排序。谁的速度快自然不用多说。

与 doc values 不同,fielddata 构建和管理 100% 在内存中,常驻于 JVM 内存堆。这意味着它本质上是不可扩展的。

fielddata可能会消耗大量的堆空间,尤其是在加载高基数(high cardinality)text字段时。一旦fielddata已加载到堆中,它将在该段的生命周期内保留。此外,加载fielddata是一个昂贵的过程,可能会导致用户遇到延迟命中。这就是默认情况下禁用fielddata的原因。

doc_values虽然速度稍慢,但doc_values的优势还是非常明显的。一个很显著的点就是他不会随着文档的增多引起OOM问题。正如前面说的,doc_values在磁盘创建排序和聚合所需的正排索引。这样我们就避免了在生产环境给ES设置一个很大的HEAP_SIZE,也使得JVM的GC更加高效,这个又为其它的操作带来了间接的好处。

1
2
3
4
5
6
7
1.当没有doc value的字段需要聚合时,需要打开fielddata,然后临时在内存中建立正排索引,fielddata的构建和管理发生在JVM heap中。

2.Fielddata默认是不启用的,因为text字段比较长,一般只做关键字分词和搜索,很少拿来进行全文匹配和聚合还有排序。

3.ES采用了circuit breaker(熔断)机制避免fielddata一次性超过物理内存大小而导致内存溢出,如果发生熔断,查询会被终止并返回异常。

4.fielddata使用的是jvm内存,doc value在内存不足时会静静的待在磁盘中,而当内存充足时,会蹦到内存里提升性能。

es

es

为什么不可以用倒排索引计算聚合?

对于聚合部分,我们需要找到匹配的doc里所有唯一的词项(term)。需要遍历每个doc获取所有trem词项,然后再一个个去倒排索引表中进行查找,是一个 n x m 的操作,做这件事情性能很低,很有可能会造成全表遍历。

因此通过正排索引来解决聚合问题

es

批量查询

1
2
3
语法:
GET /_mget
GET /<index>/_mget
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//批量查询 查询id =2 和id = 3 的数据
GET /_mget
{
"docs": [
{
"_index": "product",
"_id": 2
},
{
"_index": "product",
"_id": 3
}
]
}
1
2
3
4
5
6
7
8
9
10
11
12
//封装,把索引名(product提取出来)
GET /product/_mget
{
"docs": [
{
"_id": 2
},
{
"_id": 3
}
]
}
1
2
3
4
5
//再封装
GET /product/_mget
{
"ids":[2,3]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//include包含哪些字段  exclude排除哪些字段
GET /product/_mget
{
"docs": [
{
"_id": 2,
"_source": false //不显示字段数据
},
{
"_id": 3,
"_source": [ //指定字段数据
"name",
"price"
]
},
{
"_id": 4,
"_source": {
"include": [
"name"
],
"exclude":[
"price"
]
}
}
]
}
1
2
3
4
5
Operate:
create:PUT /index/_create/id/,强制创建(是否制定id)
delete:删除(lazy delete原理)
index:可以是创建,也可以是全量替换
update:执行partial update(全量替换,部分替换)
1
2
3
4
5
//手动指定id和自动生成
PUT /test_index/_doc/1/
{
"test":"123"
}
1
2
3
4
5
6
7
8
9
10
11
//强制执行创建 如果数据存在则报错
PUT /test_index/_doc/1/_create
{
"test":"123"
}

//同上
PUT /test_index/_create/1/
{
"test":"123"
}
1
2
3
4
5
//自动生产id(guid)
POST /test_index/_doc
{
"test":"123"
}

当使用PUT进行数据覆盖的时候,Version版本号会上升,旧的Version数据会被删除,不会马上删除,会有一个懒删除的机制。

批量操作

1
2
3
4
5
6
bulk:批量增删改  no-query
语法格式:
POST /_bulk
POST /<index>/_bulk
{"action": {"metadata"}} //操作和索引
{"data"} //数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /_bulk
{ "delete": { "_index": "product2", "_id": "1" }}

{ "create": { "_index": "product2", "_id": "2" }}
{ "name": "_bulk create 2" }

{ "create": { "_index": "product2", "_id": "12" }}
{ "name": "_bulk create 12" }

{ "index": { "_index": "product2", "_id": "3" }}
{ "name": "index product2 " }

{ "index": { "_index": "product2", "_id": "13" }}
{ "name": "index product2" }

//当出现冲突时尝试三次,三次失败后就放弃
{ "update": { "_index": "product2", "_id": "4","retry_on_conflict" : "3"} }
{ "doc" : {"test_field2" : "bulk test1"} }

bulk批处理操作要求数据分两行编写,不可以将{}进行换行操作。

1
2
3
4
//加?filter_path=items.*.error  只显示失败的,返回从操作失败的数据信息
POST /_bulk?filter_path=items.*.error
{ "delete": { "_index": "product2", "_id": "1" }}
{ "create": { "_index": "product2", "_id": "2" }}
1
2
3
4
5
6
7
//version=2&&version_type=external 通过版本更新数据,避免并发覆盖---CAS
PUT /version_index/_doc/1?version=2&&version_type=external
{
"title": "窈窕淑女,君子好逑"
}
//新版本使用者两个
//if_seq_no` and `if_primary_term`

ES是通过CAS+Version解决并发的问题!!!

最后更新: 2020年12月13日 20:39

原始链接: https://midkuro.gitee.io/2020/12/09/elasticearch-index/

× 请我吃糖~
打赏二维码