Join field type

join データ型は、同じインデックス内のドキュメント間に親子関係を作成する特別なフィールドです。 relations セクションでは、ドキュメント内の可能な関係のセットを定義し、各関係は親の名前と子の名前で構成されます。

複数のレベルの関係を使用してリレーショナルモデルを再現することはお勧めしません。各レベルの関係は、クエリ時にメモリと計算の観点からオーバーヘッドを追加します。より良い検索パフォーマンスのために、データを非正規化してください。

親子関係は次のように定義できます:

Python

  1. resp = client.indices.create(
  2. index="my-index-000001",
  3. mappings={
  4. "properties": {
  5. "my_id": {
  6. "type": "keyword"
  7. },
  8. "my_join_field": {
  9. "type": "join",
  10. "relations": {
  11. "question": "answer"
  12. }
  13. }
  14. }
  15. },
  16. )
  17. print(resp)

Ruby

  1. response = client.indices.create(
  2. index: 'my-index-000001',
  3. body: {
  4. mappings: {
  5. properties: {
  6. my_id: {
  7. type: 'keyword'
  8. },
  9. my_join_field: {
  10. type: 'join',
  11. relations: {
  12. question: 'answer'
  13. }
  14. }
  15. }
  16. }
  17. }
  18. )
  19. puts response

Js

  1. const response = await client.indices.create({
  2. index: "my-index-000001",
  3. mappings: {
  4. properties: {
  5. my_id: {
  6. type: "keyword",
  7. },
  8. my_join_field: {
  9. type: "join",
  10. relations: {
  11. question: "answer",
  12. },
  13. },
  14. },
  15. },
  16. });
  17. console.log(response);

Console

  1. PUT my-index-000001
  2. {
  3. "mappings": {
  4. "properties": {
  5. "my_id": {
  6. "type": "keyword"
  7. },
  8. "my_join_field": {
  9. "type": "join",
  10. "relations": {
  11. "question": "answer"
  12. }
  13. }
  14. }
  15. }
  16. }
フィールドの名前
questionanswer の親である単一の関係を定義します。

ジョインを持つドキュメントをインデックスするには、関係の名前とドキュメントのオプションの親を source に提供する必要があります。たとえば、次の例では、question コンテキスト内に2つの parent ドキュメントを作成します:

Python

  1. resp = client.index(
  2. index="my-index-000001",
  3. id="1",
  4. refresh=True,
  5. document={
  6. "my_id": "1",
  7. "text": "This is a question",
  8. "my_join_field": {
  9. "name": "question"
  10. }
  11. },
  12. )
  13. print(resp)
  14. resp1 = client.index(
  15. index="my-index-000001",
  16. id="2",
  17. refresh=True,
  18. document={
  19. "my_id": "2",
  20. "text": "This is another question",
  21. "my_join_field": {
  22. "name": "question"
  23. }
  24. },
  25. )
  26. print(resp1)

Ruby

  1. response = client.index(
  2. index: 'my-index-000001',
  3. id: 1,
  4. refresh: true,
  5. body: {
  6. my_id: '1',
  7. text: 'This is a question',
  8. my_join_field: {
  9. name: 'question'
  10. }
  11. }
  12. )
  13. puts response
  14. response = client.index(
  15. index: 'my-index-000001',
  16. id: 2,
  17. refresh: true,
  18. body: {
  19. my_id: '2',
  20. text: 'This is another question',
  21. my_join_field: {
  22. name: 'question'
  23. }
  24. }
  25. )
  26. puts response

Js

  1. const response = await client.index({
  2. index: "my-index-000001",
  3. id: 1,
  4. refresh: "true",
  5. document: {
  6. my_id: "1",
  7. text: "This is a question",
  8. my_join_field: {
  9. name: "question",
  10. },
  11. },
  12. });
  13. console.log(response);
  14. const response1 = await client.index({
  15. index: "my-index-000001",
  16. id: 2,
  17. refresh: "true",
  18. document: {
  19. my_id: "2",
  20. text: "This is another question",
  21. my_join_field: {
  22. name: "question",
  23. },
  24. },
  25. });
  26. console.log(response1);

