Поиск по сайту — одна из тех вещей, про которые думают в последнюю очередь. Сделали каталог, добавили фильтры, а поиск — «потом докрутим». Потом оказывается, что пользователи вводят «красные кросы» и ничего не находят, потому что в базе написано «красные кроссовки». Или каталог вырос до 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 там, где его не требует масштаб — это лишняя инфраструктура, деньги и точка отказа.
Если сомневаетесь — начните с простого решения и добавляйте сложность, когда почувствуете конкретную боль: медленные запросы, плохое качество результатов, недовольные пользователи. Преждевременная оптимизация поиска — один из частых способов потратить время не туда.