Полнотекстовый поиск в PostgreSQL: нужен ли Elasticsearch
Практика3 минРедакция Innovialabs
Коротко
Большинству сайтов хватает PostgreSQL: tsvector с GIN-индексом даёт релевантный поиск по словам, pg_trgm прощает опечатки, а Elasticsearch окупается только при высокой нагрузке, сложном ранжировании или богатом фасетном поиске.
Короткий ответ
На старте Elasticsearch, скорее всего, не нужен. В PostgreSQL есть встроенный полнотекстовый поиск: он разбивает текст на нормализованные слова (tsvector), сопоставляет их с запросом (tsquery), ранжирует результаты и работает быстро благодаря GIN-индексу. Расширение pg_trgm добавляет нечёткое сравнение для опечаток и частей слов. Вместе они закрывают большинство каталогов, блогов, баз знаний и админок — без второй системы, которую надо синхронизировать.
Шаг 1. Колонка для поиска
Генерируемая колонка обновляет поисковый вектор автоматически. Веса поднимают совпадения в заголовке выше совпадений в тексте.
ALTER TABLE articles
ADD COLUMN search tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('russian', coalesce(title, '')), 'A') ||
setweight(to_tsvector('russian', coalesce(body, '')), 'B')
) STORED;
CREATE INDEX articles_search_idx ON articles USING GIN (search);
'russian' — встроенная конфигурация со стеммингом: «статьи» находит «статья». Для английского — 'english'.
Для узбекского встроенной конфигурации нет. Используйте 'simple' (нижний регистр без стемминга) и удаляйте варианты апострофа, чтобы o‘zbek, o'zbek и ozbek совпадали:
to_tsvector('simple', regexp_replace(coalesce(title_uz, ''), '[‘’ʻʼ`'']', '', 'g'))
Ту же нормализацию применяйте к запросу пользователя.
Шаг 2. Запрос и ранжирование
websearch_to_tsquery понимает то, что пользователи реально вводят: обычные слова, фразы в кавычках и - для исключения слова.
SELECT id, title,
ts_rank(search, q) AS rank,
ts_headline('russian', body, q, 'MaxFragments=2') AS snippet
FROM articles,
websearch_to_tsquery('russian', 'настройка сервера') AS q
WHERE search @@ q
ORDER BY rank DESC
LIMIT 20;
@@отбирает совпавшие строки и использует GIN-индекс.ts_rankсортирует по релевантности с учётом весов.ts_headlineстроит фрагмент с подсветкой слов. Он относительно дорогой, поэтому вызывайте его только для строк, которые показываете.
Для поиска по префиксу, как в подсказках при вводе, используйте to_tsquery('russian', 'настр:*').
Шаг 3. Опечатки и частичные совпадения через pg_trgm
Полнотекстовый поиск сравнивает целые нормализованные слова, поэтому «postgers» не найдёт «postgres». Триграммы решают это, сравнивая фрагменты из трёх букв.
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX articles_title_trgm_idx ON articles USING GIN (title gin_trgm_ops);
SELECT id, title, similarity(title, 'postgers') AS sim
FROM articles
WHERE title % 'postgers'
ORDER BY sim DESC
LIMIT 10;
%возвращает строки выше порога похожести (pg_trgm.similarity_threshold).- Тот же индекс ускоряет запросы
ILIKE '%текст%'. - Для длинных полей
word_similarityи оператор<%сравнивают запрос с наиболее похожим фрагментом текста.
Частая связка: сначала полнотекстовый поиск, а если он ничего не нашёл — триграммный поиск по заголовкам с подсказкой «Возможно, вы имели в виду».
Частые ошибки
- Вызов
to_tsvectorвWHEREбез подходящего индекса — каждый запрос превращается в полный перебор. - Разные конфигурации поиска при индексации и при запросе.
ILIKE '%слово%'на больших таблицах без триграммного индекса.- Забытый
coalesce: одно полеNULLпревращает весь склеенный вектор вNULL.
Когда отдельный поисковый движок оправдан
| Задача | PostgreSQL | Elasticsearch или OpenSearch |
|---|---|---|
| Поиск по словам со стеммингом | Да | Да |
| Устойчивость к опечаткам | pg_trgm | Встроено, гибко |
| Ранжирование | ts_rank, проще | Релевантность BM25, бусты, function score |
| Фасеты по многим полям на больших данных | Возможно, но тяжело | Сильная сторона |
| Автодополнение под высокой нагрузкой | Возможно | Специальные типы полей |
| Синонимы, много языков | Словари, больше ручной работы | Богатая цепочка анализаторов |
| Стоимость эксплуатации | Ничего дополнительно | Отдельный кластер и синхронизация |
Переходите на отдельный движок, когда поиск становится ключевой функцией продукта: большой каталог с множеством фильтров, требовательная настройка релевантности или поисковые запросы, которые начинают нагружать основную базу. До этого PostgreSQL сохраняет архитектуру простой.