Прямой ответ
Elasticsearch — распределённый поисковый движок, построенный поверх библиотеки Lucene. Его основная структура — инвертированный индекс: вместо хранения «документ → текст» хранится «термин → список документов, где он встречается». Это превращает поиск слова из перебора всех документов в обращение к словарю терминов.
Как текст попадает в индекс:
- Анализатор разбивает текст на токены: разделение по пробелам и пунктуации.
- Нормализация приводит токены к нижнему регистру, убирает диакритику.
- Стемминг или лемматизация приводит слова к базовой форме: «ноутбуки» → «ноутбук».
- Стоп-слова удаляются: предлоги и союзы не несут поисковой ценности.
- Термины записываются в инвертированный индекс вместе с позициями и частотой.
Критично: анализатор применяется и при индексации, и при поиске. Если поле индексировалось со стеммингом, а запрос анализируется без него, совпадения не будет. Настройка анализатора — основная часть работы над качеством поиска.
PUT /products
{
"settings": {
"analysis": {
"analyzer": {
"ru_stem": {
"tokenizer": "standard",
"filter": ["lowercase", "russian_stemmer", "stop_ru"]
}
}
}
},
"mappings": {
"properties": {
"title": { "type": "text", "analyzer": "ru_stem" },
"title_kw": { "type": "keyword" },
"price": { "type": "integer" },
"category_id": { "type": "long" }
}
}
}
Важное различие типов: text анализируется и используется для полнотекстового поиска, keyword хранится как есть и используется для точной фильтрации, сортировки и агрегаций. Поле, по которому нужна и фильтрация, и поиск, обычно описывают дважды через fields.
Релевантность считается по алгоритму BM25 (по умолчанию): чем чаще термин встречается в документе и чем реже он в коллекции, тем выше оценка; длина документа снижает оценку. Результат можно объяснить через API _explain, что необходимо при отладке порядка выдачи.
Практика: настройка и эксплуатация
1. Elasticsearch — не источник истины. Он оптимизирован под поиск, а не под транзакции. Данные обычно живут в основной базе и синхронизируются в индекс. Задержка синхронизации и поведение при сбое должны быть определены; о потоке изменений — в статье про CDC.
2. Обновление индекса не мгновенно. По умолчанию индекс обновляется раз в секунду (refresh_interval), поэтому только что записанный документ может быть не виден в поиске. Для задач, где это критично, интервал настраивают или используют явное обновление.
3. Сегменты и слияния. Данные хранятся в неизменяемых сегментах, которые периодически сливаются. Это LSM-подобная модель: запись дёшева, чтение требует обращения к нескольким сегментам — см. LSM-tree.
4. Маппинг лучше задавать явно. Автоматическое определение типов удобно на старте и опасно в продакшене: строковое поле может стать text там, где нужен keyword, и изменить это без переиндексации нельзя.
5. Переиндексация — регулярная операция. Изменение анализатора или маппинга требует создания нового индекса и переноса данных. Практика — псевдоним (alias), который переключают на новый индекс без остановки сервиса.
6. Агрегации — сильная сторона. Подсчёт по категориям, гистограммы цен, фасетная навигация выполняются одним запросом вместе с поиском.
GET /products/_search
{
"query": { "match": { "title": "ноутбук для программирования" } },
"aggs": {
"by_category": { "terms": { "field": "category_id", "size": 10 } },
"price_hist": { "histogram": { "field": "price", "interval": 20000 } }
}
}
Рекомендации:
- Измеряйте качество поиска. Без набора эталонных запросов с ожидаемыми результатами настройка анализатора превращается в угадывание.
- Разделяйте поиск и фильтрацию. Полнотекстовый
matchпо анализируемому полю и точныйtermпоkeyword— разные операции с разной стоимостью. - Не используйте Elasticsearch как основную базу. Отсутствие полноценных транзакций и высокая стоимость обновления отдельных документов делают его неудобным для OLTP — о различии нагрузок в статье про OLTP и OLAP.
- Сравнивайте с встроенными средствами. PostgreSQL поддерживает полнотекстовый поиск и триграммный индекс; для умеренных объёмов этого достаточно — см. полнотекстовый поиск в PostgreSQL.
- Следите за размером индекса. Реплики удваивают объём; сегменты до слияния занимают больше места, чем полезный объём.
Практический сценарий: поиск не находил товары из-за словоформ
Контекст: поиск по каталогу не возвращал результатов по запросу «ноутбуки», хотя товары с названием «Ноутбук» были в наличии.
- Диагностика. Проверка показала: поле индексировалось стандартным анализатором без стемминга для русского языка. Термин «ноутбуки» попадал в индекс как есть и не совпадал с «ноутбук».
- Дополнительно. Запрос строился как
termвместоmatch, поэтому запрос не проходил через анализатор вовсе и требовал точного совпадения со значением в индексе. - Действия. (а) Добавили анализатор со стеммером и стоп-словами для русского языка; (б) запрос перевели на
match, чтобы анализ применялся и к запросу; (в) для фильтров по бренду и категории оставилиkeywordиterm; (г) создали новый индекс, перенесли данные и переключили псевдоним; (д) собрали набор из 40 эталонных запросов с ожидаемыми результатами для проверки. - Результат. Доля запросов без результатов снизилась с 23 до 4 %, число переходов в карточку товара выросло.
- Профилактика. Набор эталонных запросов добавили в CI: при изменении анализатора прогон показывает изменение порядка выдачи.
Вывод: качество полнотекстового поиска определяется настройкой анализа текста, а не мощностью движка. Без набора эталонных запросов изменения в анализаторе невозможно оценить объективно.
FAQ
Чем Elasticsearch отличается от поиска в реляционной базе?
Поисковый движок строит инвертированный индекс и оценивает релевантность, тогда как LIKE '%строка%' выполняет перебор. Встроенный полнотекстовый поиск PostgreSQL ближе к поисковому движку, но уступает в гибкости анализа и распределённости.
Что такое BM25 и можно ли его настроить?
Это функция оценки релевантности, учитывающая частоту термина в документе, обратную частоту в коллекции и длину документа. Параметры k1 и b настраиваются, но менять их стоит только при наличии измеримой метрики качества.
Насколько быстро данные становятся доступны для поиска? По умолчанию — в течение секунды из-за интервала обновления индекса. Для сценариев «записал и сразу ищет» интервал уменьшают или выполняют явное обновление, что увеличивает нагрузку.
Нужен ли кластер для небольшого каталога? Обычно нет. Одна нода с репликой справляется с сотнями тысяч документов. Кластеризация нужна при больших объёмах, высокой нагрузке или требованиях к доступности.
Источники: Elasticsearch reference, PostgreSQL docs (text search) — ссылки в шапке.