Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Elasticsearch: полнотекстовый поиск и релевантность

Elasticsearch: как устроен полнотекстовый поиск

Elasticsearch: инвертированный индекс, анализаторы и токенизация, BM25, агрегации и ограничения поискового движка.

Прямой ответ

Elasticsearch — распределённый поисковый движок, построенный поверх библиотеки Lucene. Его основная структура — инвертированный индекс: вместо хранения «документ → текст» хранится «термин → список документов, где он встречается». Это превращает поиск слова из перебора всех документов в обращение к словарю терминов.

Как текст попадает в индекс:

  1. Анализатор разбивает текст на токены: разделение по пробелам и пунктуации.
  2. Нормализация приводит токены к нижнему регистру, убирает диакритику.
  3. Стемминг или лемматизация приводит слова к базовой форме: «ноутбуки» → «ноутбук».
  4. Стоп-слова удаляются: предлоги и союзы не несут поисковой ценности.
  5. Термины записываются в инвертированный индекс вместе с позициями и частотой.

Критично: анализатор применяется и при индексации, и при поиске. Если поле индексировалось со стеммингом, а запрос анализируется без него, совпадения не будет. Настройка анализатора — основная часть работы над качеством поиска.

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 } }
  }
}

Рекомендации:

  1. Измеряйте качество поиска. Без набора эталонных запросов с ожидаемыми результатами настройка анализатора превращается в угадывание.
  2. Разделяйте поиск и фильтрацию. Полнотекстовый match по анализируемому полю и точный term по keyword — разные операции с разной стоимостью.
  3. Не используйте Elasticsearch как основную базу. Отсутствие полноценных транзакций и высокая стоимость обновления отдельных документов делают его неудобным для OLTP — о различии нагрузок в статье про OLTP и OLAP.
  4. Сравнивайте с встроенными средствами. PostgreSQL поддерживает полнотекстовый поиск и триграммный индекс; для умеренных объёмов этого достаточно — см. полнотекстовый поиск в PostgreSQL.
  5. Следите за размером индекса. Реплики удваивают объём; сегменты до слияния занимают больше места, чем полезный объём.

Практический сценарий: поиск не находил товары из-за словоформ

Контекст: поиск по каталогу не возвращал результатов по запросу «ноутбуки», хотя товары с названием «Ноутбук» были в наличии.

  1. Диагностика. Проверка показала: поле индексировалось стандартным анализатором без стемминга для русского языка. Термин «ноутбуки» попадал в индекс как есть и не совпадал с «ноутбук».
  2. Дополнительно. Запрос строился как term вместо match, поэтому запрос не проходил через анализатор вовсе и требовал точного совпадения со значением в индексе.
  3. Действия. (а) Добавили анализатор со стеммером и стоп-словами для русского языка; (б) запрос перевели на match, чтобы анализ применялся и к запросу; (в) для фильтров по бренду и категории оставили keyword и term; (г) создали новый индекс, перенесли данные и переключили псевдоним; (д) собрали набор из 40 эталонных запросов с ожидаемыми результатами для проверки.
  4. Результат. Доля запросов без результатов снизилась с 23 до 4 %, число переходов в карточку товара выросло.
  5. Профилактика. Набор эталонных запросов добавили в CI: при изменении анализатора прогон показывает изменение порядка выдачи.

Вывод: качество полнотекстового поиска определяется настройкой анализа текста, а не мощностью движка. Без набора эталонных запросов изменения в анализаторе невозможно оценить объективно.

FAQ

Чем Elasticsearch отличается от поиска в реляционной базе? Поисковый движок строит инвертированный индекс и оценивает релевантность, тогда как LIKE '%строка%' выполняет перебор. Встроенный полнотекстовый поиск PostgreSQL ближе к поисковому движку, но уступает в гибкости анализа и распределённости.

Что такое BM25 и можно ли его настроить? Это функция оценки релевантности, учитывающая частоту термина в документе, обратную частоту в коллекции и длину документа. Параметры k1 и b настраиваются, но менять их стоит только при наличии измеримой метрики качества.

Насколько быстро данные становятся доступны для поиска? По умолчанию — в течение секунды из-за интервала обновления индекса. Для сценариев «записал и сразу ищет» интервал уменьшают или выполняют явное обновление, что увеличивает нагрузку.

Нужен ли кластер для небольшого каталога? Обычно нет. Одна нода с репликой справляется с сотнями тысяч документов. Кластеризация нужна при больших объёмах, высокой нагрузке или требованиях к доступности.

Источники: Elasticsearch reference, PostgreSQL docs (text search) — ссылки в шапке.