考古|_parent 在 ES 2.0 被重写,老索引不重建就用不上新实现

简介: 考古|_parent 在 ES 2.0 被重写,老索引不重建就用不上新实现

2.0 把 parent/child 的实现整个重写,_parent 默认走 doc values,但只对 2.0 及以后创建的索引生效,所以用 _parent 的老索引升级后功能照常、改进拿不到,要 reindex 才行。同一份迁移文档里还有两条:父类型不能先于子类型存在,top_children 查询被删。本文对照 v2.4.6 源码把这三条的触发条件核了一遍。

说明

来源和边界先交代:本文的依据出自 v2.4.6 标签下的官方文件(parent_child.asciidoc、ParentFieldMapper.java、Version.java),文中引用的原文均来自这些文件。我没有启动集群、也没有跑测试,示例取自官方示例;哪些是文档写明、哪些是我的推断,正文里逐处标注。

01-parent_child

图 1:官方文件 parent_child.asciidoc(v2.4.6 标签)。来源:GitHub elastic/elasticsearch v2.4.6 parent_child.asciidoc

先看官方原文

2.0 的迁移文档(parent_child.asciidoc 第 4–10 行)开头是这两段:

Parent/child has been rewritten completely to reduce memory usage and to execute has_child and has_parent queries faster and more efficient. The _parent field uses doc values by default. The refactored and improved implementation is only active for indices created on or after version 2.0.

In order to benefit from all the performance and memory improvements, we recommend reindexing all existing indices that use the _parent field.

第三句是重点:新实现只对 2.0 及以后创建的索引生效。集群从 1.x 升到 2.x,老索引的文件不变,父子关系还是走老路径,功能正常,内存和查询上的改进拿不到。要拿到,只能 reindex。文档没有给出内存或性能的具体数字,本文也不编数字。

02-MetaDataMappingServiceTests

图 2:官方文件 MetaDataMappingServiceTests.java(v2.4.6 标签)。来源:GitHub elastic/elasticsearch v2.4.6 MetaDataMappingServiceTests.java

这条迁移的风险在于升级后没有任何信号

通常的 breaking change 会报错或行为明显变化,读者马上知道要处理。这一条升级后父子查询照常返回结果,没有报错,也没有日志提示,容易被当成已经升级完成。后面发现父子查询还是慢、内存还是高,才会回头看到文档里那句 "we recommend reindexing"。

我的建议是升级 2.0 时就把这类索引的 reindex 排进计划。原因有两个:这些改进只在重建后才生效,不重建等于没有升级这部分;数据量只会增长,重建的窗口越往后越难找。

reindex 的成本也值得先掂量。父子关系解决的是"一对多、子文档需要独立更新",我的判断是:如果子文档很少变动,用 nested 或把子对象放进父文档,查询和维护成本往往更低。这是建模上的建议,本文没有做基准测试。

03-ParentFieldMapper

图 3:官方文件 ParentFieldMapper.java(v2.4.6 标签)。来源:GitHub elastic/elasticsearch v2.4.6 ParentFieldMapper.java

"按创建版本生效"在源码里是什么样

迁移文档的说法可以在 v2.4.6 源码里对上。ParentFieldMapper 第 126–131 行在构建 _parent 的 mapper 时,对创建版本早于 Version.V_2_0_0_beta1 的索引,把 parent/child 两个 join 字段的 doc values 关掉:

if (context.indexCreatedVersion().before(Version.V_2_0_0_beta1)) {
   
    childJoinFieldType.setHasDocValues(false);
    childJoinFieldType.setDocValuesType(DocValuesType.NONE);
    parentJoinFieldType.setHasDocValues(false);
    parentJoinFieldType.setDocValuesType(DocValuesType.NONE);
}

第 281–284 行的 joinFieldTypeForParentType 对同样的条件做了同样的处理。所以"doc values 默认开启"只对 2.0 及以后创建的索引成立,老索引在同一份代码里被显式关掉。Version.java 里 1.7.0 的版本号是 1070099,2.0.0-beta1 是 2000001(Version.java 第 249、264 行),可以用来判断 index.version.created 属于哪一边。

