Join field type
join
データ型は、同じインデックス内のドキュメント間に親子関係を作成する特別なフィールドです。 relations
セクションでは、ドキュメント内の可能な関係のセットを定義し、各関係は親の名前と子の名前で構成されます。
複数のレベルの関係を使用してリレーショナルモデルを再現することはお勧めしません。各レベルの関係は、クエリ時にメモリと計算の観点からオーバーヘッドを追加します。より良い検索パフォーマンスのために、データを非正規化してください。
親子関係は次のように定義できます:
Python
resp = client.indices.create(
index="my-index-000001",
mappings={
"properties": {
"my_id": {
"type": "keyword"
},
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
}
}
}
},
)
print(resp)
Ruby
response = client.indices.create(
index: 'my-index-000001',
body: {
mappings: {
properties: {
my_id: {
type: 'keyword'
},
my_join_field: {
type: 'join',
relations: {
question: 'answer'
}
}
}
}
}
)
puts response
Js
const response = await client.indices.create({
index: "my-index-000001",
mappings: {
properties: {
my_id: {
type: "keyword",
},
my_join_field: {
type: "join",
relations: {
question: "answer",
},
},
},
},
});
console.log(response);
Console
PUT my-index-000001
{
"mappings": {
"properties": {
"my_id": {
"type": "keyword"
},
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
}
}
}
}
}
フィールドの名前 | |
question が answer の親である単一の関係を定義します。 |
ジョインを持つドキュメントをインデックスするには、関係の名前とドキュメントのオプションの親を source
に提供する必要があります。たとえば、次の例では、question
コンテキスト内に2つの parent
ドキュメントを作成します:
Python
resp = client.index(
index="my-index-000001",
id="1",
refresh=True,
document={
"my_id": "1",
"text": "This is a question",
"my_join_field": {
"name": "question"
}
},
)
print(resp)
resp1 = client.index(
index="my-index-000001",
id="2",
refresh=True,
document={
"my_id": "2",
"text": "This is another question",
"my_join_field": {
"name": "question"
}
},
)
print(resp1)
Ruby
response = client.index(
index: 'my-index-000001',
id: 1,
refresh: true,
body: {
my_id: '1',
text: 'This is a question',
my_join_field: {
name: 'question'
}
}
)
puts response
response = client.index(
index: 'my-index-000001',
id: 2,
refresh: true,
body: {
my_id: '2',
text: 'This is another question',
my_join_field: {
name: 'question'
}
}
)
puts response
Js
const response = await client.index({
index: "my-index-000001",
id: 1,
refresh: "true",
document: {
my_id: "1",
text: "This is a question",
my_join_field: {
name: "question",
},
},
});
console.log(response);
const response1 = await client.index({
index: "my-index-000001",
id: 2,
refresh: "true",
document: {
my_id: "2",
text: "This is another question",
my_join_field: {
name: "question",
},
},
});
console.log(response1);
Console
PUT my-index-000001/_doc/1?refresh
{
"my_id": "1",
"text": "This is a question",
"my_join_field": {
"name": "question"
}
}
PUT my-index-000001/_doc/2?refresh
{
"my_id": "2",
"text": "This is another question",
"my_join_field": {
"name": "question"
}
}
このドキュメントは question ドキュメントです。 |
親ドキュメントをインデックスする際には、通常のオブジェクト表記でカプセル化する代わりに、関係の名前だけをショートカットとして指定することができます:
Python
resp = client.index(
index="my-index-000001",
id="1",
refresh=True,
document={
"my_id": "1",
"text": "This is a question",
"my_join_field": "question"
},
)
print(resp)
resp1 = client.index(
index="my-index-000001",
id="2",
refresh=True,
document={
"my_id": "2",
"text": "This is another question",
"my_join_field": "question"
},
)
print(resp1)
Js
const response = await client.index({
index: "my-index-000001",
id: 1,
refresh: "true",
document: {
my_id: "1",
text: "This is a question",
my_join_field: "question",
},
});
console.log(response);
const response1 = await client.index({
index: "my-index-000001",
id: 2,
refresh: "true",
document: {
my_id: "2",
text: "This is another question",
my_join_field: "question",
},
});
console.log(response1);
Console
PUT my-index-000001/_doc/1?refresh
{
"my_id": "1",
"text": "This is a question",
"my_join_field": "question"
}
PUT my-index-000001/_doc/2?refresh
{
"my_id": "2",
"text": "This is another question",
"my_join_field": "question"
}
親ドキュメントの簡略表記は関係名を使用します。 |
子をインデックスする際には、関係の名前とドキュメントの親IDを _source
に追加する必要があります。
親の系譜を同じシャードにインデックスする必要があるため、常に子ドキュメントをその大きな親IDを使用してルーティングする必要があります。
たとえば、次の例では、2つの child
ドキュメントをインデックスする方法を示しています:
Python
resp = client.index(
index="my-index-000001",
id="3",
routing="1",
refresh=True,
document={
"my_id": "3",
"text": "This is an answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
},
)
print(resp)
resp1 = client.index(
index="my-index-000001",
id="4",
routing="1",
refresh=True,
document={
"my_id": "4",
"text": "This is another answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
},
)
print(resp1)
Ruby
response = client.index(
index: 'my-index-000001',
id: 3,
routing: 1,
refresh: true,
body: {
my_id: '3',
text: 'This is an answer',
my_join_field: {
name: 'answer',
parent: '1'
}
}
)
puts response
response = client.index(
index: 'my-index-000001',
id: 4,
routing: 1,
refresh: true,
body: {
my_id: '4',
text: 'This is another answer',
my_join_field: {
name: 'answer',
parent: '1'
}
}
)
puts response
Js
const response = await client.index({
index: "my-index-000001",
id: 3,
routing: 1,
refresh: "true",
document: {
my_id: "3",
text: "This is an answer",
my_join_field: {
name: "answer",
parent: "1",
},
},
});
console.log(response);
const response1 = await client.index({
index: "my-index-000001",
id: 4,
routing: 1,
refresh: "true",
document: {
my_id: "4",
text: "This is another answer",
my_join_field: {
name: "answer",
parent: "1",
},
},
});
console.log(response1);
Console
PUT my-index-000001/_doc/3?routing=1&refresh
{
"my_id": "3",
"text": "This is an answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
}
PUT my-index-000001/_doc/4?routing=1&refresh
{
"my_id": "4",
"text": "This is another answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
}
ルーティング値は必須です。なぜなら、親ドキュメントと子ドキュメントは同じシャードにインデックスされなければならないからです。 | |
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_parent
、my_child
、…)。
また、親子関係ごとに1つのフィールドを作成します。このフィールドの名前は、join
フィールドの名前に #
と関係の親の名前を続けたものです。たとえば、my_parent
→ [my_child
、another_child
] 関係の場合、join
フィールドは my_join_field#my_parent
という追加のフィールドを作成します。
このフィールドには、ドキュメントが子である場合(my_child
または another_child
)、ドキュメントがリンクする親 _id
が含まれ、親である場合(my_parent
)にはドキュメントの _id
が含まれます。
join
フィールドを含むインデックスを検索する際、これら2つのフィールドは常に検索応答に返されます:
Python
resp = client.search(
index="my-index-000001",
query={
"match_all": {}
},
sort=[
"my_id"
],
)
print(resp)
Js
const response = await client.search({
index: "my-index-000001",
query: {
match_all: {},
},
sort: ["my_id"],
});
console.log(response);
Console
GET my-index-000001/_search
{
"query": {
"match_all": {}
},
"sort": ["my_id"]
}
Console-Result
{
...,
"hits": {
"total": {
"value": 4,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "my-index-000001",
"_id": "1",
"_score": null,
"_source": {
"my_id": "1",
"text": "This is a question",
"my_join_field": "question"
},
"sort": [
"1"
]
},
{
"_index": "my-index-000001",
"_id": "2",
"_score": null,
"_source": {
"my_id": "2",
"text": "This is another question",
"my_join_field": "question"
},
"sort": [
"2"
]
},
{
"_index": "my-index-000001",
"_id": "3",
"_score": null,
"_routing": "1",
"_source": {
"my_id": "3",
"text": "This is an answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
},
"sort": [
"3"
]
},
{
"_index": "my-index-000001",
"_id": "4",
"_score": null,
"_routing": "1",
"_source": {
"my_id": "4",
"text": "This is another answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
},
"sort": [
"4"
]
}
]
}
}
このドキュメントは question ジョインに属します。 |
|
このドキュメントは question ジョインに属します。 |
|
このドキュメントは answer ジョインに属します。 |
|
子ドキュメントのリンクされた親ID |
Parent-join queries and aggregations
詳細については、has_child
および has_parent
クエリ、children
集計、及び inner hits を参照してください。
join
フィールドの値は集計やスクリプトでアクセス可能であり、parent_id
クエリ でクエリすることができます:
Python
resp = client.search(
index="my-index-000001",
query={
"parent_id": {
"type": "answer",
"id": "1"
}
},
aggs={
"parents": {
"terms": {
"field": "my_join_field#question",
"size": 10
}
}
},
runtime_mappings={
"parent": {
"type": "long",
"script": "\n emit(Integer.parseInt(doc['my_join_field#question'].value)) \n "
}
},
fields=[
{
"field": "parent"
}
],
)
print(resp)
Js
const response = await client.search({
index: "my-index-000001",
query: {
parent_id: {
type: "answer",
id: "1",
},
},
aggs: {
parents: {
terms: {
field: "my_join_field#question",
size: 10,
},
},
},
runtime_mappings: {
parent: {
type: "long",
script:
"\n emit(Integer.parseInt(doc['my_join_field#question'].value)) \n ",
},
},
fields: [
{
field: "parent",
},
],
});
console.log(response);
Console
GET my-index-000001/_search
{
"query": {
"parent_id": {
"type": "answer",
"id": "1"
}
},
"aggs": {
"parents": {
"terms": {
"field": "my_join_field#question",
"size": 10
}
}
},
"runtime_mappings": {
"parent": {
"type": "long",
"script": """
emit(Integer.parseInt(doc['my_join_field#question'].value))
"""
}
},
"fields": [
{ "field": "parent" }
]
}
parent id フィールドをクエリする(has_parent クエリ および has_child クエリ も参照) |
|
parent id フィールドで集計する(children 集計も参照) |
|
スクリプト内で parent id フィールドにアクセスする。 |
Global ordinals
join
フィールドは、ジョインを高速化するために グローバルオーディナル を使用します。グローバルオーディナルは、シャードに変更があった後に再構築する必要があります。シャードに保存される親IDの値が多いほど、join
フィールドのグローバルオーディナルを再構築するのに時間がかかります。
デフォルトでは、グローバルオーディナルは積極的に構築されます:インデックスが変更された場合、join
フィールドのグローバルオーディナルはリフレッシュの一部として再構築されます。これにより、リフレッシュにかなりの時間が追加される可能性があります。しかし、ほとんどの場合、これは正しいトレードオフです。そうでなければ、最初の親ジョインクエリまたは集計が使用されるときにグローバルオーディナルが再構築されます。これにより、ユーザーにとって重要な遅延スパイクが発生する可能性があり、通常、複数の join
フィールドのグローバルオーディナルが多くの書き込みが発生している単一のリフレッシュ間隔内で再構築されることが悪化します。
join
フィールドがあまり使用されず、書き込みが頻繁に発生する場合、積極的な読み込みを無効にすることが理にかなっているかもしれません:
Python
resp = client.indices.create(
index="my-index-000001",
mappings={
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
},
"eager_global_ordinals": False
}
}
},
)
print(resp)
Ruby
response = client.indices.create(
index: 'my-index-000001',
body: {
mappings: {
properties: {
my_join_field: {
type: 'join',
relations: {
question: 'answer'
},
eager_global_ordinals: false
}
}
}
}
)
puts response
Js
const response = await client.indices.create({
index: "my-index-000001",
mappings: {
properties: {
my_join_field: {
type: "join",
relations: {
question: "answer",
},
eager_global_ordinals: false,
},
},
},
});
console.log(response);
Console
PUT my-index-000001
{
"mappings": {
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
},
"eager_global_ordinals": false
}
}
}
}
グローバルオーディナルによって使用されるヒープの量は、次のように親関係ごとに確認できます:
Python
resp = client.indices.stats(
metric="fielddata",
human=True,
fields="my_join_field",
)
print(resp)
resp1 = client.nodes.stats(
metric="indices",
index_metric="fielddata",
human=True,
fields="my_join_field",
)
print(resp1)
Ruby
response = client.indices.stats(
metric: 'fielddata',
human: true,
fields: 'my_join_field'
)
puts response
response = client.nodes.stats(
metric: 'indices',
index_metric: 'fielddata',
human: true,
fields: 'my_join_field'
)
puts response
Js
const response = await client.indices.stats({
metric: "fielddata",
human: "true",
fields: "my_join_field",
});
console.log(response);
const response1 = await client.nodes.stats({
metric: "indices",
index_metric: "fielddata",
human: "true",
fields: "my_join_field",
});
console.log(response1);
Console
# Per-index
GET _stats/fielddata?human\u0026fields=my_join_field#question
# Per-node per-index
GET _nodes/stats/indices/fielddata?human\u0026fields=my_join_field#question
Multiple children per parent
単一の親に対して複数の子を定義することも可能です:
Python
resp = client.indices.create(
index="my-index-000001",
mappings={
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": [
"answer",
"comment"
]
}
}
}
},
)
print(resp)
Ruby
response = client.indices.create(
index: 'my-index-000001',
body: {
mappings: {
properties: {
my_join_field: {
type: 'join',
relations: {
question: [
'answer',
'comment'
]
}
}
}
}
}
)
puts response
Js
const response = await client.indices.create({
index: "my-index-000001",
mappings: {
properties: {
my_join_field: {
type: "join",
relations: {
question: ["answer", "comment"],
},
},
},
},
});
console.log(response);
Console
PUT my-index-000001
{
"mappings": {
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": ["answer", "comment"]
}
}
}
}
}
question は answer と comment の親です。 |
Multiple levels of parent join
複数のレベルの関係を使用してリレーショナルモデルを再現することはお勧めしません。各レベルの関係は、クエリ時にメモリと計算の観点からオーバーヘッドを追加します。より良い検索パフォーマンスのために、データを非正規化してください。
親子関係の複数のレベル:
Python
resp = client.indices.create(
index="my-index-000001",
mappings={
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": [
"answer",
"comment"
],
"answer": "vote"
}
}
}
},
)
print(resp)
Ruby
response = client.indices.create(
index: 'my-index-000001',
body: {
mappings: {
properties: {
my_join_field: {
type: 'join',
relations: {
question: [
'answer',
'comment'
],
answer: 'vote'
}
}
}
}
}
)
puts response
Js
const response = await client.indices.create({
index: "my-index-000001",
mappings: {
properties: {
my_join_field: {
type: "join",
relations: {
question: ["answer", "comment"],
answer: "vote",
},
},
},
},
});
console.log(response);
Console
PUT my-index-000001
{
"mappings": {
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": ["answer", "comment"],
"answer": "vote"
}
}
}
}
}
question は answer と comment の親です。 |
|
answer は vote の親です。 |
上記のマッピングは次のツリーを表します:
``````bash
question
/ \
/ \
comment answer
|
|
vote
孫ドキュメントをインデックスするには、`````routing````` 値が祖父(系譜の大きな親)と等しくなる必要があります:
#### Python
``````python
resp = client.index(
index="my-index-000001",
id="3",
routing="1",
refresh=True,
document={
"text": "This is a vote",
"my_join_field": {
"name": "vote",
"parent": "2"
}
},
)
print(resp)
Ruby
response = client.index(
index: 'my-index-000001',
id: 3,
routing: 1,
refresh: true,
body: {
text: 'This is a vote',
my_join_field: {
name: 'vote',
parent: '2'
}
}
)
puts response
Js
const response = await client.index({
index: "my-index-000001",
id: 3,
routing: 1,
refresh: "true",
document: {
text: "This is a vote",
my_join_field: {
name: "vote",
parent: "2",
},
},
});
console.log(response);
Console
PUT my-index-000001/_doc/3?routing=1&refresh
{
"text": "This is a vote",
"my_join_field": {
"name": "vote",
"parent": "2"
}
}
この子ドキュメントは、その祖父と親と同じシャードに存在する必要があります。 | |
このドキュメントの親ID(answer ドキュメントを指す必要があります) |