Настройка Elasticsearch для морфологического поиска (русский, английский)
Стандартный анализатор и даже встроенные языковые анализаторы Elasticsearch используют алгоритмы стемминга — эвристическое срезание окончаний. Стемминг быстрый, но неточный: «листья» → «лист», «лиса» → «лис», но «бегать» и «бег» дают разные стеммы, хотя семантически связаны. Морфологический анализ — это полноценный разбор словоформ с использованием словарей: «бегать», «бежит», «бегу» → лемма «бегать».
Стемминг vs. Лемматизация
Стемминг (Snowball, Porter) — обрезает окончания по правилам. Быстро, ~1–2 мс на токен, не требует словарей. Ошибается на нестандартных словах, не знает контекста.
Лемматизация — словарный поиск базовой формы (леммы). Точнее, требует словарей по 50–500 MB, медленнее при индексации, но значительно лучше для морфологически богатых языков (русский, украинский, польский).
Плагин Morphological Analyzer (morpheus / mystem)
Для продакшен-качества морфологического поиска на русском — плагин analysis-morphology или elasticsearch-analysis-mystem.
Установка elasticsearch-analysis-mystem (Яндекс Mystem через плагин):
# Нет официального плагина в реестре Elastic — используем community build
/usr/share/elasticsearch/bin/elasticsearch-plugin install \
https://github.com/imotov/elasticsearch-analysis-morphology/releases/download/5.5.0.1/elasticsearch-analysis-morphology-5.5.0.1.zip
Важно: версия плагина должна совпадать с версией Elasticsearch. Для ES 8.x — использовать актуальные форки или альтернативы.
Альтернатива — плагин analysis-nori (корейский морфологический, не подходит) или ICU + стемминг. Для ES 8.x наиболее стабильный вариант — встроенный russian анализатор + дополнение через синонимы для проблемных случаев.
Конфигурация с analysis-morphology плагином
PUT /docs
{
"settings": {
"analysis": {
"filter": {
"russian_morphology": {
"type": "russian_morphology"
},
"english_morphology": {
"type": "english_morphology"
},
"russian_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"english_stop": {
"type": "stop",
"stopwords": "_english_"
}
},
"analyzer": {
"ru_en_morphology": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"russian_morphology",
"english_morphology",
"russian_stop",
"english_stop"
]
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "ru_en_morphology",
"search_analyzer": "ru_en_morphology"
}
}
}
}
Если плагин не подходит по версии, реализуем через встроенные средства с Hunspell словарями.
Hunspell — словарный стемминг
Hunspell — spell-checker с поддержкой морфологических словарей. Elasticsearch поддерживает Hunspell нативно через hunspell token filter.
Подготовка словарей:
# Создать директорию для словарей
mkdir -p /etc/elasticsearch/hunspell/ru_RU
cd /etc/elasticsearch/hunspell/ru_RU
# Скачать словарь (из LibreOffice/OpenOffice репозитория)
wget https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ru_RU/ru_RU.dic
wget https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ru_RU/ru_RU.aff
# Аналогично для en_US
mkdir -p /etc/elasticsearch/hunspell/en_US
wget https://cgit.freedesktop.org/libreoffice/dictionaries/plain/en/en_US.dic
wget https://cgit.freedesktop.org/libreoffice/dictionaries/plain/en/en_US.aff
Конфигурация Elasticsearch для использования Hunspell:
# elasticsearch.yml
indices.analysis.hunspell.dictionary.location: /etc/elasticsearch/hunspell
indices.analysis.hunspell.dictionary.ignore_case: true
Настройка анализатора с Hunspell:
PUT /articles
{
"settings": {
"analysis": {
"filter": {
"ru_hunspell": {
"type": "hunspell",
"locale": "ru_RU",
"dedup": true,
"longest_only": false
},
"en_hunspell": {
"type": "hunspell",
"locale": "en_US",
"dedup": true
}
},
"analyzer": {
"ru_hunspell_analyzer": {
"tokenizer": "standard",
"filter": ["lowercase", "ru_hunspell", "russian_stop_words"]
},
"en_hunspell_analyzer": {
"tokenizer": "standard",
"filter": ["lowercase", "en_hunspell", "english_stop_words"]
},
"mixed_analyzer": {
"tokenizer": "standard",
"filter": [
"lowercase",
"ru_hunspell",
"en_hunspell",
"unique"
]
}
},
"filter": {
"russian_stop_words": {
"type": "stop",
"stopwords": "_russian_"
},
"english_stop_words": {
"type": "stop",
"stopwords": "_english_"
}
}
}
}
}
dedup: true — убирает дублирующиеся леммы (слово может иметь несколько форм с одинаковой леммой).
Проверка качества морфологии
# Проверяем как анализируется текст
POST /articles/_analyze
{
"analyzer": "ru_hunspell_analyzer",
"text": "Разработчики создали приложение для управления задачами"
}
Ожидаемые токены: разработчик, создать, приложение, управление, задача — базовые формы.
Без морфологии (snowball): разработчик, создал, приложен, управлен, задач — стеммированные урезки.
Запрос «разработка» с Hunspell найдёт документы со словами «разработчик», «разработчики», «разработан» — все из одного морфологического гнезда.
Двуязычный маппинг
Для контента, где русский и английский в одном поле:
"properties": {
"content": {
"type": "text",
"analyzer": "mixed_analyzer",
"search_analyzer": "mixed_analyzer",
"fields": {
"ru": {
"type": "text",
"analyzer": "ru_hunspell_analyzer"
},
"en": {
"type": "text",
"analyzer": "en_hunspell_analyzer"
}
}
}
}
Поиск с бустингом по языковым полям:
{
"query": {
"multi_match": {
"query": "machine learning обучение",
"fields": ["content^1", "content.ru^2", "content.en^1.5"],
"type": "most_fields"
}
}
}
Производительность Hunspell
Hunspell при индексации медленнее Snowball в 3–10 раз — словарный lookup vs. алгоритмическое срезание. Для индекса с >10 млн документов это существенно.
Оптимизации:
- Использовать
longest_only: true— возвращать только самую длинную лемму, уменьшает число токенов - Увеличить
refresh_intervalдо 60–300 секунд при bulk-индексации - Разделить индексацию по воркерам с bulk API
# Bulk индексация с отключённым refresh
PUT /articles/_settings
{
"index": {
"refresh_interval": "300s",
"number_of_replicas": 0
}
}
# После завершения индексации
PUT /articles/_settings
{
"index": {
"refresh_interval": "1s",
"number_of_replicas": 1
}
}
POST /articles/_forcemerge?max_num_segments=1
Сроки
Настройка Hunspell-анализаторов с подготовкой словарей для ru/en — 1–2 рабочих дня. Тестирование на реальных данных и подбор параметров (stop words, longest_only) — ещё 1 день. Миграция существующего индекса с переиндексацией — дополнительный день в зависимости от объёма.







