文章524
标签277
分类67

ElasticSearch为什么在高版本移除映射类型

由于ElasticSearch官方文档使用的还是2.x版本, 而在使用其中的某些API时, 会出现deprecated提示. 本篇主要总结ElasticSearch中那些由于版本更新而不再推荐使用的API, 例如: 移除了映射类型(mapping types). 同时也作为正式学习ElasticSearch前的又一次预热.

本篇主要内容:

  • 什么是映射类型(mapping types)
  • 为什么要移除映射类型
  • 映射类型的可选替代方案: 每种文档类型一个索引/自定义类型字段
  • Elastic Search 各个版本对types的支持
  • 一些Elastic Search在新版本的使用技巧
  • ……

ElasticSearch为什么在高版本移除映射类型

官方英文解释在这:https://www.elastic.co/guide/en/elasticsearch/reference/current/removal-of-types.html

注意:在Elasticsearch6.0.0或者或者更新版本中创建的索引只会包含一个映射类型(mappingtype). 在5.x中创建的具有多个映射类型的索引在Elasticsearch6.x中依然会正常工作。在Elasticsearch7.0.0中,映射类型将会被完全移除。

一. 什么是映射类型?

从Elasticsearch的第一个发行版开始,每一个文档都会被存储在一个单独的索引中,并且配以一个单独的映射类型。一个映射类型被用来表示被索引的文档或者实体的类型,比如一个twitter索引可能会有一个user类型和一个tweet类型。

每一个映射类型都可以有其自身的字段,所以user类型可能有一个full_name字段,一个user_name字段和一个email字段,而tweet类型可能会包含一个content字段,一个tweeted_at字段,以及与user类型中类似的user_name字段。

每个文档都有一个_type元字段用来保存类型名,搜索可以通过在URL中指定类型名将搜索限定于一个或多个类型中:

1
2
3
4
5
6
7
8
GET twitter/user,tweet/_search
{
"query": {
"match": {
"user_name": "kimchy"
}
}
}_type字段的值会与文档的_id字段的值组合起来生成_uid字段,所以具有相同_id的不同类型的多个文档可以共存于一个索引中。

映射类型也被用来建立文档之间的父子关系,比如question类型的文档可以是answer类型文档的父亲。



二. 为什么要移除映射类型(mappingtypes)

在Elasticsearch 7.0.0或更高版本中创建的索引不再接受_default_映射,索引在6.x中创建将继续在Elasticsearch 6.x中运行,类型在api 7.0中是不受支持的,它会中断对索引创建、put映射、get映射、put模板、get模板和get字段映射API的更改。

开始的时候,我们说”索引(index)”类似于SQL数据库中的”数据库”,将”类型(type)”等同于”表”。

这是一个糟糕的类比,并且导致了一些错误的假设。在SQL数据库中,表之间是相互独立的。一个表中的各列并不会影响到其它表中的同名的列。而在映射类型(mappingtype)中却不是这样的。

在同一个Elasticsearch索引中,其中不同映射类型中的同名字段在内部是由同一个Lucene字段来支持的。换句话说,使用上面的例子,user类型中的user_name字段与tweet类型中的user_name字段是完全一样的,并且两个user_name字段在两个类型中必须具有相同的映射(定义).

这会在某些情况下导致一些混乱. 比如,在同一个索引中,当你想在其中的一个类型中将deleted字段作为date类型,而在另一个类型中将其作为boolean字段。

在此之上需要考虑一点,如果同一个索引中存储的各个实体如果只有很少或者根本没有同样的字段,这种情况会导致稀疏数据,并且会影响到Lucene的高效压缩数据的能力。

基于这些原因,将映射类型的概念从Elasticsearch中移除!



三. 映射类型的可选替代方案

1. 每种文档类型一个索引

第一种选择就是每个文档类型对应一个索引。你可以不将tweets和users存储于同一个索引,而将它们分别存储于tweets索引和users索引中。索引之间是完全相互独立的,不同索引中的(同名的)字段类型也就不会产生冲突了。