Console

  1. PUT my-index-000001/_doc/1?refresh
  2. {
  3. "my_id": "1",
  4. "text": "This is a question",
  5. "my_join_field": {
  6. "name": "question"
  7. }
  8. }
  9. PUT my-index-000001/_doc/2?refresh
  10. {
  11. "my_id": "2",
  12. "text": "This is another question",
  13. "my_join_field": {
  14. "name": "question"
  15. }
  16. }
このドキュメントは question ドキュメントです。

親ドキュメントをインデックスする際には、通常のオブジェクト表記でカプセル化する代わりに、関係の名前だけをショートカットとして指定することができます:

Python

  1. resp = client.index(
  2. index="my-index-000001",
  3. id="1",
  4. refresh=True,
  5. document={
  6. "my_id": "1",
  7. "text": "This is a question",
  8. "my_join_field": "question"
  9. },
  10. )
  11. print(resp)
  12. resp1 = client.index(
  13. index="my-index-000001",
  14. id="2",
  15. refresh=True,
  16. document={
  17. "my_id": "2",
  18. "text": "This is another question",
  19. "my_join_field": "question"
  20. },
  21. )
  22. print(resp1)

Js

  1. const response = await client.index({
  2. index: "my-index-000001",
  3. id: 1,
  4. refresh: "true",
  5. document: {
  6. my_id: "1",
  7. text: "This is a question",
  8. my_join_field: "question",
  9. },
  10. });
  11. console.log(response);
  12. const response1 = await client.index({
  13. index: "my-index-000001",
  14. id: 2,
  15. refresh: "true",
  16. document: {
  17. my_id: "2",
  18. text: "This is another question",
  19. my_join_field: "question",
  20. },
  21. });
  22. console.log(response1);

Console

  1. PUT my-index-000001/_doc/1?refresh
  2. {
  3. "my_id": "1",
  4. "text": "This is a question",
  5. "my_join_field": "question"
  6. }
  7. PUT my-index-000001/_doc/2?refresh
  8. {
  9. "my_id": "2",
  10. "text": "This is another question",
  11. "my_join_field": "question"
  12. }
親ドキュメントの簡略表記は関係名を使用します。

子をインデックスする際には、関係の名前とドキュメントの親IDを _source に追加する必要があります。

親の系譜を同じシャードにインデックスする必要があるため、常に子ドキュメントをその大きな親IDを使用してルーティングする必要があります。

たとえば、次の例では、2つの child ドキュメントをインデックスする方法を示しています:

Python

  1. resp = client.index(
  2. index="my-index-000001",
  3. id="3",
  4. routing="1",
  5. refresh=True,
  6. document={
  7. "my_id": "3",
  8. "text": "This is an answer",
  9. "my_join_field": {
  10. "name": "answer",
  11. "parent": "1"
  12. }
  13. },
  14. )
  15. print(resp)
  16. resp1 = client.index(
  17. index="my-index-000001",
  18. id="4",
  19. routing="1",
  20. refresh=True,
  21. document={
  22. "my_id": "4",
  23. "text": "This is another answer",
  24. "my_join_field": {
  25. "name": "answer",
  26. "parent": "1"
  27. }
  28. },
  29. )
  30. print(resp1)

Ruby

  1. response = client.index(
  2. index: 'my-index-000001',
  3. id: 3,
  4. routing: 1,
  5. refresh: true,
  6. body: {
  7. my_id: '3',
  8. text: 'This is an answer',
  9. my_join_field: {
  10. name: 'answer',
  11. parent: '1'
  12. }
  13. }
  14. )
  15. puts response
  16. response = client.index(
  17. index: 'my-index-000001',
  18. id: 4,
  19. routing: 1,
  20. refresh: true,
  21. body: {
  22. my_id: '4',
  23. text: 'This is another answer',
  24. my_join_field: {
  25. name: 'answer',
  26. parent: '1'
  27. }
  28. }
  29. )
  30. puts response

