Тип поля join
Тип данных join — это специальное поле, которое создаёт родительско-дочерние отношения внутри документов одного индекса. В разделе relations определён набор возможных отношений между документами, каждое из которых представляет собой имя родителя и имя ребёнка.
Не рекомендуется использовать несколько уровней отношений для имитации реляционной модели. Каждый уровень отношений добавляет издержки при запросах с точки зрения памяти и вычислений. Для лучшей производительности поиска денормализуйте данные вместо этого.
Родительско-дочернее отношение можно определить следующим образом:
PUT my-index-000001
{
"mappings": {
"properties": {
"my_id": {
"type": "keyword"
},
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
}
}
}
}
} | Имя поля | |
| Определяет единственное отношение, где |
Для индексации документа с соединением необходимо указать имя отношения и необязательного родителя документа в source. Например, следующий пример создаёт два документа parent в контексте question:
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"
}
} | Этот документ — документ |
При индексации родительских документов можно указать только имя отношения как сокращение вместо его инкапсуляции в обычную нотацию объекта:
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"
} | Более простое обозначение для родительского документа использует только имя отношения. |
При индексации дочернего документа необходимо добавить имя отношения и идентификатор родителя документа в _source.
Для индексации генеалогического древа родителя необходимо, чтобы родитель и дочерние документы были индексированы в одном фрагменте, поэтому вы всегда должны использовать маршрутизацию дочерних документов, используя идентификатор родителя.
Например, следующий пример демонстрирует, как индексировать два документа child:
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"
}
} | Значение маршрутизации обязательно, потому что родительские и дочерние документы должны быть индексированы в одном фрагменте | |
|
| |
| Идентификатор родителя этого дочернего документа |
Родительское соединение и производительность
Поле соединения не следует использовать как соединения в реляционной базе данных. В Elasticsearch ключом к хорошей производительности является денормализация данных в документы. Каждое поле соединения, запрос has_child или has_parent добавляет значительную нагрузку на производительность запросов. Это также может привести к созданию глобальных ординалов.
Единственный случай, когда поле соединения имеет смысл, — это если ваши данные содержат отношение один ко многим, где один сущность значительно превосходит другую. Пример такого случая — использование с продуктами и предложениями для этих продуктов. В том случае, если предложений значительно больше, чем продуктов, имеет смысл моделировать продукт как родительский документ, а предложение — как дочерний.
Ограничения родительского соединения
- Разрешена только одна карта поля
joinна индекс. - Родительские и дочерние документы должны быть индексированы в одном фрагменте. Это означает, что одинаковое значение
routingнеобходимо указать при получении, удалении или обновлении дочернего документа. - Элемент может иметь несколько дочерних элементов, но только одного родителя.
- Возможно добавление нового отношения к существующему полю
join. - Также возможно добавить дочерний элемент к существующему элементу, но только если этот элемент уже является родителем.
Поиск с родительским соединением
Родительское соединение создаёт одно поле для индексации имени отношения в документе (my_parent, my_child, …).
Также создаётся одно поле на каждое родительско-дочернее отношение. Название этого поля — имя поля join, за которым следуют # и имя родителя в отношении. Например, для отношения my_parent → [my_child, another_child] поле join создаёт дополнительное поле с именем my_join_field#my_parent.
Это поле содержит родительский _id, к которому документ связан, если документ является дочерним (my_child или another_child), и _id документа, если он родительский (my_parent).
При поиске индекса, содержащего поле join, эти два поля всегда возвращаются в ответе на запрос:
GET my-index-000001/_search
{
"query": {
"match_all": {}
},
"sort": ["my_id"]
} Вернёт:
{
...,
"hits": {
"total": {
"value": 4,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "my-index-000001",
"_type": "_doc",
"_id": "1",
"_score": null,
"_source": {
"my_id": "1",
"text": "This is a question",
"my_join_field": "question"
},
"sort": [
"1"
]
},
{
"_index": "my-index-000001",
"_type": "_doc",
"_id": "2",
"_score": null,
"_source": {
"my_id": "2",
"text": "This is another question",
"my_join_field": "question"
},
"sort": [
"2"
]
},
{
"_index": "my-index-000001",
"_type": "_doc",
"_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",
"_type": "_doc",
"_id": "4",
"_score": null,
"_routing": "1",
"_source": {
"my_id": "4",
"text": "This is another answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
},
"sort": [
"4"
]
}
]
}
} | Этот документ принадлежит соединению | |
| Этот документ принадлежит соединению | |
| Этот документ принадлежит соединению | |
| Связанный идентификатор родителя для дочернего документа |
Запросы и агрегации с родительским соединением
См. запросы has_child и has_parent, агрегацию children и внутренние результаты для получения дополнительной информации.
Значение поля join доступно в агрегациях и скриптах, и может быть запрошено с помощью запроса parent_id:
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" }
]
} | Запрос к полю | |
| Агрегирование по полю | |
| Доступ к полю |
Глобальные ординалы
Поле join использует глобальные ординалы для ускорения соединений. Глобальные ординалы необходимо перестраивать после любого изменения фрагмента. Чем больше значений идентификаторов родителей хранится в фрагменте, тем дольше занимает перестройка глобальных ординалов для поля join.
Глобальные ординалы по умолчанию строятся в режиме ожидания: если индекс изменился, глобальные ординалы для поля join будут перестроены в рамках обновления. Это может значительно увеличить время обновления. Однако в большинстве случаев это правильный компромисс, иначе глобальные ординалы перестраиваются, когда используется первый запрос или агрегация с родительским соединением. Это может привести к значительным задержкам для пользователей, и обычно это ухудшается, поскольку несколько глобальных ординалов для поля join могут быть перестроены в течение одного интервала обновления при множестве операций записи.
Если поле join используется редко, а записи происходят часто, может быть целесообразно отключить немедленную загрузку:
PUT my-index-000001
{
"mappings": {
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
},
"eager_global_ordinals": false
}
}
}
} Объём памяти, используемый глобальными ординалами, можно проверить для каждого родительского отношения следующим образом:
# Per-index GET _stats/fielddata?human&fields=my_join_field#question # Per-node per-index GET _nodes/stats/indices/fielddata?human&fields=my_join_field#question
Несколько дочерних элементов на родителя
Также возможно определить несколько дочерних элементов для одного родителя:
PUT my-index-000001
{
"mappings": {
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": ["answer", "comment"]
}
}
}
}
} |
|
Несколько уровней родительского объединения
Мы не рекомендуем использовать несколько уровней отношений для дублирования реляционной модели. Каждый уровень отношения добавляет издержки при выполнении запросов с точки зрения памяти и вычислений. Для лучшей производительности поиска денормализуйте данные вместо этого.
Несколько уровней родитель/дочь:
PUT my-index-000001
{
"mappings": {
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": ["answer", "comment"],
"answer": "vote"
}
}
}
}
} |
| |
|
|
Вышеприведенное отображение представляет следующую структуру дерева:
question
/ \
/ \
comment answer
|
|
vote Для индексирования документа внука требуется значение routing, равное значению прародителя (более старшего родителя в родословной):
PUT my-index-000001/_doc/3?routing=1&refresh
{
"text": "This is a vote",
"my_join_field": {
"name": "vote",
"parent": "2"
}
} | Этот документ-потомок должен находиться на том же фрагменте, что и его прародитель и родитель | |
| Идентификатор родителя этого документа (должен указывать на документ |
© 2023-2025 Elasticsearch
As of September 2024, Elasticsearch is available under a choice of three licenses: the Server Side Public License (SSPL), the Elastic License, or the AGPLv3 (OSI approved).
Elasticsearch and the Elasticsearch logo are trademarks of Elasticsearch B.V., registered in the U.S. and in other countries.
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/parent-join.html