父类型不能先于子类型存在

2.0 里,一个类型通过 _parent 元字段声明自己是另一个类型的子类型(迁移文档 第 12 行起的 "Parent type cannot pre-exist"):

PUT my_index
{
   
  "mappings": {
   
    "my_parent": {
   },
    "my_child": {
   
      "_parent": {
   
        "type": "my_parent"
      }
    }
  }
}

文档对顺序的限定是:

The mapping for the parent type can be added at the same time as the mapping for the child type, but cannot be added before the child type.

_parent 元字段文档的限制一节说得更直接(parent-field.asciidoc 第 109–111 行):_parent.type 只能指向一个还不存在的类型,一个类型在创建之后不能再变成父类型。

源码里这条校验在 MetaDataMappingService 第 269–277 行:对 2.0 及以后创建的索引,往已有索引里新增一个带 _parent 的类型时,如果它指向的类型已经存在、而且还不是任何类型的父类型,就抛 IllegalArgumentException,消息是:

can't add a _parent field that points to an already existing type, that isn't already a parent

测试里有对应的两个用例(MetaDataMappingServiceTests):先建 type,再给 child 加指向 type 的 _parent,会失败;先建 parent 和 child1,再加指向 parent 的 child2,因为 parent 已经是父类型,所以允许。

对建索引脚本的影响:如果脚本是先建好父类型的 mapping,再用 put mapping 去加子类型,会触发这个错误;在创建索引的同一个请求里同时给出父类型和子类型的 mapping,文档说明是可以的。这个限制只对 2.0 及以后创建的索引生效,条件里的 Version.indexCreated(...).onOrAfter(Version.V_2_0_0_beta1) 就是判断依据。

top_children 查询被删了

迁移文档(第 38–43 行)的原文:

The top_children query has been removed in favour of the has_child query. It wasn't always faster than the has_child query and the results were usually inaccurate. The total hits and any aggregations in the same search request would be incorrect if top_children was used.

官方给了三条理由:

  1. 并不总是比 has_child 快;
  2. 结果通常不准确;
  3. 同一个请求里的 total hits 和聚合会算错。

第三条的影响最大:不只是返回的文档不准,连总数和聚合都跟着错。用了 top_children 的查询在 2.0 里没有"先用着"的余地,要换成 has_child。这次删除没有保留兼容期,从理由看是合理的:结果错误的查询,保留比删除代价更高。

2.0 前后对照

项 1.x 2.0
_parent 的 doc values 源码里对老索引关闭 新建索引默认开启
新实现的生效范围 无 仅 2.0 及以后创建的索引
老索引 沿用旧路径 要 reindex 才能受益
父子相关查询 has_child、has_parent、top_children has_child、has_parent
父类型的定义顺序 无此限制 不能先于子类型存在

怎么确认索引用的是不是新实现

文档没有给判断方法,但既然新实现只看创建版本,可以直接看索引设置里的 index.version.created:

curl -s 'localhost:9200/_cat/indices?v'
curl -s 'localhost:9200/my_index/_settings?pretty' | grep version

输出里的 created 是版本号的数字形式。按上面 Version.java 的编码,2.4.6 是 2040699,1.x 创建的索引是以 1 开头的七位数,比如 1070099。创建于 2000001(2.0.0-beta1)之前的索引,父子关系是老实现,要重建。

reindex 怎么做

下面的流程把老索引的数据搬到一个新建的 2.0+ 索引。reindex 在 v2.4.6 里是 experimental,reindex 文档第 4 行标明 API 可能以不兼容的方式变化;文档开头的 IMPORTANT(第 6 行起)还写明 reindex 不会帮你建目标索引,mapping、分片数等要先自己配好。

# 1) 建新索引,父类型和子类型在同一个请求里给出
curl -XPUT 'localhost:9200/my_index_v2' -d '{
  "mappings": {
    "my_parent": {},
    "my_child": { "_parent": { "type": "my_parent" } }
  }
}'