Js

  1. const response = await client.index({
  2. index: "my-index-000001",
  3. id: 3,
  4. routing: 1,
  5. refresh: "true",
  6. document: {
  7. my_id: "3",
  8. text: "This is an answer",
  9. my_join_field: {
  10. name: "answer",
  11. parent: "1",
  12. },
  13. },
  14. });
  15. console.log(response);
  16. const response1 = await client.index({
  17. index: "my-index-000001",
  18. id: 4,
  19. routing: 1,
  20. refresh: "true",
  21. document: {
  22. my_id: "4",
  23. text: "This is another answer",
  24. my_join_field: {
  25. name: "answer",
  26. parent: "1",
  27. },
  28. },
  29. });
  30. console.log(response1);

Console

  1. PUT my-index-000001/_doc/3?routing=1&refresh
  2. {
  3. "my_id": "3",
  4. "text": "This is an answer",
  5. "my_join_field": {
  6. "name": "answer",
  7. "parent": "1"
  8. }
  9. }
  10. PUT my-index-000001/_doc/4?routing=1&refresh
  11. {
  12. "my_id": "4",
  13. "text": "This is another answer",
  14. "my_join_field": {
  15. "name": "answer",
  16. "parent": "1"
  17. }
  18. }
ルーティング値は必須です。なぜなら、親ドキュメントと子ドキュメントは同じシャードにインデックスされなければならないからです。
answer はこのドキュメントのジョインの名前です。
この子ドキュメントの親ID

Parent-join and performance

ジョインフィールドは、リレーショナルデータベースのジョインのように使用すべきではありません。Elasticsearchにおいて、良好なパフォーマンスの鍵はデータをドキュメントに非正規化することです。各ジョインフィールド、has_child または has_parent クエリは、クエリパフォーマンスに対して重要な負担を追加します。また、グローバルオーディナルを構築するトリガーにもなります。

ジョインフィールドが意味を持つ唯一のケースは、データが一対多の関係を含み、一方のエンティティが他方のエンティティを大幅に上回る場合です。このようなケースの例は、製品とその製品のオファーのユースケースです。オファーが製品の数を大幅に上回る場合、製品を親ドキュメントとしてモデル化し、オファーを子ドキュメントとしてモデル化することが理にかなっています。

Parent-join restrictions

  • インデックスごとに1つの join フィールドマッピングのみが許可されます。
  • 親ドキュメントと子ドキュメントは同じシャードにインデックスされなければなりません。これは、子ドキュメントを取得削除、または更新する際に、同じ routing 値を提供する必要があることを意味します。
  • 1つの要素は複数の子を持つことができますが、親は1つだけです。
  • 既存の join フィールドに新しい関係を追加することが可能です。
  • 既存の要素に子を追加することも可能ですが、その要素がすでに親である場合のみです。

Searching with parent-join

親ジョインは、ドキュメント内の関係の名前をインデックスするための1つのフィールドを作成します(my_parentmy_child、…)。

また、親子関係ごとに1つのフィールドを作成します。このフィールドの名前は、join フィールドの名前に # と関係の親の名前を続けたものです。たとえば、my_parent → [my_childanother_child] 関係の場合、join フィールドは my_join_field#my_parent という追加のフィールドを作成します。

このフィールドには、ドキュメントが子である場合(my_child または another_child)、ドキュメントがリンクする親 _id が含まれ、親である場合(my_parent)にはドキュメントの _id が含まれます。

join フィールドを含むインデックスを検索する際、これら2つのフィールドは常に検索応答に返されます:

Python

  1. resp = client.search(
  2. index="my-index-000001",
  3. query={
  4. "match_all": {}
  5. },
  6. sort=[
  7. "my_id"
  8. ],
  9. )
  10. print(resp)

Js

  1. const response = await client.search({
  2. index: "my-index-000001",
  3. query: {
  4. match_all: {},
  5. },
  6. sort: ["my_id"],
  7. });
  8. console.log(response);

