Получить доступ

Полнотекстовый поиск (Elasticsearch)

Почему LIKE не поиск, инвертированный индекс, анализаторы, релевантность BM25, query vs filter context и поиск как read model.

Почему не LIKE

LIKE '%слово%' в SQL — это не поиск: ведущий wildcard не использует индекс (полный скан), нет учёта морфологии, опечаток, синонимов и релевантности — все результаты «равны».

Полнотекстовый поиск решает другую задачу: найти документы, релевантные запросу на естественном языке, и отранжировать их по степени соответствия. Под это заточены поисковые движки вроде Elasticsearch (поверх Lucene).

Инвертированный индекс

Сердце поиска — инвертированный индекс: отображение «термин → список документов, где он встречается» (с позициями и частотами). Это зеркало обычного индекса «документ → его слова».

Благодаря ему запрос «найдите документы со словом kafka» сводится к мгновенному взгляду в список по термину, а не к перебору документов — поэтому поиск по миллионам документов быстр.

Анализаторы: как текст становится терминами

Перед попаданием в индекс текст проходит analyzer: токенизацию (разбиение на слова), приведение к нижнему регистру, удаление стоп-слов и стемминг/лемматизацию (running → run, бежал → бежать).

Тот же анализатор применяется к запросу — иначе термины не совпадут. Для русского и других языков нужен подходящий языковой анализатор; дефолтный плохо работает с морфологией. Неправильный mapping/analyzer — частая причина «поиск ничего не находит».

Релевантность

Результаты ранжируются по score. Классическая идея TF-IDF: термин важнее, если часто встречается в документе (TF) и редок в коллекции (IDF). Современный дефолт — BM25, улучшенная версия с насыщением частоты и учётом длины документа.

Поэтому поиск возвращает не просто «совпало/нет», а упорядоченный по релевантности список — этого LIKE не умеет в принципе.

Query против filter context

Запросы делятся на query context (влияет на релевантность: match по тексту со скорингом) и filter context (да/нет, без скоринга: статус, дата, категория). Фильтры кэшируются и быстрее, поэтому точные ограничения стоит класть в filter, а текстовый поиск — в query.

Составные запросы собирают через bool (must/should/filter/must_not), комбинируя релевантность и жёсткие условия.

{ "query": { "bool": {
  "must":   [ { "match": { "title": "kafka offset" } } ],  // скоринг
  "filter": [ { "term":  { "status": "published" } } ]      // да/нет, кэшируется
}}}

Поиск как read model

Elasticsearch обычно не источник истины, а read model: данные приходят из основной БД через индексацию (outbox/CDC/батч), поэтому индекс отстаёт — это eventual consistency, и UI должен это учитывать.

Под капотом индекс разбит на шарды и реплики (распределение и отказоустойчивость). Делать ES единственным хранилищем критичных транзакционных данных — рискованно: его сила в поиске и аналитике, а не в строгих гарантиях записи.

Как отвечать на собеседовании

Сильный ответ: объясните инвертированный индекс и роль анализаторов (один и тот же для текста и запроса), упомяните BM25 для релевантности, различите query/filter context и подчеркните, что поиск — это производная read model поверх источника истины с задержкой индексации.

Начните с того, почему задача вообще другая: LIKE '%слово%' отвечает «совпало или нет» и при этом не может использовать индекс из-за ведущего wildcard, а поиск должен найти релевантное и ОТРАНЖИРОВАТЬ. Ранжирование — то, чего у LIKE нет в принципе, и с него стоит начинать ответ.

Инвертированный индекс объясняйте одной фразой с переворотом: обычный индекс ведёт от документа к его словам, инвертированный — от термина к списку документов. Поэтому запрос сводится к чтению готового списка, а не к перебору документов.

Анализаторы — место, где кандидат показывает практику. Скажите, что текст проходит токенизацию, приведение к нижнему регистру, удаление стоп-слов и стемминг, и что ТОТ ЖЕ анализатор обязан применяться к запросу, иначе термины не совпадут. Добавьте, что для русского нужен языковой анализатор, а неправильный mapping — самая частая причина жалобы «поиск ничего не находит».

Релевантность: назовите идею TF-IDF (термин важен, если част в документе и редок в коллекции) и уточните, что современный дефолт — BM25 с насыщением частоты и поправкой на длину документа.

Разница query и filter context — короткий, но выигрышный пункт: текстовый поиск идёт в query и влияет на score, точные ограничения (статус, дата, категория) — в filter, где скоринга нет, а результат кэшируется и работает быстрее.

Завершите архитектурным тезисом: Elasticsearch — не источник истины, а read model, наполняемая из основной БД через outbox/CDC/батч. Отсюда неизбежное отставание индекса (eventual consistency), которое должен учитывать UI, и вывод: держать в ES единственную копию критичных транзакционных данных рискованно.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем