原英文版地址: https://www.elastic.co/guide/en/elasticsearch/reference/7.7/removal-of-types.html, 原文档版权归 www.elastic.co 所有
本地英文版地址: ../en/removal-of-types.html

删除的映射类型

在 Elasticsearch 7.0.0 或更高版本中创建的索引不再接受_default_映射。 在 6.x 中创建的索引将继续像以前一样在 Elasticsearch 6.x 中运行。 在7.0的API中 type 被废弃,对索引创建、设置映射、获取映射、设置模板、获取模板及获取字段映射API进行了重大更改。

映射类型是什么?

自从Elasticsearch第一次发布以来,每个文档都存储在一个索引中,并被分配一个映射类型。 映射类型用于表示被索引的文档或实体的类型,例如,一个名为twitter的索引可能有一个user类型和一个tweet类型。

每种映射类型都有自己的字段,因此用户类型可能有一个full_name字段、一个user_name字段和一个email字段;而tweet类型可能有一个content字段、一个tweeted_at字段,和用户类型一样,还有一个user_name字段。

每个文档都有一个包含类型名称的_type元字段,通过在URL中指定类型名称,可以将搜索限制到一个或多个类型:

GET twitter/user,tweet/_search
{
  "query": {
    "match": {
      "user_name": "kimchy"
    }
  }
}

字段_type与文档的_id组合起来生成字段_uid,因此具有相同_id的不同类型的文档可以存在于一个索引中。

映射类型还用于建立文档之间的父-子关系,因此question类型的文档可以是answer类型文档的父文档。

为什么要删除映射类型?

最初,我们谈到“索引(index)”类似于SQL数据库中的“数据库(database)”,而“类型(type)”相当于“表(tbale)”。

这是一个糟糕的类比,导致了错误的假设。 在SQL数据库中,表是相互独立的。 一个表中的列与另一个表中同名的列没有关系。 对于映射类型中的字段,情况却并非如此。

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

例如,当你希望在同一个索引中,字段deleted在一种类型下是date(类型的)字段,而在另一种类型下是boolean(类型的)字段时,这可能会导致失败。

最重要的是,在同一个索引中存储很少或没有共同字段的不同实体会导致数据稀疏,并干扰 Lucene 有效压缩文档的能力。

出于这些原因,我们决定从Elasticsearch中删除映射类型的概念。

映射类型的替代方法

一个文档类型一个索引

第一种方法是为每种文档类型建立一个索引。 可以将 tweets 存储在索引tweets中,将 users 存储在索引user中,而不是将 tweets 和 users 存储在同一个索引twitter中。 索引彼此完全独立,因此索引之间不会有字段类型冲突。

这种方法有两个有点:

  • 数据更可能是密集的,因此可以受益于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": "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字段来实现相同的目的,如下所示:

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字段。

没有映射类型的父/子

以前,父-子关系是通过将一个映射类型作为父类型,将一个或多个其他映射类型作为子类型来表示的。 没有类型,我们就不能再使用这种语法。 父子特性将继续像以前一样工作,只是表达文档之间关系的方式已经更改为使用新的join字段

映射类型的删除计划

对于我们的用户来说,这是一个很大的变化,所以我们尽量让它不那么痛苦。变更将按如下方式展开:

Elasticsearch 5.6.0
  • 在索引上设置index.mapping.single_type: true将启用每个索引一个类型的行为,这将在 6.0 中强制实施。
  • 父-子关系的join字段替换在 5.6 中创建的索引上可用。
Elasticsearch 6.x
  • 在 5.x 中创建的索引将像在 5.x 中一样继续在 6.x 中运行。
  • 在 6.x 中创建的索引只允许一个索引一个类型。 类型可以使用任何名称,但只能有一个。 首选的类型名称是_doc,这样索引API就有了与 7.0 中相同的路径:PUT {index}/_doc/{id}POST {index}/_doc
  • _type名称不能再与_id组合形成字段_uid。 字段_uid已成为字段_id的别名。
  • 新的索引不再支持旧式的父/子索引,而应该使用join字段代替。
  • _default_映射类型已废弃。
  • 在6.8中,索引创建、索引模板和映射API支持查询字符串参数(include_type_name),该参数指示请求和响应是否应包含类型名称。 它默认为true,如果准备升级到 7.0 则应该显式的设置一个值。 没有显式类型的索引将使用名为_doc的虚拟类型。