Console

  1. GET my-index-000001/_search
  2. {
  3. "query": {
  4. "match_all": {}
  5. },
  6. "sort": ["my_id"]
  7. }

返される内容:

Console-Result

  1. {
  2. ...,
  3. "hits": {
  4. "total": {
  5. "value": 4,
  6. "relation": "eq"
  7. },
  8. "max_score": null,
  9. "hits": [
  10. {
  11. "_index": "my-index-000001",
  12. "_id": "1",
  13. "_score": null,
  14. "_source": {
  15. "my_id": "1",
  16. "text": "This is a question",
  17. "my_join_field": "question"
  18. },
  19. "sort": [
  20. "1"
  21. ]
  22. },
  23. {
  24. "_index": "my-index-000001",
  25. "_id": "2",
  26. "_score": null,
  27. "_source": {
  28. "my_id": "2",
  29. "text": "This is another question",
  30. "my_join_field": "question"
  31. },
  32. "sort": [
  33. "2"
  34. ]
  35. },
  36. {
  37. "_index": "my-index-000001",
  38. "_id": "3",
  39. "_score": null,
  40. "_routing": "1",
  41. "_source": {
  42. "my_id": "3",
  43. "text": "This is an answer",
  44. "my_join_field": {
  45. "name": "answer",
  46. "parent": "1"
  47. }
  48. },
  49. "sort": [
  50. "3"
  51. ]
  52. },
  53. {
  54. "_index": "my-index-000001",
  55. "_id": "4",
  56. "_score": null,
  57. "_routing": "1",
  58. "_source": {
  59. "my_id": "4",
  60. "text": "This is another answer",
  61. "my_join_field": {
  62. "name": "answer",
  63. "parent": "1"
  64. }
  65. },
  66. "sort": [
  67. "4"
  68. ]
  69. }
  70. ]
  71. }
  72. }
このドキュメントは question ジョインに属します。
このドキュメントは question ジョインに属します。
このドキュメントは answer ジョインに属します。
子ドキュメントのリンクされた親ID

Parent-join queries and aggregations

詳細については、has_child および has_parent クエリ、children 集計、及び inner hits を参照してください。

join フィールドの値は集計やスクリプトでアクセス可能であり、parent_id クエリ でクエリすることができます:

