7.2 父子文档 (Parent-Child Relationship)


文档摘要

7.2 父子文档 (Parent-Child Relationship 逐渐被 Join 数据类型替代) Elasticsearch 高级特性:从父子文档到 Join 数据类型的演进 父子文档 (Parent-Child Relationship):历史与局限 在 Elasticsearch 早期版本中,父子文档关系是实现文档之间一对多关系的主要方式。它允许你将某些文档指定为“父文档”,而其他文档则作为其“子文档”。这种关系常用于表示诸如博客文章和评论、订单和订单项等场景。 1.1 父子文档的工作原理 父子文档关系的核心在于在索引文档时指定 字段。当索引一个子文档时,你需要指定其父文档的 ID。

7.2 父子文档 (Parent-Child Relationship 逐渐被 Join 数据类型替代)

Elasticsearch 高级特性:从父子文档到 Join 数据类型的演进

1. 父子文档 (Parent-Child Relationship):历史与局限

在 Elasticsearch 早期版本中,父子文档关系是实现文档之间一对多关系的主要方式。它允许你将某些文档指定为“父文档”,而其他文档则作为其“子文档”。这种关系常用于表示诸如博客文章和评论、订单和订单项等场景。

1.1 父子文档的工作原理

父子文档关系的核心在于在索引文档时指定 _parent 字段。当索引一个子文档时,你需要指定其父文档的 ID。Elasticsearch 会确保父文档和子文档被路由到相同的分片 (shard),这对于保证查询性能至关重要。

映射 (Mapping) 定义

在定义映射时,你需要为子文档类型指定 _parent 字段,并指定父文档的类型。例如,假设我们有两个类型: blog_post (父类型) 和 comment (子类型)。

