Синонимы и кастомные анализаторы
Морфология, стемминг и стоп-слова, разобранные в предыдущих статьях, решают одну проблему: разные словоформы одного слова. Но у поискового движка есть и другая слепая зона — бизнес-синонимы: слова с разным написанием, которые означают одно и то же. Пользователь набирает «ноутбук», а в каталоге товар называется «laptop» или «лэптоп». Пользователь пишет «купить», а нужно найти документы с «приобрести». Анализатор russian с этим не справится — обрезание суффиксов здесь бессильно.
Именно здесь в дело вступают token filter synonym и synonym_graph. А вместе с ними — возможность собирать кастомные анализаторы точно под задачу, комбинируя любые фильтры в нужном порядке.
Два формата правил синонимов
Elasticsearch поддерживает Solr-формат — простой текстовый список, по одному правилу на строку. Правила бывают двух типов.
Эквивалентные синонимы — все термины взаимозаменяемы:
ноутбук, laptop, лэптоп
смартфон, мобильник, телефон
купить, приобрести, заказатьПо умолчанию (expand: true) такое правило разворачивается во все направления: запрос «laptop» найдёт документы с «ноутбук» и наоборот. При expand: false все термины схлопываются в первый — поиск ведётся только по нему.
Явное отображение (explicit mapping) — левая сторона отображается в правую, но не наоборот:
personal computer, пк => компьютер
нью-йорк => new york, ny
i-pod, ipod => ipodЗапросы «пк» или «personal computer» превратятся в «компьютер», но запрос «компьютер» не найдёт «пк» — связь односторонняя. Удобно для нормализации: пользователь вводит аббревиатуру или написание с дефисом, а в индексе хранится каноническая форма.
synonym и synonym_graph: в чём разница
В Elasticsearch два фильтра для синонимов:
synonym | synonym_graph | |
|---|---|---|
| Многословные синонимы | Могут давать позиционные конфликты | Корректная обработка через граф токенов |
| Рекомендуется для | Индексного анализатора | Поискового анализатора |
| Горячее обновление | Не поддерживается | Поддерживается (updateable: true) |
Проблема synonym с многословными синонимами конкретная: правило нью-йорк => new york создаёт токены «new» и «york» в одной позиции с «нью-йорк». Запрос match_phrase начинает вести себя непредсказуемо. Фильтр synonym_graph строит полный граф токенов с правильными позициями, поэтому фраза «нью-йорк» корректно распознаётся как единое двусловное понятие.
Порядок фильтров имеет значение. Если стеммер стоит до синоним-фильтра, он обработает и сами правила синонимов при загрузке. Правила тогда пришлось бы писать в «стемминговой» форме — неудобно. Рекомендуемый порядок: lowercase → stop → synonym_graph → stemmer — синонимы раскрываются в обычной форме слов, и только потом стеммер работает с результатом.
Индексация или поиск: когда применять синонимы
Синонимы можно добавить в анализатор на этапе индексации или только на этапе поиска — последствия разные:
| Индексный анализатор | Поисковый анализатор | |
|---|---|---|
| Как работает | Синонимы «вшиты» в инвертированный индекс | Применяются к тексту запроса в момент поиска |
| Обновление словаря | Требует полного reindex | Горячее обновление через _reload_search_analyzers |
| Размер индекса | Растёт (больше токенов) | Не меняется |
| Рекомендуется | Редко, только если словарь стабилен | В большинстве случаев |
Рекомендуемый паттерн — два разных анализатора на одно поле: analyzer (только морфология) и search_analyzer (морфология + synonym_graph). Словарь синонимов можно обновить в любой момент без переиндексации всего индекса.
flowchart TD
DOC([Документ]) --> IA
QUERY([Запрос пользователя]) --> SA
IA --> IDX[(Инвертированный индекс)]
SA --> IDX
subgraph IA["ru_index_analyzer"]
direction LR
A1[tokenizer] --> A2[lowercase] --> A3[stop] --> A4[stemmer]
end
subgraph SA["ru_search_analyzer"]
direction LR
B1[tokenizer] --> B2[lowercase] --> B3[stop] --> B4[synonym_graph] --> B5[stemmer]
endСборка кастомного анализатора: полный пример
Создадим индекс товарного каталога с двумя анализаторами:
PUT /catalog
{
"settings": {
"analysis": {
"filter": {
"ru_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"ru_stemmer": {
"type": "stemmer",
"language": "russian"
},
"product_synonyms": {
"type": "synonym_graph",
"updateable": true,
"synonyms_path": "analysis/product_synonyms.txt"
}
},
"analyzer": {
"ru_index_analyzer": {
"tokenizer": "standard",
"filter": ["lowercase", "ru_stop", "ru_stemmer"]
},
"ru_search_analyzer": {
"tokenizer": "standard",
"filter": ["lowercase", "ru_stop", "product_synonyms", "ru_stemmer"]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ru_index_analyzer",
"search_analyzer": "ru_search_analyzer"
}
}
}
}Файл config/analysis/product_synonyms.txt содержит одно правило на строку:
ноутбук, laptop, лэптоп
смартфон, мобильный телефон, мобильник
телевизор, тв, tv
наушники, гарнитураТот же подход работает и для английского каталога — достаточно заменить ru_stop и ru_stemmer на английские варианты и наполнить словарь английскими синонимами; принципы конфигурации те же, что описаны в Анализ английского текста: стемминг и стоп-слова.
Проверим, как search-анализатор видит запрос «купить laptop»:
POST /catalog/_analyze
{
"analyzer": "ru_search_analyzer",
"text": "купить laptop"
}При словаре выше «laptop» раскроется в эквивалентные токены «ноутбук», «laptop», «лэптоп» на одной позиции, затем стеммер обработает каждый из них. Документ с заголовком «Лёгкий лэптоп для офиса» будет найден, даже если пользователь написал «laptop».
Синонимы inline: только для прототипов
Если файл неудобен — например, при локальной разработке или в Docker с одной нодой — правила задаются прямо в настройках индекса:
"product_synonyms": {
"type": "synonym_graph",
"synonyms": [
"ноутбук, laptop, лэптоп",
"смартфон, мобильник, телефон"
]
}Минус принципиальный: горячее обновление с updateable: true при inline-синонимах не работает. Для смены правил придётся пересоздать индекс или перейти на synonyms_path / Synonyms API.
Обновление словаря без переиндексации
Главное преимущество поискового анализатора с synonym_graph — словарь можно обновить «на горячую». Три обязательных условия:
- Параметр
updateable: trueв фильтре - Фильтр используется только в
search_analyzer, а не вanalyzer - Правила хранятся в файле или через Synonyms API — не inline
Шаги обновления через файл:
# 1. Отредактировать файл на КАЖДОЙ ноде кластера
# (автоматической синхронизации между нодами нет)
# 2. Перезагрузить search-анализаторы
POST /catalog/_reload_search_analyzers
# 3. Очистить request cache
POST /catalog/_cache/clearSynonyms API (появился в ES 8.10) — управление словарём через REST без доступа к файловой системе нод. Идеально для Docker и облачных деплоев:
PUT /_synonyms/product_synonyms
{
"synonyms_set": [
{ "id": "laptops", "synonyms": "ноутбук, laptop, лэптоп" },
{ "id": "phones", "synonyms": "смартфон, мобильник, телефон" }
]
}После обновления через API всё равно нужно вызвать POST /catalog/_reload_search_analyzers.
Синонимы и морфология: вместе сильнее
Если вы уже используете Hunspell или morphology-плагин (см. Анализ русского текста: морфология, стемминг, Hunspell), синонимы добавляются в ту же цепочку без конфликтов. Правило простое: синонимы до стеммера — правила пишем в обычной форме слов; синонимы после — в форме лемм. Первый вариант читаемее и проще в поддержке.
Синонимы хорошо уживаются и с векторным поиском. В Гибридный поиск: retrievers и RRF синонимы на BM25-стороне компенсируют терминологические разрывы, которые эмбеддинги иногда пропускают при коротких запросах.
См. также
- Анатомия анализатора: char filters, токенизатор, token filters
- Анализ английского текста: стемминг и стоп-слова
- Анализ русского текста: морфология, стемминг, Hunspell
- Маппинг: явный, динамический и шаблоны индексов
- Полнотекстовые запросы: match, match_phrase, multi_match
- Гибридный поиск: retrievers и RRF