这种方式有两个好处:

  • 数据更倾向于密集(而不是稀疏),这样就能获益于Lucene的压缩技术;
  • 因为同一个索引中的所有的文档代表同一种实体,用于为全文搜索打分的条件统计会更精确

每个索引可以依据其可能的文档存储量级来设置相关的配置:可以对users使用较少的主分片,同时对tweets使用较大数量的主分片。


2. 自定义类型字段

当然,一个集群中可以创建的主分片的数量是有限制的,所以你可能不想为一个只有几千个文档的集合去浪费一整个分片。这种情况下你可以使用你自己定义的type字段,它看起来和原来的_type工作机制类似。

我们继续使用上面的user/tweet例子。原来的工作流程可能像下面这样:

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
29
30
31
32
33
34
35
36
37
38
39
PUT twitter
{
"mappings": {
"user": {
"properties": {
"name": { "type": "text" },
"user_name": { "type": "keyword" },
"email": { "type": "keyword" }
}
},
"tweet": {
"properties": {
"content": { "type": "text" },
"user_name": { "type": "keyword" },
"tweeted_at": { "type": "date" }
}
}
}
}
PUT twitter/user/kimchy
{
"name": "Shay Banon",
"user_name": "kimchy",
"email": "shay@kimchy.com"
}
PUT twitter/tweet/1
{
"user_name": "kimchy",
"tweeted_at": "2017-10-24T09:00:00Z",
"content": "Types are going away"
}
GET twitter/tweet/_search
{
"query": {
"match": {
"user_name": "kimchy"
}
}
}

你可以通过自定义的type字段实现同样的目的:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
PUT twitter
{
"mappings": {
"doc": {
"properties": {
"type": { "type": "keyword" },
"name": { "type": "text" },
"user_name": { "type": "keyword" },
"email": { "type": "keyword" },
"content": { "type": "text" },
"tweeted_at": { "type": "date" }
}
}
}
}
PUT twitter/doc/user-kimchy
{
"type": "user",
"name": "Shay Banon",
"user_name": "kimchy",
"email": "shay@kimchy.com"
}
PUT twitter/doc/tweet-1
{
"type": "tweet",
"user_name": "kimchy",
"tweeted_at": "2017-10-24T09:00:00Z",
"content": "Types are going away"
}
GET twitter/_search
{
"query": {
"bool": {
"must": {
"match": {
"user_name": "kimchy"
}
},
"filter": {
"match": {
"type": "tweet"
}
}
}
}
}

显式type字段替代了隐式_type字段。


3. 在没有映射类型的情况下实现父子关系

先前,我们通过将一个映射类型指定为父,另一个或多个映射类型为子来表示父子关系。在没有类型的情况下,我们就不能使用这种语法了。父子关系的特征会向之前那样工作,不同之处在于文档之间这种关系的表示方式变成了使用新的join字段。



四. 映射类型的移除计划

对于用户来说,这是一个巨大的变化,所以已经尝试让它尽可能地不那么痛苦,更改将按如下方式进行:

Elasticsearch 5.6.0

  • 在索引上设置index.mapping.single_type: true将启用在6.0中强制执行的单类型/索引行为。
  • 在5.6中创建的索引中可以使用父—子join字段替换。

Elasticsearch 6.x

  • 在5.x中创建索引将继续在6.x中运行就像5.x。
  • 索引在6.x中创建只允许每个索引使用单一类型,类型可以使用任何名称,但只能有一个,首选的类型名称是_doc,因此索引API具有与7.0中相同的路径:PUT {index}/_doc/{id} and POST {index}/_doc。
  • _type名称不能再与_id组合以形成_uid字段,_uid字段已成为_id字段的别名。
  • 新的索引不再支持旧式的父/子索引,而是应该使用join字段。
  • _default_映射类型已弃用。
  • 在6.8中,索引创建、索引模板和映射API支持查询字符串参数(include_type_name),该参数指示请求和响应是否应该包含类型名称。它默认为true,应该设置为一个显式值,以便准备升级到7.0,未设置include_type_name将导致一个弃用警告,没有显式类型的索引将使用虚拟类型名称_doc。

