原文: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中指定类型名将搜索限定于一个或多个类型中:
GET twitter/user,tweet/_search { "query": { "match": { "user_name": "kimchy" } } }
_type字段的值会与文档的_id字段的值组合起来生成_uid字段,所以具有相同_id的不同类型的多个文档可以共存于一个索引中。
映射类型也被用来建立文档之间的父子关系,比如question类型的文档可以是answer类型文档的父亲。
为什么要移除映射类型(mappingtypes)
开始的时候,我们说“索引(index)”类似于SQL数据库中的“数据库”,将“类型(type)”等同于“表”。
这是一个糟糕的类比,并且导致了一些错误的假设。在SQL数据库中,表之间是相互独立的。一个表中的各列并不会影响到其它表中的同名的列。而在映射类型(mappingtype)中却不是这样的。
在同一个Elasticsearch索引中,其中不同映射类型中的同名字段在内部是由同一个Lucene字段来支持的。换句话说,使用上面的例子,user类型中的user_name字段与tweet类型中的user_name字段是完全一样的,并且两个user_name字段在两个类型中必须具有相同的映射(定义)。
这会在某些情况下导致一些混乱,比如,在同一个索引中,当你想在其中的一个类型中将deleted字段作为date类型,而在另一个类型中将其作为boolean字段。
在此之上需要考虑一点,如果同一个索引中存储的各个实体如果只有很少或者根本没有同样的字段,这种情况会导致稀疏数据,并且会影响到Lucene的高效压缩数据的能力。
基于这些原因,将映射类型的概念从Elasticsearch中移除。
映射类型的可选替代方案
每种文档类型一个索引
第一种选择就是每个文档类型对应一个索引。你可以不将tweets和users存储于同一个索引,而将它们分别存储于tweets索引和users索引中。索引之间是完全相互独立的,不同索引中的(同名的)字段类型也就不会产生冲突了。
这种方式有两个好处:
数据更倾向于密集(而不是稀疏),这样就能获益于Lucene的压缩技术。
因为同一个索引中的所有的文档代表同一种实体,用于为全文搜索打分的条件统计会更精确。
每个索引可以依据其可能的文档存储量级来设置相关的配置:可以对users使用较少的主分片,同时对tweets使用较大数量的主分片。
自定义类型字段
当然,一个集群中可以创建的主分片的数量是有限制的,所以你可能不想为一个只有几千个文档的集合去浪费一整个分片。这种情况下你可以使用你自己定义的type字段,它看起来和原来的_type工作机制类似。
我们继续使用上面的user/tweet例子。原来的工作流程可能像下面这样:
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": "[email protected]" } 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字段实现同样的目的:
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": "[email protected]" } 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" } } } } }
在没有映射类型的情况下实现父子关系
先前,我们通过将一个映射类型指定为父,另一个或多个映射类型为子来表示父子关系。在没有类型的情况下,我们就不能使用这种语法了。父子关系的特征会向之前那样工作,不同之处在于文档之间这种关系的表示方式变成了使用新的join字段。
映射类型的移除计划
对于用户来说这是一个大的变化,所以我们尽可能将这个过程变得平滑。这次变更的计划如下:
Elasticsearch 5.6.0
在一个索引上设置index.mapping.single_type:true会启用单索引-单一类型的行为,而这种行为在6.0中是强制的。
在5.6中创建的索引可以使用创建文档间父子关系的新的join字段。
Elasticsearch 6.x
5.x中创建的索引可以在6.x中正常工作
6.x中创建的索引只允许单个索引中存在单一的类型。任意的名字都可以作为类型,但是只能有一个。
_type名不再与_id组合生成_uid字段。_uid字段变成仅仅是_id字段的别名。
新创建的索引不再支持旧风格的父子关系,而应该使用join字段。
_default_映射类型被标记为不推荐使用。
Elasticsearch 7.x
URL中的type参数为可选。比如,索引一个文档不再要求提供文档类型。
GET|PUT_mapping API支持一个查询字符串参数(include_type_name),通过这个参数来指定请求体是否应该包含一个类型名的层。默认是true。7.x中没有显式指定类型的索引将会使用默认的_doc类型名。
_default_映射类型移除。
Elasticsearch 8.x
不再支持URL中的type参数。
include_type_name参数默认为false。
Elasticsearch 9.x
include_type_name参数移除。
将索引从多类型迁移到单类型
可以用ReindexAPI将多类型索引转化成单类型索引。下面的例子可以在Elasticsearch 5.6或者Elasticsearch6.x中使用。在6.x中,不必指定index.mapping.single_type,因为6.x中是默认开启的。
每种文档类型转化成一个索引
这个例子将我们的twitter索引分成了tweets索引和users索引:
PUT users { "settings": { "index.mapping.single_type": true }, "mappings": { "user": { "properties": { "name": { "type": "text" }, "user_name": { "type": "keyword" }, "email": { "type": "keyword" } } } } } PUT tweets { "settings": { "index.mapping.single_type": true }, "mappings": { "tweet": { "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字段,并将其设置为原来_type的值。同时将类型附加于_id,以防止不同类型的文档具有冲突IDs的情况:
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'; """ } }