Elasticsearch 7.x
  • 在请求中指定类型已被废弃。 例如,为文档编制索引不再需要文档type。 新的编制索引API在显式指定 id 的情况下是PUT {index}/_doc/{id},在自动生成 id 的情况下POST {index}/_doc。 请注意,在 7.0 中,_doc是路径的永久部分,它表示终端名称,而不是文档类型。
  • 索引创建、索引模板和映射API中的参数include_type_name将默认为false。 设置该参数将导致一个废弃警告。
  • _default_映射类型已被删除。
Elasticsearch 8.x
  • 不再支持在请求中指定类型。
  • 参数include_type_name已被删除。

将多类型索引迁移到单一类型

重新索引(reindex) API可用于将多类型索引转换为单一类型索引。 下面这个示例可用于 Elasticsearch 5.6 或 Elasticsearch 6.x。 在 6.x 中,不需要指定index.mapping.single_type,因为这是默认值。

一个文档类型一个索引

第一个例子将索引twitter切分为索引tweetsusers

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",
    "type": "_doc"
  }
}

POST _reindex
{
  "source": {
    "index": "twitter",
    "type": "tweet"
  },
  "dest": {
    "index": "tweets",
    "type": "_doc"
  }
}

自定义 type 字段

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

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';
    """
  }
}

7.0 中的无类型API

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

即使目标索引包含自定义类型(type),无类型API也能工作。 例如,如果一个索引有一个自定义的类型my_type,我们可以使用无类型index调用向它添加文档,并使用无类型get调用加载文档。

索引 API

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

如果准备升级到 7.0, 应该在 6.8 版本的索引中显式设置。 为了避免在 6.8 中出现弃用警告,可以将该参数设置为truefalse。 在 7.0 中,设置include_type_name将导致弃用警告。

查看此选项设置为false时与Elasticsearch交互的一些示例:

PUT index_name?include_type_name=false
{
  "mappings": {
    "properties": { 
      "foo": {
        "type": "keyword"
      }
    }
  }
}

映射直接包含在mappings键下,没有类型名。

PUT index_name/_mappings?include_type_name=false
{
  "properties": { 
    "bar": {
      "type": "text"
    }
  }
}

映射直接包含在mappings键下,没有类型名。

GET index_name/_mappings?include_type_name=false

上面的调用返回:

{
  "index_name": {
    "mappings": {
      "properties": { 
        "foo": {
          "type": "keyword"
        },
        "bar": {
          "type": "text"
        }
      }
    }
  }
}

映射直接包含在mappings键下,没有类型名。

文档API

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

PUT index_name/_doc/1
{
  "foo": "baz"
}
{
  "_index": "index_name",
  "_id": "1",
  "_type": "_doc",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 0,
  "_primary_term": 1
}

类似的,getdelete API使用{index}/_doc/{id}路径:

GET index_name/_doc/1

在 7.0 中,_doc代表终端名称,而不是文档类型。 _doc组件是indexgetdelete API的永久路径部分,在 8.0 中不会被删除。

对于包含类型和终端名称(如_update)的API路径,在 7.0 中,终端将紧跟在索引名称之后:

POST index_name/_update/1
{
    "doc" : {
        "foo" : "qux"
    }
}

GET /index_name/_source/1

类型也不应该出现在请求体中。 下面这个批量(bulk)编制索引的示例省略了URL和各个批量命令中的类型:

POST _bulk
{ "index" : { "_index" : "index_name", "_id" : "3" } }
{ "foo" : "baz" }
{ "index" : { "_index" : "index_name", "_id" : "4" } }
{ "foo" : "qux" }

搜索 API

当调用搜索API(如_search_msearch_explain)时,URL中不应包含类型。 此外,不应在查询、聚合或脚本中使用_type字段。

响应中的类型

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

请注意,当使用已废弃的类型化API时,索引的映射类型将正常返回,但是无类型API将在响应中返回伪类型_doc。 例如,下面的无类型get调用将始终返回_doc作为类型,即使映射具有像my_type这样的自定义类型名称:

PUT index/my_type/1
{
  "foo": "baz"
}

GET index_name/_doc/1
{
    "_index" : "index_name",
    "_type" : "_doc",
    "_id" : "1",
    "_version" : 1,
    "_seq_no" : 0,
    "_primary_term" : 1,
    "found": true,
    "_source" : {
        "foo" : "baz"
    }
}

索引模板

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

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

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.8 和 7.0 节点组成的集群中,参数include_type_name应该在索引API(如索引创建)中指定。 这是因为该参数在 6.8 和 7.0 之间有不同的默认值,因此相同的映射定义对两个节点版本都无效。

无类型文档API(如bulkupdate)仅在 7.0 版本中可用,不适用于 6.8 版本的节点。 这也适用于执行文档查找的无类型版本的查询,例如terms查询。