Имеется сущность, у которой может быть произвольное количество названий.
По этим названиям делается поиск в эластике.
Названия лежат в одном поле как массив. У поля сложный analyzer со сложным tokenizer.
Проблема у меня в том, что elastic рассматривает поле со множеством значений (массив) как строку, и релевантность результатов поиска считается как суммарная релевантность по всему массиву, а не как релевантность одного конкретного совпавшего элемента массива.
Ниже сильно упрощеный пример.
Создаём индекс
curl -XDELETE 'http://localhost:9200/tests'
curl -XPUT 'http://localhost:9200/tests' -d'{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"type": "custom",
"tokenizer": "edge_ngram_tokenizer",
"filter": ["lowercase", "asciifolding"]
}
},
"tokenizer": {
"edge_ngram_tokenizer": {
"type": "edgeNGram",
"min_gram": "3",
"max_gram": "12",
"token_chars": ["letter", "digit"]
}
}
}
},
"mappings": {
"test": {
"properties": {
"name": {
"type": "string",
"analyzer": "my_analyzer"
}
}
}
}
}'
Заполняем значениями
curl -XPOST 'http://localhost:9200/tests/test' -d'{ "id": 1, "name": ["text"] }'
curl -XPOST 'http://localhost:9200/tests/test' -d'{ "id": 2, "name": ["text", "text"] }'
Ищем
curl -XGET 'http://localhost:9200/tests/test/_search' -d'{
"query": {
"match": {
"name": "text"
}
}
}'
Результаты
{
"took": 0,
"timed_out": false,
"_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 },
"hits": {
"total": 2,
"max_score": 0.7911257,
"hits": [{
"_index": "tests",
"_type": "test",
"_id": "AWOtIL2gdpqdbX7hdDXg",
"_score": 0.7911257,
"_source": { "id": 2, "name": [ "text", "text" ] }
}, {
"_index": "tests",
"_type": "test",
"_id": "AWOtIL0ldpqdbX7hdDXf",
"_score": 0.51623213,
"_source": { "id": 1, "name": [ "text" ] }
}]
}
}
В результате имеем у id: 2 релевантность 0.7911257, а у id: 1 релевантность 0.51623213.
Мне нужно по запросу получить обе строки, и получить у них обязательно одинаковую релевантность.
Что делать?
Моя проблема происходит из-за того, что в production варианте кода я разбиваю название на несколько подполей, которые анализируются разными analyzer'ами, по которым затем идёт сложный dis_max поиск с разными весовыми коэффициентами на каждое из подполей (буст на полное совпадение, буст на совпадение начала стоки, поиск по частичному совпадению т.д.).
Я знаю два решения проблемы, но оба мне не подходят. Возможно, есть ещё какие-то варианты?
1. Когда названий мало, их можно хранить в отдельных полях name_0, name_1, name_2 и т.д.
При поиске делать dis_max запрос с tie_breaker: 0 и с релевантностью всё будет было хорошо.
"query": {
"dis_max": {
"queries": [
{ "match": { "name_0": "text" } },
{ "match": { "name_1": "text" } },
{ "match": { "name_2": "text" } }
],
"tie_breaker": 0,
"boost": 1
}
}
2. Можно хранить в эластике по одной записи на каждое название
curl -XPOST 'http://localhost:9200/tests/test' -d'{ "product_id": 1, "name": "text" }'
curl -XPOST 'http://localhost:9200/tests/test' -d'{ "product_id": 2, "name": "text" }'
curl -XPOST 'http://localhost:9200/tests/test' -d'{ "product_id": 2, "name": "text" }'
В таком варианте полученные результаты приходится дополнительно агрегировать по product_id, и в этом случае получаем проблемы с пагинацией результатов и с дальнейшим агрегированием результатов.