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

Полнотекстовый поиск — технология поиска по содержимому текстовых документов с учётом морфологии языка, синонимов, стоп-слов и ранжирования результатов по релевантности. В отличие от простого поиска по подстроке (LIKE '%слово%'), полнотекстовый поиск понимает, что «бежать», «бегу» и «бежали» — формы одного слова, умеет находить документы с несколькими ключевыми словами в разных частях текста и упорядочивает результаты по близости к запросу.

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

Ядро полнотекстового поиска — инвертированный индекс (inverted index). При индексировании каждый документ разбивается на токены (слова), которые нормализуются (лемматизация или стемминг): «бегает», «бежал» → «бег». Получается словарь: ключ — нормализованный токен, значение — список документов, в которых он встречается, с позициями и частотами.

При поиске запрос проходит те же преобразования, и система ищет документы, содержащие нормализованные термины запроса. Ранжирование вычисляется по алгоритмам TF-IDF или BM25: документ, где слово встречается часто (TF) и само слово редко (IDF), ранжируется выше.

Полнотекстовый поиск в PostgreSQL

PostgreSQL содержит встроенный полнотекстовый поиск без дополнительных расширений:

  • tsvector — тип для хранения нормализованного представления документа: to_tsvector('russian', 'Быстрая лиса прыгнула') → лексемы с позициями.
  • tsquery — тип для поисковых запросов: to_tsquery('russian', 'быстрый & лиса').
  • Оператор @@ проверяет соответствие: tsvector @@ tsquery.
  • Функция ts_rank() вычисляет релевантность.
  • Функция ts_headline() формирует сниппет с подсветкой найденных слов.

Для ускорения поиска создаётся GIN-индекс: CREATE INDEX ON documents USING GIN(to_tsvector('russian', content));. Или хранится готовый вектор в отдельном столбце с триггером обновления.

Конфигурации языка и словари

PostgreSQL поддерживает языковые конфигурации, определяющие стоп-слова (предлоги, союзы, не несущие смысловой нагрузки) и стемминг. Для русского языка используется конфигурация 'russian'. Словари подключаются в порядке очереди: простой (without stemming), ispell (словарь форм), snowball (алгоритмический стеммер). Кастомные словари позволяют добавлять синонимы и специализированную терминологию.

Elasticsearch и OpenSearch

Elasticsearch — специализированная поисковая система на основе Apache Lucene. Изначально проектировалась для полнотекстового поиска, а не для транзакционных данных. Возможности, выходящие за рамки PostgreSQL:

  • Фасетный поиск с агрегациями (counts, ranges, terms).
  • Fuzzy search — поиск с учётом опечаток (расстояние Левенштейна).
  • Горизонтальное масштабирование через шардирование из коробки.
  • Phrase search с учётом порядка слов и расстояния между ними.
  • Rich analyzer ecosystem: языковые анализаторы, tokenizer-ы, char_filter-ы.
  • Percolator — «обратный поиск»: запрос соответствует документу, а не наоборот.

Алгоритм ранжирования BM25

BM25 (Best Match 25) — современный стандарт ранжирования, пришедший на смену TF-IDF. Учитывает длину документа (нормализация): частое слово в коротком документе ценится выше, чем в длинном. Параметр k1 контролирует насыщение TF (по умолчанию 1.2), b — нормализацию длины (0.75). Elasticsearch использует BM25 по умолчанию с версии 5.0. PostgreSQL реализует собственный алгоритм через ts_rank, близкий к TF-IDF.

Морфологический анализ для русского языка

Русский язык с его развитой морфологией требует более сложного лингвистического анализа, чем английский. Стемминг (алгоритм Snowball/Porter для русского) отсекает окончания приближённо. Лемматизация — точнее: приводит к словарной форме. Для PostgreSQL существует расширение pg_stemmer и интеграция с Mystem (Яндекс). Elasticsearch поддерживает морфологию через плагин analysis-snowball и community-плагины для русского.

Гибридный поиск: fulltext + семантика

Полнотекстовый поиск точен для ключевых слов, но не понимает смысла. Семантический поиск (pgvector, Elasticsearch dense_vector) понимает смысл, но плохо работает с точными совпадениями. Современные системы комбинируют оба подхода через RRF (Reciprocal Rank Fusion) — объединение ранжированных списков. Такой гибридный поиск обеспечивает лучшее качество для большинства пользовательских запросов.

Частые вопросы

  • Чем полнотекстовый поиск отличается от LIKE '%слово%'?

    LIKE '%слово%' — простое посимвольное совпадение, не использует индекс (полный скан таблицы), не понимает морфологии и не ранжирует результаты. Полнотекстовый поиск строит инвертированный индекс, нормализует слова (бежал = бег), применяет стоп-слова, вычисляет релевантность и работает на порядки быстрее на больших данных.

  • Когда выбрать Elasticsearch вместо PostgreSQL для поиска?

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

  • Как реализовать поиск с учётом опечаток?

    PostgreSQL: расширение pg_trgm создаёт триграммный индекс, позволяющий искать похожие строки по расстоянию Левенштейна (similarity function). Elasticsearch: параметр fuzziness в match-запросе задаёт допустимое число ошибок (AUTO, 0, 1, 2). Для русских слов fuzziness=1 исправляет одну ошибку в слове длиной 5+ символов.

Не хватает деталей?

Напишите, что уточнить по теме «полнотекстовый поиск» — это помогает улучшать материал и подсказывает, какие термины добавить дальше. Email необязателен: укажите, если хотите ответ только для вас (мы не шлём рассылки).

Поделиться