Elasticsearch 7.x

  • 在请求中指定类型已弃用,例如,索引文档不再需要文档type,对于显式id,新的索引API是PUT {index}/_doc/{id},对于自动生成的id则是POST {index}/_doc,注意,在7.0中,_doc是路径的一个永久部分,它表示端点名称,而不是文档类型。
  • 索引创建、索引模板和映射API中的include_type_name参数默认为false,完全设置该参数将导致一个弃用警告。
  • 删除_default_映射类型。

Elasticsearch 8.x

  • 不再支持在请求中指定类型。
  • 删除include_type_name参数。


五. 一些Elastic Search的使用技巧

1. 将多类型索引迁移到单类型

Reindex API可用于将多类型索引转换为单类型索引,下面的例子可以在Elasticsearch 5.6或Elasticsearch 6.x中使用,在6.x,不需要指定index.mapping.single_type作为默认值

每种文档类型的索引

第一个示例将twitter索引拆分为tweets索引和users索引:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
PUT users
{
"settings": {
"index.mapping.single_type": true
},
"mappings": {
"_doc": {
"properties": {
"name": {
"type": "text"
},
"user_name": {
"type": "keyword"
},
"email": {
"type": "keyword"
}
}
}
}
}

PUT tweets
{
"settings": {
"index.mapping.single_type": true
},
"mappings": {
"_doc": {
"properties": {
"content": {
"type": "text"
},
"user_name": {
"type": "keyword"
},
"tweeted_at": {
"type": "date"
}
}
}
}
}

POST _reindex
{
"source": {
"index": "twitter",
"type": "user"
},
"dest": {
"index": "users"
}
}

POST _reindex
{
"source": {
"index": "twitter",
"type": "tweet"
},
"dest": {
"index": "tweets"
}
}
自定义类型字段

下一个示例添加一个自定义类型字段,并将其设置为原始_type的值,它还将类型添加到_id中,以防有任何不同类型的文档具有冲突的id:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
PUT new_twitter
{
"mappings": {
"_doc": {
"properties": {
"type": {
"type": "keyword"
},
"name": {
"type": "text"
},
"user_name": {
"type": "keyword"
},
"email": {
"type": "keyword"
},
"content": {
"type": "text"
},
"tweeted_at": {
"type": "date"
}
}
}
}
}


POST _reindex
{
"source": {
"index": "twitter"
},
"dest": {
"index": "new_twitter"
},
"script": {
"source": """
ctx._source.type = ctx._type;
ctx._id = ctx._type + '-' + ctx._id;
ctx._type = '_doc';
"""
}
}

2. 新版本中的无类型API

在Elasticsearch 7.0中,每个API都支持无类型请求,指定类型将产生一个弃用警告。

即使目标索引包含自定义类型,无类型API也可以工作,例如,如果索引具有自定义类型名称my_type,则可以使用无类型index调用向其添加文档,并使用无类型get调用加载文档。

索引API

索引创建、索引模板和映射API支持一个新的include_type_name URL参数,该参数指定请求和响应中的映射定义是否应该包含类型名称. 版本6.8中的参数默认为true,以匹配在映射中使用类型名称的7.0之前的行为,它在7.0版本中默认为false,将在8.0版本中删除。

它应该在6.8中明确设置,以便准备升级到7.0,为了避免6.8中的弃用警告,可以将参数设置为true或false,在7.0中,设置include_type_name将导致一个弃用警告。

查看一些与Elasticsearch交互的例子,这个选项设置为false:

1
2
3
4
5
6
7
8
9
10
PUT index?include_type_name=false
{
"mappings": {
"properties": {
"foo": {
"type": "keyword"
}
}
}
}
  • 映射直接包含在mappings键下,没有类型名称。
1
2
3
4
5
6
7
8
PUT index/_mappings?include_type_name=false
{
"properties": {
"bar": {
"type": "text"
}
}
}
1
GET index/_mappings?include_type_name=false

上面的调用返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"index": {
"mappings": {
"properties": {
"foo": {
"type": "keyword"
},
"bar": {
"type": "text"
}
}
}
}
}

3. 文档API

