Поиск по сайту — одна из тех вещей, про которые думают в последнюю очередь. Сделали каталог, добавили фильтры, а поиск — «потом докрутим». Потом оказывается, что пользователи вводят «красные кросы» и ничего не находят, потому что в базе написано «красные кроссовки». Или каталог вырос до 50 000 позиций, и каждый запрос кладёт базу на три секунды.

Разберём, как устроен поиск изнутри, когда хватает простых решений и в какой момент стоит смотреть в сторону Elasticsearch.

Как обычно делают поиск на сайте

Самый распространённый вариант — запрос через LIKE в SQL. Выглядит примерно так:

SELECT * FROM products WHERE name LIKE '%кроссовки%'

Это работает. Пока товаров мало, пока запросы короткие и пока база не нагружена другими задачами. При нескольких тысячах записей такой запрос выполняется за миллисекунды. Никаких проблем.

Проблема начинается, когда процент совпадений маленький — база перебирает все строки, не может использовать обычный индекс (B-tree индексы по LIKE с wildcard в начале строки не работают), и запрос становится тяжёлым. При 100 000 записях время ответа может вырасти до 1–3 секунд. При миллионе — хуже.

Вторая проблема — точность. LIKE ищет подстроку буквально. «Кроссовки» не найдёт «кросовки» (опечатка), «кроссовок» (другое склонение) или «sneakers» (синоним). Пользователь не найдёт товар и уйдёт.

Что такое полнотекстовый поиск

Полнотекстовый поиск — другой подход. Перед поиском текст проходит обработку:

Токенизация — текст разбивается на отдельные слова (токены). «Красные кроссовки Nike» → [«красные», «кроссовки», «nike»].

Нормализация — слова приводятся к базовой форме. «Кроссовками», «кроссовок», «кроссовки» — всё становится «кроссовк» (стемминг) или «кроссовка» (лемматизация). Зависит от реализации.

Построение инвертированного индекса — создаётся структура вида «слово → список документов, где оно встречается». Это как индекс в конце книги, только автоматический.

Когда пользователь что-то ищет, его запрос проходит ту же обработку и сопоставляется с индексом. Поиск по индексу — это не перебор всей таблицы, а прямое обращение к нужным записям. Поэтому быстро даже на миллионах документов.

Встроенный полнотекстовый поиск в базах данных

MySQL, PostgreSQL и другие популярные СУБД умеют полнотекстовый поиск без всяких дополнительных инструментов.

В PostgreSQL это выглядит так:

SELECT * FROM products
WHERE to_tsvector('russian', name) @@ to_tsquery('russian', 'кроссовки')

Функция to_tsvector строит вектор из текста с учётом русской морфологии, to_tsquery обрабатывает запрос. Работает быстро, поддерживает ранжирование по релевантности, обрабатывает морфологию.

MySQL тоже поддерживает FULLTEXT индексы:

SELECT *, MATCH(name, description) AGAINST('кроссовки' IN NATURAL LANGUAGE MODE) AS score
FROM products
WHERE MATCH(name, description) AGAINST('кроссовки' IN NATURAL LANGUAGE MODE)
ORDER BY score DESC

Для большинства сайтов — корпоративных, портфолио, небольших магазинов — встроенного полнотекстового поиска хватает. Если у вас корпоративный сайт с несколькими сотнями страниц или интернет-магазин с тысячами товаров — настройте FULLTEXT индекс в PostgreSQL или MySQL, и этого будет достаточно.

Когда встроенного поиска перестаёт хватать

Есть несколько сигналов, что пора смотреть в сторону специализированных решений:

Объём данных. Десятки миллионов документов — полнотекстовый поиск в PostgreSQL начинает проседать. Не сразу, но при сложных запросах с несколькими условиями время ответа растёт.

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

Сложные требования к релевантности. Нужно учитывать популярность товара, персональную историю пользователя, бустить определённые категории, настраивать синонимы через интерфейс без правки кода — встроенный поиск это не умеет.

Фасетный поиск. Это когда одновременно нужны фильтры (цена, бренд, размер), счётчики по каждому фильтру и полнотекстовый поиск. PostgreSQL справится, но медленно при больших объёмах.

Опечатки и нечёткий поиск. «Найк», «Adiddas», «iPhon» — пользователи ошибаются. Специализированные движки умеют нечёткое совпадение (fuzzy matching) из коробки и настраиваются под допустимое расстояние Левенштейна.

Elasticsearch — что это и как работает

Elasticsearch — поисковый движок на основе Apache Lucene. Распределённый, масштабируемый, заточенный именно под поиск и аналитику.

Ключевые концепции:

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

Шарды — индекс делится на части, которые могут распределяться по разным серверам. Так достигается горизонтальное масштабирование.

Анализаторы — цепочки обработки текста. Можно настроить отдельный анализатор для каждого поля: одни поля анализировать с учётом русской морфологии, другие — хранить как есть.

Пример запроса к Elasticsearch:

{
  "query": {
    "multi_match": {
      "query": "красные кроссовки",
      "fields": ["name^3", "description", "tags"],
      "fuzziness": "AUTO"
    }
  }
}

Здесь ^3 означает, что совпадение в поле name весит втрое больше, чем в описании. fuzziness: AUTO включает нечёткий поиск — найдёт «кросовки» и «кросовку».