PUT /my_index { "mappings": { "properties": { "comment": { "type": "join", // 在 7.x 版本中,即使是父子关系,底层也开始使用 join 类型,但早期版本使用 _parent 字段 "relations": { "blog_post": "comment" } }, "blog_post_title": { "type": "text" }, "comment_text": { "type": "text" } } } }

索引文档

索引父文档 blog_post:

POST /my_index/_doc/blog_post_1?routing=blog_post_1 // 为了演示 routing,显式指定 routing key { "blog_post_title": "Elasticsearch 父子文档详解" }

索引子文档 comment,并指定 parent 字段:

POST /my_index/_doc/comment_1?routing=blog_post_1&parent=blog_post_1 // routing 必须与父文档一致,并指定 parent { "comment_text": "这篇文章写得真好!", "comment": "comment" // 使用 join 字段,并标记为 "comment" 关系 }

注意: 在早期的父子文档实现中,你可能需要使用 _parent 字段和 parent 参数,但实际上在 7.x 版本,即使是父子文档的概念,底层也开始使用 join 数据类型来管理关系。为了更清晰地理解演进过程,我们这里先用 join 类型模拟早期的父子文档行为。

查询父子文档

要查询与特定父文档关联的子文档,可以使用 has_child 查询。

GET /my_index/_search { "query": { "has_child": { "type": "comment", "query": { "match": { "comment_text": "真好" } }, "inner_hits": {} // 获取子文档的具体内容 } } }

反之,要查询与特定子文档关联的父文档,可以使用 has_parent 查询。

GET /my_index/_search { "query": { "has_parent": { "parent_type": "blog_post", "query": { "match": { "blog_post_title": "Elasticsearch 父子文档详解" } }, "inner_hits": {} // 获取父文档的具体内容 } } }

mermaid 图示:父子文档关系

1.2 父子文档的局限性

尽管父子文档关系在早期 Elasticsearch 版本中提供了一种处理文档关系的方法,但它存在一些固有的局限性,导致其逐渐被弃用:

  • 性能瓶颈: 父子文档必须路由到相同的分片,这限制了数据分布的灵活性,并可能导致热点分片问题,尤其是在父文档拥有大量子文档时。查询性能也可能受到影响,因为需要跨分片进行协调。

  • 复杂性: 父子关系的实现和管理相对复杂,包括路由配置、查询语法的特殊性等。

  • 功能限制: 父子关系只支持一对多关系,对于更复杂的关系类型(例如多对多,或更细粒度的关系控制)无能为力。

  • 维护成本: 随着 Elasticsearch 的发展,维护和优化父子文档关系变得越来越困难,社区也逐渐倾向于寻找更通用的解决方案。

正是由于这些局限性,Elasticsearch 团队开始着手寻找更灵活、更高效的方式来处理文档之间的关系,最终 Join 数据类型 应运而生,并逐渐取代了父子文档关系。

2. Join 数据类型:更灵活、更强大的关系处理方案

Elasticsearch 版本引入了 Join 数据类型,旨在提供更强大、更灵活的方式来处理文档之间的关系,它不仅解决了父子文档的诸多局限性,还提供了更广泛的应用场景。

2.1 Join 数据类型的工作原理

Join 数据类型允许你在单个索引中定义多种类型的关系,例如父子关系、兄弟关系、甚至更复杂的关系。它通过在文档中添加一个特殊的 join 字段来标记文档在关系中所扮演的角色,并定义不同关系类型之间的连接。

映射 (Mapping) 定义

使用 Join 数据类型,你需要在映射中定义 join 字段,并指定 relations 属性来描述索引中存在的不同关系类型。

PUT /my_index_join { "mappings": { "properties": { "my_join_field": { "type": "join", "relations": { "question": "answer", // 定义 question -> answer 的父子关系 "blog_post": "comment", // 定义 blog_post -> comment 的父子关系 "department": "employee" // 定义 department -> employee 的父子关系 } }, "question_title": { "type": "text" }, "answer_text": { "type": "text" }, "blog_post_title": { "type": "text" }, "comment_text": { "type": "text" }, "department_name": { "type": "keyword" }, "employee_name": { "type": "text" } } } }

在这个例子中,我们定义了三种关系: questionanswerblog_postcomment,以及 departmentemployeemy_join_field 就是我们的 Join 数据类型字段。

索引文档

索引父文档 question:

POST /my_index_join/_doc/question_1 { "question_title": "Elasticsearch Join 数据类型详解", "my_join_field": { "name": "question" // 标记为 "question" 类型 } }

索引子文档 answer,并指定 parent 关系:

POST /my_index_join/_doc/answer_1?routing=question_1&parent=question_1 // routing 仍需与父文档一致,并指定 parent 参数 { "answer_text": "Join 数据类型非常强大!", "my_join_field": { "name": "answer", // 标记为 "answer" 类型 "parent": "question_1" // 指向父文档 ID } }

注意: 与早期的 _parent 字段不同,join 字段本身就包含了关系信息 (nameparent),使得关系定义更加明确和集中。 routingparent 参数在索引子文档时仍然需要,以确保父子文档在同一分片。

查询 Join 数据类型

查询 Join 数据类型的方式与父子文档类似,仍然使用 has_childhas_parent 查询,但需要指定 join 字段的名称。

查询与 question 父文档关联的 answer 子文档:

GET /my_index_join/_search { "query": { "has_child": { "type": "answer", // 仍然使用 type,但 type 现在指的是 relations 中定义的子关系名称 "join": "my_join_field", // 指定 join 字段名称 "query": { "match": { "answer_text": "强大" } }, "inner_hits": {} } } }

查询与 answer 子文档关联的 question 父文档:

GET /my_index_join/_search { "query": { "has_parent": { "parent_type": "question", // 仍然使用 parent_type,指 relations 中定义的父关系名称 "join": "my_join_field", // 指定 join 字段名称 "query": { "match": { "question_title": "Join 数据类型详解" } }, "inner_hits": {} } } }

更灵活的关系查询

Join 数据类型不仅支持 has_childhas_parent 查询,还可以在聚合 (Aggregation) 中使用,实现更复杂的关系分析。例如,我们可以统计每个部门有多少员工:

GET /my_index_join/_search { "aggs": { "departments": { "terms": { "field": "department_name" }, "aggs": { "employee_count": { "children": { // 使用 children aggregation,指定 join 字段和子关系类型 "type": "employee", "join": "my_join_field" } } } } } }

mermaid 图示:Join 数据类型关系

2.2 Join 数据类型的优势

相比于父子文档关系,Join 数据类型具有以下显著优势:

  • 更灵活的关系定义: Join 数据类型允许在一个索引中定义多种关系类型,并为每种关系指定名称,使得数据建模更加灵活,可以适应更复杂的需求。

  • 更清晰的关系表达: 关系信息被明确地定义在 join 字段中,使得文档之间的关系更加清晰易懂,维护性更高。

  • 更广泛的应用场景: Join 数据类型不仅可以用于父子关系,还可以用于兄弟关系、多对多关系等,应用场景更加广泛。

  • 更好的性能: 虽然 Join 数据类型底层仍然依赖于 routing 机制来保证父子文档在同一分片,但其更灵活的设计为未来的性能优化提供了更大的空间。

  • 更强大的查询和聚合能力: Join 数据类型不仅支持父子文档的查询方式,还可以在聚合中使用,实现更复杂的关系分析和统计。

3. 父子文档到 Join 数据类型的迁移

由于 Join 数据类型的优势明显,Elasticsearch 官方推荐使用 Join 数据类型来替代父子文档关系。虽然 Elasticsearch 版本仍然支持父子文档的语法,但官方已经明确表示将在未来的版本中逐步移除对父子文档的支持。

迁移策略

如果你现有的 Elasticsearch 集群中使用了父子文档关系,建议尽早迁移到 Join 数据类型。迁移过程主要包括以下步骤:

  1. 修改映射 (Mapping): 将原有的父子文档映射修改为使用 Join 数据类型,定义 join 字段和 relations 属性。

  2. 重新索引数据 (Reindexing): 由于映射发生了变化,你需要重新索引现有的数据。在重新索引的过程中,需要将原有的父子关系信息转换为 Join 数据类型所需的格式,即在文档中添加 join 字段,并设置 nameparent 属性。

  3. 修改查询和聚合 (Queries and Aggregations): 更新现有的查询和聚合语句,将针对父子文档的查询方式修改为针对 Join 数据类型的查询方式,例如在 has_childhas_parent 查询中指定 join 字段名称。

迁移示例

假设你之前使用父子文档关系存储博客文章和评论,现在要迁移到 Join 数据类型。

原有父子文档映射 (假设为 type-per-index 模式):

PUT /blog_posts_index { "mappings": { "blog_post": { "properties": { "blog_post_title": { "type": "text" } } } } } PUT /comments_index { "mappings": { "comment": { "_parent": { "type": "blog_post" }, "properties": { "comment_text": { "type": "text" } } } } }

迁移后的 Join 数据类型映射 (single-index 模式):

PUT /blog_index_join_migrated { "mappings": { "properties": { "my_join_field": { "type": "join", "relations": { "blog_post": "comment" } }, "blog_post_title": { "type": "text" }, "comment_text": { "type": "text" } } } }

数据迁移 (Reindex 过程 - 简化示例,实际需要考虑批量操作和滚动重启等):

blog_posts_index 迁移 blog_post 文档:

POST /blog_index_join_migrated/_doc/blog_post_1 { "blog_post_title": "Elasticsearch 父子文档迁移到 Join 数据类型", "my_join_field": { "name": "blog_post" } }

comments_index 迁移 comment 文档 (假设 comment_1 的父文档是 blog_post_1):

POST /blog_index_join_migrated/_doc/comment_1?routing=blog_post_1&parent=blog_post_1 { "comment_text": "迁移成功!", "my_join_field": { "name": "comment", "parent": "blog_post_1" } }

完成映射修改和数据重新索引后,你需要相应地修改你的应用程序代码,更新查询和聚合语句,以适应 Join 数据类型的语法。

4. 总结

父子文档关系是 Elasticsearch 早期版本中处理文档关系的一种尝试,但由于其固有的局限性,逐渐被更强大、更灵活的 Join 数据类型所取代。Join 数据类型不仅解决了父子文档的性能瓶颈和功能限制,还提供了更广泛的应用场景和更清晰的关系表达。

在 Elasticsearch 以后的版本中,强烈建议使用 Join 数据类型来处理文档之间的关系。迁移到 Join 数据类型不仅可以提升性能,还能为未来的扩展和更复杂的关系建模奠定基础。理解父子文档的历史背景和 Join 数据类型的优势,有助于你更好地选择和使用 Elasticsearch 的高级特性,构建更高效、更强大的搜索应用。

随着 Elasticsearch 的不断演进,掌握其最新的特性和最佳实践至关重要。Join 数据类型正是 Elasticsearch 在关系数据处理方面迈出的重要一步,值得每一位 Elasticsearch 用户深入学习和应用。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U