在7.0中,必须使用{index}/_doc路径调用索引API,以便自动生成_id,使用显式id调用{index}/_doc/{id}。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PUT index/_doc/1
{
"foo": "baz"
}
{
"_index": "index",
"_id": "1",
"_type": "_doc",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}

类似地,get和delete API使用路径{index}/_doc/{id}:

1
GET index/_doc/1

在7.0中,_doc表示端点名称,而不是文档类型,_doc组件是文档index、get和delete API路径的永久部分,在8.0中不会被删除。

对于同时包含类型和端点名(如_update)的API路径,在7.0中端点将立即跟随索引名:

1
2
3
4
5
6
7
8
POST index/_update/1
{
"doc" : {
"foo" : "qux"
}
}

GET /index/_source/1

类型也不应该再出现在请求体中,下面的bulk索引示例省略了URL和单个批量命令中的类型:

1
2
3
4
5
POST _bulk
{ "index" : { "_index" : "index", "_id" : "3" } }
{ "foo" : "baz" }
{ "index" : { "_index" : "index", "_id" : "4" } }
{ "foo" : "qux" }

4. 搜索API

在调用诸如_search、_msearch或_explain之类的搜索API时,URL中不应该包含类型,此外,_type字段不应该用于查询、聚合或脚本!

文档和搜索API将继续在响应中返回_type键,以避免中断响应解析,然而,键被认为是不赞成的,不应该再被引用,类型将在8.0中从响应中完全删除。

注意,当使用废弃的类型化API时,索引的映射类型将作为正常返回,但是无类型API将在响应中返回虚拟类型_doc,例如,下面的无类型get调用总是返回_doc作为类型,即使映射有一个像my_type这样的自定义类型名:

1
2
3
4
5
6
PUT index/my_type/1
{
"foo": "baz"
}

GET index/_doc/1

​

1
2
3
4
5
6
7
8
9
10
11
12
{
"_index" : "index",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found": true,
"_source" : {
"foo" : "baz"
}
}

5. 索引模版

建议通过将include_type_name设置为false来重新添加索引模板,使其无类型,在底层,无类型模板在创建索引时将使用虚拟类型_doc!

如果将无类型模板用于类型化索引创建调用,或者将类型化模板用于无类型索引创建调用,则仍将应用模板,但索引创建调用将决定是否应该有类型。例如在下面的示例中,index-1-01将具有一个类型,尽管它匹配一个没有类型的模板,而index-2-01将具有无类型,尽管它匹配一个定义了类型的模板,index-1-01和index-2-01都将从匹配的模板中继承foo字段。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
PUT _template/template1
{
"index_patterns":[ "index-1-*" ],
"mappings": {
"properties": {
"foo": {
"type": "keyword"
}
}
}
}

PUT _template/template2?include_type_name=true
{
"index_patterns":[ "index-2-*" ],
"mappings": {
"type": {
"properties": {
"foo": {
"type": "keyword"
}
}
}
}
}

PUT index-1-01?include_type_name=true
{
"mappings": {
"type": {
"properties": {
"bar": {
"type": "long"
}
}
}
}
}

PUT index-2-01
{
"mappings": {
"properties": {
"bar": {
"type": "long"
}
}
}
}

在隐式索引创建的情况下,因为文档在索引中被索引,而索引还不存在,所以总是使用模板,这通常不是一个问题,因为无类型索引调用要处理有类型的索引。


6. 混合版本的集群

在由6.8和7.0节点组成的集群中,应该在索引创建之类的索引API中指定参数include_type_name,这是因为参数在6.8和7.0之间有不同的默认值,所以相同的映射定义对两个节点版本都无效!

诸如bulk和update之类的无类型文档API仅在7.0版本时可用,不能用于6.8节点,对于执行文档查找的查询的无类型版本,如terms,也是如此。



附录

参考文章


本文作者:Jasonkay
本文链接:https://jasonkayzk.github.io/cn/2019/10/03/ElasticSearch%E4%B8%BA%E4%BB%80%E4%B9%88%E5%9C%A8%E9%AB%98%E7%89%88%E6%9C%AC%E7%A7%BB%E9%99%A4%E6%98%A0%E5%B0%84%E7%B1%BB%E5%9E%8B/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可