# 2) 搬运数据
curl -XPOST 'localhost:9200/_reindex' -d '{
  "source": { "index": "my_index" },
  "dest":   { "index": "my_index_v2" }
}'

# 3) 核对数量,再切别名
curl -s 'localhost:9200/my_index/_count'
curl -s 'localhost:9200/my_index_v2/_count'

_parent 的值是从哪里带过去的

这里要纠正一种常见的担心:是不是要先把 parent id 存成 _source 里的普通字段,reindex 才带得过去。读 v2.4.6 的源码,不需要。

所以按代码读,_parent 和 _routing 会被 reindex 原样带到目标索引,不依赖 _source 里有没有这个字段。这是读源码的推断,没有跑过,建议先拿一组小数据验证:reindex 之后对目标索引跑 has_child,能返回结果就说明关系带过去了。官方 reindex 文档的脚本章节(第 263 行)也把 _parent 列在脚本能修改的元数据里。

路由的约束

_parent 文档里还有一条限制:父子文档必须在同一个分片,parent ID 同时作为子文档的路由值,所以对子文档做 GET、DELETE、UPDATE 时都要带同样的 parent 值。reindex 之后写入端和读取端对 parent 的传法不能变,这是改完索引之后要核对的客户端行为。

reindex 前的准备

父子文档之间有依赖关系,动手前建议确认这几件事:

  1. 规模:父文档数、子文档数、平均每个父下挂多少个子,用来估时间和磁盘;
  2. reindex 期间的写入:如果业务还在写,期间新增和修改的文档不会自动出现在新索引里。常见的做法是新老索引双写一段时间,reindex 历史数据,切换查询后再停掉对旧索引的写入,代价是写入逻辑要改;
  3. 别名:用别名指向索引,切换时改别名指向,业务代码不用改,前提是所有读写都走别名,直接写索引名的代码会漏;
  4. 试跑:拿一个小的父子索引完整跑一遍(reindex、校验、切换),再动大的。试跑能暴露 mapping 顺序、查询改写这类问题。

重建之后怎么验证父子关系

reindex 跑完不等于完成。建议验三样:

  1. 计数:父文档数、子文档数和原索引一致,能发现漏文档;
  2. 抽样验证关系:挑几个父文档,用 has_child 查它们下面的子文档,数量要对:
curl -s -XPOST 'localhost:9200/my_index_v2/_search' -d '{
  "query": {
    "has_child": {
      "type": "my_child",
      "query": { "match_all": {} }
    }
  }
}'

反向用 has_parent 也验一次;

  1. 对比关键查询:选几个业务上真实的父子查询,在新旧索引上各跑一次,对比返回的文档 id 集合。计数和抽样都通过,也可能出现个别子文档挂错父的局部错误,只有这一步能发现。

什么时候别用父子关系

适合的场景:一对多,且子文档需要频繁独立增删改。父子的价值在于更新子文档时不用重写整个父文档。

不太适合的场景:

  • 一对多但子文档很少变动:用 nested 或直接把子对象放进父文档,查询和维护都简单;
  • 想用它做 JOIN 出报表:ES 不是为这个设计的,放在应用层或数仓里更合适;

2.0 的重写之后,内存和查询效率相对 1.x 有改进,但 _parent 文档里列出的限制还在:父子要在同一个分片,父类型不能事后追加,对子文档做 GET、DELETE、UPDATE 要带 parent。这是选型时的取舍。

写在最后

这篇涉及三条改动:新实现只对 2.0 及以后创建的索引生效,老索引要 reindex;父类型不能先于子类型存在,put mapping 顺序写反会报错;top_children 被删,要换成 has_child。

前两条都是不报错或要到特定操作时才暴露的类型:老索引升级后功能正常、改进没拿到,建索引脚本的顺序只在用 put mapping 追加子类型时才出问题。第三条会让旧的查询请求直接失败,至少能被发现。