Elasticsearch умеет многое: агрегации (для фасетных фильтров), геопоиск, поиск по вектору (для семантического поиска с ML), автодополнение, подсветку совпадений в тексте.

Но у него есть цена — в прямом смысле. Elasticsearch требует отдельного сервера (или кластера), хорошего объёма RAM (минимум 2–4 ГБ под JVM), настройки синхронизации данных из основной базы и поддержки.

Альтернативы Elasticsearch

Elasticsearch — не единственный вариант. Есть более простые в эксплуатации решения:

Typesense — open source, написан на C++, потребляет значительно меньше памяти, чем Elasticsearch. Проще в настройке, быстро разворачивается. Хороший выбор для средних проектов, которым нужен нечёткий поиск и фасеты, но нет ресурсов на поддержку Elasticsearch.

MeiliSearch — тоже open source, ориентирован на простоту. Работает быстро, поддерживает опечатки, есть готовые SDK для популярных языков. Подходит для небольших и средних каталогов.

Algolia — облачный сервис. Не нужно разворачивать ничего своего, есть щедрый бесплатный план (10 000 запросов в месяц), отличная документация. Из минусов — данные уходят на внешний сервер, при больших объёмах дорого.

Sphinx Search — старый добрый движок, который до сих пор используется. Хорошо работает с MySQL, прост в настройке для базовых задач. Но развивается медленно.

Как организовать синхронизацию данных

Главная архитектурная задача при использовании отдельного поискового движка — держать индекс актуальным. Добавили товар в базу — он должен появиться в поиске. Изменили цену — изменение должно отразиться.

Три подхода:

Синхронная запись. При каждом сохранении в базу — сразу обновляем индекс. Просто реализовать, данные всегда актуальны. Риск — если Elasticsearch недоступен, операция упадёт.

Очередь сообщений. Изменения в базе отправляются в очередь (RabbitMQ, Kafka, Redis Streams), воркер читает очередь и обновляет индекс. Надёжнее, но сложнее. Есть небольшая задержка между изменением и появлением в поиске.

Периодическая переиндексация. Раз в N минут перестраиваем весь индекс или его часть. Самое простое решение, но с задержкой. Для каталогов, где данные меняются редко — нормально.

Для большинства интернет-магазинов подходит очередь с задержкой в несколько секунд: покупатель не заметит, что только что добавленный товар появился в поиске через 5 секунд, а не мгновенно.

Практическое решение: когда что выбирать

До 10 000 документов, простые требования — LIKE-запрос или FULLTEXT индекс в вашей СУБД. Не усложняйте.

10 000–500 000 документов, нужна морфология и нечёткий поиск — встроенный полнотекстовый поиск PostgreSQL с правильно настроенным анализатором для русского языка. Или MeiliSearch/Typesense, если хочется проще в настройке.

Более 500 000 документов, высокая нагрузка, сложная релевантность — Elasticsearch или Typesense на отдельном сервере. Здесь уже нужен отдельный специалист или опытная команда.

Быстрый старт без инфраструктуры — Algolia. Платите деньгами, а не временем на настройку.

Когда в REEXY собирают требования для интернет-магазина, один из первых вопросов — сколько товаров сейчас и сколько планируется через год. Ответ на него определяет, нужен ли отдельный поисковый движок или хватит решения внутри основной базы. Интернет-магазин от 10 000 ₽ включает базовый поиск; для серьёзного каталога интеграция с поисковым движком обсуждается отдельно.

Индексация для поисковых систем — отдельная история

Есть ещё один вид индексации, который часто путают с поиском по сайту — индексация страниц Гуглом и Яндексом.

Это другой процесс. Поисковые роботы обходят сайт, читают содержимое страниц и добавляют их в свой индекс. Чтобы они делали это правильно и быстро, нужно:

Файл sitemap.xml — карта сайта, список всех страниц с датами обновления. Роботы используют его как ориентир.

Robots.txt — указывает, какие страницы не нужно индексировать (корзина, личный кабинет, страницы пагинации).

Правильные мета-теги — title и description для каждой страницы. Robots-директивы для страниц, которые не должны попасть в поиск.

Скорость загрузки — медленные сайты хуже индексируются. Google прямо говорит, что Core Web Vitals влияют на ранжирование.

Структурированные данные (Schema.org) — разметка, которая помогает поисковикам понять, что на странице: товар, статья, рецепт, организация. Для интернет-магазинов это особенно важно — правильная разметка даёт расширенные сниппеты с ценой и наличием прямо в выдаче.

Ситуация, когда страницы сайта не попадают в индекс Яндекса — довольно распространённая. Причин много: закрытые страницы в robots.txt, дубли из-за параметров URL, медленная загрузка, проблемы с canonical. Это уже задача для SEO-аудита, а не для настройки поиска.

Главное коротко

Поиск по сайту — не одно решение, а спектр. На одном конце — простой LIKE-запрос для блога с 50 статьями. На другом — Elasticsearch-кластер с настроенной релевантностью, синонимами и ML-ранжированием для маркетплейса.

Большинство сайтов находятся посередине и вполне обходятся встроенным полнотекстовым поиском базы данных с нормально настроенным русским анализатором. Не нужно разворачивать Elasticsearch там, где его не требует масштаб — это лишняя инфраструктура, деньги и точка отказа.

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