join类型

join类型是一个特殊的字段,它创建了位于同一个索引内文档的父/子关系。relations部分定义了存在于文档内的一系列可能的关系。每个关系需要一个父名,和子名。一个父子关系可以定义为如下方式:

  1. PUT my_index
  2. {
  3. "mappings": {
  4. "_doc": {
  5. "properties": {
  6. "my_join_field": {
  7. "type": "join",
  8. "relations": {
  9. "question": "answer"
  10. }
  11. }
  12. }
  13. }
  14. }
  15. }

my_join_field是字段的名字;

定义单个关系,questionanswer的父级。

索引带 join 的文档,关系的名字,文档的可选父级必须在source中提供。下面例子中创建了2个使用了question上下文创建的父级文档。

  1. PUT my_index/_doc/1?refresh
  2. {
  3. "text": "This is a question",
  4. "my_join_field": {
  5. "name": "question"
  6. }
  7. }
  8. PUT my_index/_doc/2?refresh
  9. {
  10. "text": "This is a another question",
  11. "my_join_field": {
  12. "name": "question"
  13. }
  14. }

该文档是一个question文档。

当索引父级文档时,可以直接指定关系名称,而不必封装在对象表示中:

  1. PUT my_index/_doc/1?refresh
  2. {
  3. "text": "This is a question",
  4. "my_join_field": "question"
  5. }
  6. PUT my_index/_doc/2?refresh
  7. {
  8. "text": "This is another question",
  9. "my_join_field": "question"
  10. }

使用关系名称简单表示父级文档。

当索引子级文档时,关系的名称与父文档的 id 必须添加到_source字段中。

要求一系的文档必须索引到同一个分片中,所以子文档必须使用父文档 id 进行路由。

下面两个例子展示了如何索引两个child文档:

  1. PUT my_index/_doc/3?routing=1&refresh (1)
  2. {
  3. "text": "This is an answer",
  4. "my_join_field": {
  5. "name": "answer", 2
  6. "parent": "1" (3)
  7. }
  8. }
  9. PUT my_index/_doc/4?routing=1&refresh
  10. {
  11. "text": "This is another answer",
  12. "my_join_field": {
  13. "name": "answer",
  14. "parent": "1"
  15. }
  16. }
  1. 路由值是强制的,因为父子文档必须索引到同一个分片上;

  2. answer是这个文档join字段的名称;

  3. 该子文档的父文档 ID。

父子文档性能

join 字段的使用不能像关系型数据库中的联接一样使用。elasticsearch 中良好性能的关键是反范式数据到文档中。每一个 join 字段,has_childhas_parent查询都极大的拖慢查询性能。

join 字段有意义的唯一情况就是数据包含一对多的关系,其中一个实体显著多余另一个实体。一个例子就是产品和这些产品的报价。这个例子中,报价的数量显著多余产品的数量,这样就可以定义产品为父文档,报价为子文档的模型。

父子关系限制

  • 每个索引只允许使用一个join字段。
  • 父文档和子文档必须索引到一个分片上。这意味着当查询、获取、更新子文档时都必须使用同一个routing值。
  • 一个元素可以有多个子元素,但只有一个父元素。
  • 可以向现有的join字段添加新的关系。
  • 可以向现有元素添加子元素,但条件是现有元素必须是父元素。

使用父子连接搜索

parent-join 在文档内创建了一个字段索引关系的名称(my_parent, my_child, …)。

它也为每个父子关系创建了一个字段。该字段的名称就是join字段的名称,后面跟随 #和关系中父级的名称。因此,例如my_parent =>[my_child, aother_child]关系,join字段创建了一种额外的字段称为my_join_field#my_parent

如果该文档是子级(my_child or another_child),那么这个字段包含了父级_id,如果该文档是父级(my_parent),则包含其_id

当查询包含join字段的索引,这两个字段总会在查询响应中返回:

  1. GET my_index/_search
  2. {
  3. "query": {
  4. "match_all": {}
  5. },
  6. "sort": ["_id"]
  7. }

