Почему не 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 единственную копию критичных транзакционных данных рискованно.