Python

  1. resp = client.search(
  2. index="my-index-000001",
  3. query={
  4. "parent_id": {
  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. runtime_mappings={
  18. "parent": {
  19. "type": "long",
  20. "script": "\n emit(Integer.parseInt(doc['my_join_field#question'].value)) \n "
  21. }
  22. },
  23. fields=[
  24. {
  25. "field": "parent"
  26. }
  27. ],
  28. )
  29. print(resp)

Js

  1. const response = await client.search({
  2. index: "my-index-000001",
  3. query: {
  4. parent_id: {
  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. runtime_mappings: {
  18. parent: {
  19. type: "long",
  20. script:
  21. "\n emit(Integer.parseInt(doc['my_join_field#question'].value)) \n ",
  22. },
  23. },
  24. fields: [
  25. {
  26. field: "parent",
  27. },
  28. ],
  29. });
  30. console.log(response);

Console

  1. GET my-index-000001/_search
  2. {
  3. "query": {
  4. "parent_id": {
  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. "runtime_mappings": {
  18. "parent": {
  19. "type": "long",
  20. "script": """
  21. emit(Integer.parseInt(doc['my_join_field#question'].value))
  22. """
  23. }
  24. },
  25. "fields": [
  26. { "field": "parent" }
  27. ]
  28. }
parent id フィールドをクエリする(has_parent クエリ および has_child クエリ も参照)
parent id フィールドで集計する(children 集計も参照)
スクリプト内で parent id フィールドにアクセスする。

Global ordinals

join フィールドは、ジョインを高速化するために グローバルオーディナル を使用します。グローバルオーディナルは、シャードに変更があった後に再構築する必要があります。シャードに保存される親IDの値が多いほど、join フィールドのグローバルオーディナルを再構築するのに時間がかかります。

デフォルトでは、グローバルオーディナルは積極的に構築されます:インデックスが変更された場合、join フィールドのグローバルオーディナルはリフレッシュの一部として再構築されます。これにより、リフレッシュにかなりの時間が追加される可能性があります。しかし、ほとんどの場合、これは正しいトレードオフです。そうでなければ、最初の親ジョインクエリまたは集計が使用されるときにグローバルオーディナルが再構築されます。これにより、ユーザーにとって重要な遅延スパイクが発生する可能性があり、通常、複数の join フィールドのグローバルオーディナルが多くの書き込みが発生している単一のリフレッシュ間隔内で再構築されることが悪化します。

join フィールドがあまり使用されず、書き込みが頻繁に発生する場合、積極的な読み込みを無効にすることが理にかなっているかもしれません:

Python

  1. resp = client.indices.create(
  2. index="my-index-000001",
  3. mappings={
  4. "properties": {
  5. "my_join_field": {
  6. "type": "join",
  7. "relations": {
  8. "question": "answer"
  9. },
  10. "eager_global_ordinals": False
  11. }
  12. }
  13. },
  14. )
  15. print(resp)

Ruby

  1. response = client.indices.create(
  2. index: 'my-index-000001',
  3. body: {
  4. mappings: {
  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. )
  17. puts response

Js

  1. const response = await client.indices.create({
  2. index: "my-index-000001",
  3. mappings: {
  4. properties: {
  5. my_join_field: {
  6. type: "join",
  7. relations: {
  8. question: "answer",
  9. },
  10. eager_global_ordinals: false,
  11. },
  12. },
  13. },
  14. });
  15. console.log(response);

Console

  1. PUT my-index-000001
  2. {
  3. "mappings": {
  4. "properties": {
  5. "my_join_field": {
  6. "type": "join",
  7. "relations": {
  8. "question": "answer"
  9. },
  10. "eager_global_ordinals": false
  11. }
  12. }
  13. }
  14. }

グローバルオーディナルによって使用されるヒープの量は、次のように親関係ごとに確認できます:

Python

  1. resp = client.indices.stats(
  2. metric="fielddata",
  3. human=True,
  4. fields="my_join_field",
  5. )
  6. print(resp)
  7. resp1 = client.nodes.stats(
  8. metric="indices",
  9. index_metric="fielddata",
  10. human=True,
  11. fields="my_join_field",
  12. )
  13. print(resp1)

Ruby

  1. response = client.indices.stats(
  2. metric: 'fielddata',
  3. human: true,
  4. fields: 'my_join_field'
  5. )
  6. puts response
  7. response = client.nodes.stats(
  8. metric: 'indices',
  9. index_metric: 'fielddata',
  10. human: true,
  11. fields: 'my_join_field'
  12. )
  13. puts response

Js

  1. const response = await client.indices.stats({
  2. metric: "fielddata",
  3. human: "true",
  4. fields: "my_join_field",
  5. });
  6. console.log(response);
  7. const response1 = await client.nodes.stats({
  8. metric: "indices",
  9. index_metric: "fielddata",
  10. human: "true",
  11. fields: "my_join_field",
  12. });
  13. console.log(response1);

Console

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

Multiple children per parent

単一の親に対して複数の子を定義することも可能です:

Python

  1. resp = client.indices.create(
  2. index="my-index-000001",
  3. mappings={
  4. "properties": {
  5. "my_join_field": {
  6. "type": "join",
  7. "relations": {
  8. "question": [
  9. "answer",
  10. "comment"
  11. ]
  12. }
  13. }
  14. }
  15. },
  16. )
  17. print(resp)

Ruby

  1. response = client.indices.create(
  2. index: 'my-index-000001',
  3. body: {
  4. mappings: {
  5. properties: {
  6. my_join_field: {
  7. type: 'join',
  8. relations: {
  9. question: [
  10. 'answer',
  11. 'comment'
  12. ]
  13. }
  14. }
  15. }
  16. }
  17. }
  18. )
  19. puts response

Js

  1. const response = await client.indices.create({
  2. index: "my-index-000001",
  3. mappings: {
  4. properties: {
  5. my_join_field: {
  6. type: "join",
  7. relations: {
  8. question: ["answer", "comment"],
  9. },
  10. },
  11. },
  12. },
  13. });
  14. console.log(response);

Console

  1. PUT my-index-000001
  2. {
  3. "mappings": {
  4. "properties": {
  5. "my_join_field": {
  6. "type": "join",
  7. "relations": {
  8. "question": ["answer", "comment"]
  9. }
  10. }
  11. }
  12. }
  13. }
questionanswercomment の親です。

Multiple levels of parent join

複数のレベルの関係を使用してリレーショナルモデルを再現することはお勧めしません。各レベルの関係は、クエリ時にメモリと計算の観点からオーバーヘッドを追加します。より良い検索パフォーマンスのために、データを非正規化してください。

親子関係の複数のレベル:

Python

  1. resp = client.indices.create(
  2. index="my-index-000001",
  3. mappings={
  4. "properties": {
  5. "my_join_field": {
  6. "type": "join",
  7. "relations": {
  8. "question": [
  9. "answer",
  10. "comment"
  11. ],
  12. "answer": "vote"
  13. }
  14. }
  15. }
  16. },
  17. )
  18. print(resp)

Ruby

  1. response = client.indices.create(
  2. index: 'my-index-000001',
  3. body: {
  4. mappings: {
  5. properties: {
  6. my_join_field: {
  7. type: 'join',
  8. relations: {
  9. question: [
  10. 'answer',
  11. 'comment'
  12. ],
  13. answer: 'vote'
  14. }
  15. }
  16. }
  17. }
  18. }
  19. )
  20. puts response

Js

  1. const response = await client.indices.create({
  2. index: "my-index-000001",
  3. mappings: {
  4. properties: {
  5. my_join_field: {
  6. type: "join",
  7. relations: {
  8. question: ["answer", "comment"],
  9. answer: "vote",
  10. },
  11. },
  12. },
  13. },
  14. });
  15. console.log(response);

Console

  1. PUT my-index-000001
  2. {
  3. "mappings": {
  4. "properties": {
  5. "my_join_field": {
  6. "type": "join",
  7. "relations": {
  8. "question": ["answer", "comment"],
  9. "answer": "vote"
  10. }
  11. }
  12. }
  13. }
  14. }
questionanswercomment の親です。
answervote の親です。

上記のマッピングは次のツリーを表します:


``````bash
question
/ \
/ \
comment answer
|
|

vote

  1. 孫ドキュメントをインデックスするには、`````routing````` 値が祖父(系譜の大きな親)と等しくなる必要があります:
  2. #### Python
  3. ``````python
  4. resp = client.index(
  5. index="my-index-000001",
  6. id="3",
  7. routing="1",
  8. refresh=True,
  9. document={
  10. "text": "This is a vote",
  11. "my_join_field": {
  12. "name": "vote",
  13. "parent": "2"
  14. }
  15. },
  16. )
  17. print(resp)

Ruby

  1. response = client.index(
  2. index: 'my-index-000001',
  3. id: 3,
  4. routing: 1,
  5. refresh: true,
  6. body: {
  7. text: 'This is a vote',
  8. my_join_field: {
  9. name: 'vote',
  10. parent: '2'
  11. }
  12. }
  13. )
  14. puts response

Js

  1. const response = await client.index({
  2. index: "my-index-000001",
  3. id: 3,
  4. routing: 1,
  5. refresh: "true",
  6. document: {
  7. text: "This is a vote",
  8. my_join_field: {
  9. name: "vote",
  10. parent: "2",
  11. },
  12. },
  13. });
  14. console.log(response);

Console

  1. PUT my-index-000001/_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 ドキュメントを指す必要があります)