响应:

  1. {
  2. ...,
  3. "hits": {
  4. "total": 4,
  5. "max_score": null,
  6. "hits": [
  7. {
  8. "_index": "my_index",
  9. "_type": "_doc",
  10. "_id": "1",
  11. "_score": null,
  12. "_source": {
  13. "text": "This is a question",
  14. "my_join_field": "question" (1)
  15. },
  16. "sort": [
  17. "1"
  18. ]
  19. },
  20. {
  21. "_index": "my_index",
  22. "_type": "_doc",
  23. "_id": "2",
  24. "_score": null,
  25. "_source": {
  26. "text": "This is another question",
  27. "my_join_field": "question"
  28. },
  29. "sort": [
  30. "2"
  31. ]
  32. },
  33. {
  34. "_index": "my_index",
  35. "_type": "_doc",
  36. "_id": "3",
  37. "_score": null,
  38. "_routing": "1",
  39. "_source": {
  40. "text": "This is an answer",
  41. "my_join_field": {
  42. "name": "answer", (2)
  43. "parent": "1" (3)
  44. }
  45. },
  46. "sort": [
  47. "3"
  48. ]
  49. },
  50. {
  51. "_index": "my_index",
  52. "_type": "_doc",
  53. "_id": "4",
  54. "_score": null,
  55. "_routing": "1",
  56. "_source": {
  57. "text": "This is another answer",
  58. "my_join_field": {
  59. "name": "answer",
  60. "parent": "1"
  61. }
  62. },
  63. "sort": [
  64. "4"
  65. ]
  66. }
  67. ]
  68. }
  69. }
  1. 该文档属于quesion联接;
  2. 该文档属于answer联接;
  3. 该子文档联接的父文档 ID;

Parent-join 查询和聚合

查看 has_child and has_parent 查询,the children 聚合, inner hits 等。

join 字段的值在聚合和脚本操作中也是可以访问到的,也可以使用parent_id query 查询:

  1. GET my_index/_search
  2. {
  3. "query": {
  4. "parent_id": { (1)
  5. "type": "answer",
  6. "id": "1"
  7. }
  8. },
  9. "aggs": {
  10. "parents": {
  11. "terms": {
  12. "field": "my_join_field#question",
  13. "size": 10
  14. }
  15. }
  16. },
  17. "script_fields": {
  18. "parent": {
  19. "script": {
  20. "source": "doc['my_join_field#question']"
  21. }
  22. }
  23. }
  24. }
  1. 查询 parent id 字段;
  2. parent id字段上聚合(also see the children aggregation);
  3. 在 script 中获取 parent id 的值;

全局序数

join字段使用 global ordinals 来加速联接。分片有任何改变都需要重建全局序数。分片中存储的父级 ID 值越多,为join字段重建全局序数花费的时间就越久。

默认情况下,全局序数会急切的构建:如果索引更改,就会在刷新过程中重建join字段的全局序数,这将会显著的增加刷新时间。然而,大多数情况下这是正确的权衡,否则,全局序数会在第一次父子查询或者聚合查询时重建。这可能会给您的用户带来严重的延迟峰值,并且通常会更糟,因为当发生多次写入时,可能会在单个刷新间隔内尝试重建连接字段的多个全局序数。

如果不经常使用join字段,且写入频繁时,禁用急切加载比较合适:

  1. PUT my_index
  2. {
  3. "mappings": {
  4. "_doc": {
  5. "properties": {
  6. "my_join_field": {
  7. "type": "join",
  8. "relations": {
  9. "question": "answer"
  10. },
  11. "eager_global_ordinals": false
  12. }
  13. }
  14. }
  15. }
  16. }

可以按每个父级关系来查看全局序数堆的使用总量:

  1. # Per-index
  2. GET _stats/fielddata?human&fields=my_join_field#question
  3. # Per-node per-index
  4. GET _nodes/stats/indices/fielddata?human&fields=my_join_field#question

同一个父级多个子文档

同一个父级可以有多个子级:

  1. PUT my_index
  2. {
  3. "mappings": {
  4. "_doc": {
  5. "properties": {
  6. "my_join_field": {
  7. "type": "join",
  8. "relations": {
  9. "question": ["answer", "comment"]
  10. }
  11. }
  12. }
  13. }
  14. }
  15. }

questionanswercomment的父级;

多层级父子关系

不建议使用多层级关系来复制关系模型。每级关系都会在查询时增加内存和计算的开销。如果很在乎性能应该对数据去规范化。

多层级的父子关系:

  1. PUT my_index
  2. {
  3. "mappings": {
  4. "_doc": {
  5. "properties": {
  6. "my_join_field": {
  7. "type": "join",
  8. "relations": {
  9. "question": ["answer", "comment"], (1)
  10. "answer": "vote" (2)
  11. }
  12. }
  13. }
  14. }
  15. }
  16. }
  1. questionanswercomment的父级;
  2. answervote的父级;

上面的映射表示为下面的树结构:

  1. question
  2. / \
  3. / \
  4. answer comment
  5. |
  6. |
  7. vote

索引一个孙子文档需要一个跟祖父相同的routing值(世系中的祖父)。

  1. PUT my_index/_doc/3?routing=1&refresh
  2. {
  3. "text": "This is a vote",
  4. "my_join_field": {
  5. "name": "vote",
  6. "parent": "2"
  7. }
  8. }

该子文档必须与其父文档,祖父文档在同一个分片上;

该文档的父 ID(必须指向 answer 文档);

翻译

https://www.elastic.co/guide/en/elasticsearch/reference/6.3/parent-join.html