对"索引级"的改进,升级后要主动确认生效,不能假设升级集群版本就受益。确认的办法是看索引的元数据,这里是 index.version.created,而不是集群版本号。大版本升级后,挑几个有代表性的索引把设置打出来对一遍,这个清单不长,能覆盖"升级了但没真正生效"的情况。

参考

本文引用的原文与示例均来自本地 elasticsearch 仓库 v2.4.6 标签下的迁移文档;未给出内存或性能的具体数字,因为原文没有提供。

本文对照 Elasticsearch v2.4.6 标签的官方文档核对,引文逐字取自原文。撰写时未启动集群、未运行测试。

目录
相关文章
|
云安全 安全 网络安全
|
15天前
|
JSON 程序员 数据格式
最新版Notepad++下载、安装、配置保姆级教程(附官网安装包)
Notepad++ 是一款免费开源、轻量快速的 Windows 文本编辑器,支持多标签、语法高亮、代码折叠与宏录制,体积小、启动快、资源占用低,兼容 x64/x32/ARM 架构,是程序员和文字工作者的高效利器。
3568 0
|
8天前
|
JavaScript 安全 前端开发
Nodejs Brotli decoder 代码执行漏洞 (CVE-2026-111497)10629
Node.js发布安全更新修复Brotli解码器高危RCE漏洞(CVE-2026-111497),建议受影响版本用户立即升级。
2042 2
|
14天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
2431 45
|
11天前
|
Web App开发 人工智能 前端开发
2026 年 AI 浏览器怎么选?Ego Lite、Tabbit、Chrome DevTools MCP 深度对比,开发者到底该用哪个?
2026年AI浏览器选型指南:Ego Lite专注多Agent协同(Mac优先),Tabbit国产首选,原生支持Windows与Codex控制;Chrome DevTools MCP非浏览器而是开发者利器,深度集成Console/Network/Performance调试能力,让AI真正“看懂并验证”前端代码。
1467 3
|
8天前
|
开发者 索引
DeepSeekHarness大肥鱼自带插件市场了?加个地址就能用
Open Plugin Market 是一个开放插件索引平台,收录800+优质DSH插件,支持分类浏览、智能推荐与随机发现。无需安装市场,粘贴索引地址即可一键接入,网页版直访 dshopen.org,助力小开发者被看见。(239字)
1903 1
DeepSeekHarness大肥鱼自带插件市场了?加个地址就能用
|
7天前
|
人工智能 Linux API
Magpie, 轻量本地AI网关!免费Token用不完!
Magpie 是开源AI编程模型调度网关,上线一月获1.8万Star。它通过本地高性能网关统一托管多平台API密钥、账号与免费额度,实现跨Agent的模型秒级切换、静默额度轮换与资源复用,显著降低碎片化管理成本。(239字)
Magpie, 轻量本地AI网关!免费Token用不完!
|
8天前
|
安全 应用服务中间件 Go
PHP2Shell 漏洞拆解:WordPress 与 nginx 为何中招
PHP2Shell 双 RCE 已遭在野利用:WordPress、Drupal 全在射程内,nginx 其实另有洞。拆成因与修复顺序
PHP2Shell 漏洞拆解:WordPress 与 nginx 为何中招
|
4天前
|
人工智能 安全 API
2026 年阿里云双 11|百炼大模型 API 指南:免费 Token 领取、API Key 生成、代码调用实战教程
对于零基础的AI开发者、技术爱好者和小型开发团队来说,低成本、低门槛调用主流大模型接口,是开启AI项目原型开发的必经之路。百炼大模型服务平台汇集了百余款主流大模型,面向新用户提供大规模免费推理额度,不需要预先充值,即可完成文本生成、逻辑推理、代码编写、多模态解析等各类开发验证工作。很多新手在初次接触平台时,常常遇到不清楚免费额度规则、不知道如何生成API‑Key、环境配置错误、调用接口持续报错等一系列问题。
585 1