Сравнение полнотекстового поиска PostgreSQL и Elasticsearch
Одна база данных или полноценный стек поиска
Основной спор не в том, может ли PostgreSQL искать текст или может ли Elasticsearch хранить документы. Оба могут. Интересный вопрос заключается в том, где должна находиться сложность поиска.
Поиск по полному тексту в PostgreSQL существует внутри транзакционной реляционной базы данных с использованием tsvector, tsquery, словарей, ранжирования и GIN-индексов. Elasticsearch — это распределённый поисковый и аналитический движок, построенный на Lucene, с анализаторами, оценкой BM25, масштабированием на основе шардов, агрегациями и индексацией в режиме, близком к реальному времени.

Это разные операционные философии, а не просто разные списки функций.
Если вы сопоставляете этот выбор со хранением данных, конвейерами и операциями, этот обзор инфраструктуры данных дает более широкий контекст системы.
О чем на самом деле это сравнение
На низком уровне обе системы опираются на идеи инвертированных индексов, но упаковывают их совершенно по-разному. PostgreSQL рекомендует GIN в качестве предпочтительного типа индекса для поиска по тексту и описывает его как инвертированный индекс над лексемами в значениях tsvector. Elasticsearch анализирует поля типа text и индексирует их для полнотекстового поиска, а затем распределяет эти индексы по шардам и узлам для масштабирования. На практике PostgreSQL ощущается как поиск, встроенный в вашу прикладную базу данных, в то время как Elasticsearch ощущается как специализированная поисковая платформа со своим собственным runtime, жизненным циклом и моделью масштабирования.
Это сравнение в основном касается нативного поиска по полному тексту PostgreSQL и очень распространенного вспомогательного средства pg_trgm для приблизительного (fuzzy) сопоставления. Этот объем важен, потому что более широкая экосистема PostgreSQL со временем становится все более ориентированной на поиск. Расширения, такие как RUM, добавляют более богатое поведение индексов для поиска фраз и сканирования, ориентированного на ранжирование, в то время как PGroonga расширяет PostgreSQL еще одним путем индексации полного текста. Это не делает нативный PostgreSQL равным Elasticsearch, но это означает, что граница менее статична, чем предполагают многие старые сравнения.
Моя субъективная рамка проста. Поиск обычно является функцией, пока не становится поверхностью продукта. PostgreSQL имеет тенденцию побеждать, пока поиск остается просто функцией. Elasticsearch имеет тенденцию побеждать, когда поиск становится тем, что пользователи оценивают в первую очередь. Это меньше связано с названиями брендов и больше с тем, где разрешено жить логике релевантности, политике индексирования и операционной боли.
Как работает поиск по полному тексту в PostgreSQL
Поиск по полному тексту в PostgreSQL начинается с преобразования сырого текста в лексемы. to_tsvector токенизирует текст, нормализует его через настроенные словари, отбрасывает стоп-слова и сохраняет выжившие лексемы с их позициями. setweight позволяет маркировать лексемы из разных частей документа, таких как заголовок, аннотация и тело, чтобы эти части могли влиять на ранжирование по-разному. PostgreSQL также поддерживает несколько предустановленных конфигураций языков и позволяет создавать пользовательские конфигурации с парсерами и словарями.
Если вы хотите компактную SQL-справку при реализации этих паттернов, эта шпаргалка по PostgreSQL и эта шпаргалка по SQL с наиболее полезными командами являются практическими компаньонами.
Типичный паттерн для продакшена — это созданный столбец tsvector плюс GIN-индекс. Документация PostgreSQL прямо указывает, что практический поиск по тексту обычно требует индекса, и явно показывает созданный столбец, подающий данные в GIN-индекс. Этот паттерн позволяет избежать повторного вычисления to_tsvector во время проверки и сохраняет поверхность запроса чистой.
alter table posts
add column search_vector tsvector
generated always as (
setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(summary, '')), 'B') ||
setweight(to_tsvector('english', coalesce(body, '')), 'D')
) stored;
create index posts_search_idx
on posts using gin (search_vector);
select id,
title,
ts_rank_cd(
search_vector,
websearch_to_tsquery('english', '"query planner" -mysql')
) as rank
from posts
where search_vector @@ websearch_to_tsquery('english', '"query planner" -mysql')
order by rank desc
limit 20;
Со стороны запросов PostgreSQL предоставляет вам несколько парсеров, потому что ввод пользователя грязнее, чем признают инженерные блоги. to_tsquery явный и мощный. phraseto_tsquery сохраняет порядок слов с помощью оператора <->. websearch_to_tsquery принимает ввод, похожий на поисковую систему, понимает заключенные в кавычки фразы, OR и отрицание -, и никогда не вызывает синтаксических ошибок на сыром вводе пользователя. PostgreSQL также поддерживает префиксное сопоставление, прикрепляя * к лексеме в to_tsquery.
Ранжирование — это область, где нативный PostgreSQL показывает как свою силу, так и свой потолок. ts_rank и ts_rank_cd могут использовать частоту, близость и структурные веса, и модель взвешивания удивительно хороша для многих задач поиска в приложениях. В то же время собственная документация PostgreSQL отмечает, что ранжирование может быть дорогостоящим, и встроенные функции ранжирования не используют глобальную информацию. Это тихое, но важное ограничение нативного поиска по полному тексту PostgreSQL. Он может ранжировать, но релевантность не является центром тяжести движка.
Когда PostgreSQL достаточно для поиска по полному тексту
PostgreSQL достаточно чаще, чем хотели бы специализированные поисковые вендоры. Он особенно привлекателен, когда поиск остается очень близко к транзакционным строкам, джойнам, разрешениям и свежим записям. Модель MVCC в PostgreSQL обеспечивает транзакционную согласованность и чтение на основе снимков, поэтому та же база данных, которая принимает запись, может ответить на поиск без окна обновления в стиле Elasticsearch. Когда поле поиска действительно означает «найти записи внутри приложения, которое я только что отредактировал», это свойство важнее, чем глянцевые демо-версии релевантности.
Также этого достаточно, когда SQL-фильтрация составляет половину функциональности. Фильтры статуса, изоляция арендаторов (tenant isolation), состояния публикации, временные метки и реляционные джойны часто имеют такое же значение, как и релевантность ключевых слов в системах линейного бизнеса. В таких случаях поиск по полному тексту в PostgreSQL ведет себя как еще один индексированный предикат в плане реляционного запроса, а не как отдельная платформа, которую нужно кормить и поддерживать в тепле. Это скучная архитектура, и скучное часто бывает правильным способом для скорости.
Как работает Elasticsearch как поисковый движок
Elasticsearch представляет себя совершенно иначе. Собственная документация определяет его как распределенный поисковый и аналитический движок, масштабируемое хранилище данных и векторную базу данных, построенную на Apache Lucene, оптимизированную для скорости и релевантности в масштабах продакшена и работающую в режиме, близком к реальному времени. Elasticsearch разбивает каждый индекс на шарды, реплицирует эти шарды и распределяет их по узлам для увеличения емкости индексирования и запросов. Вот почему Elasticsearch редко бывает «просто индексом». Это архитектура кластера.
Под капотом основная тяжесть лежит на анализаторах. Анализатор Elasticsearch — это композиция символьных фильтров, токенизаторов и фильтров токенов. Существуют встроенные анализаторы, языковые анализаторы и пользовательские анализаторы, а обработка синонимов является частью анализа первого класса. Это означает, что поведение поиска зависит не только от запроса. Это также зависит от того, как нормализуются как документы, так и запросы, прежде чем начнется даже оценка.
Для практического API-справочника при реализации этих паттернов эта шпаргалка по Elasticsearch собирает основные команды и операционные сокращения.
PUT posts
{
"mappings": {
"properties": {
"title": { "type": "text" },
"summary": { "type": "text" },
"body": { "type": "text" },
"tags": { "type": "keyword" }
}
}
}
GET posts/_search
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "query planner",
"fields": ["title^3", "summary^2", "body"],
"type": "best_fields"
}
}
],
"must_not": [
{ "match": { "body": "mysql" } }
]
}
},
"aggs": {
"by_tag": {
"terms": {
"field": "tags"
}
}
},
"highlight": {
"fields": {
"body": {}
}
}
}
Query DSL — это место, где Elasticsearch начинает ощущаться как нативный поисковый движок, а не как база данных. bool комбинирует условия с помощью must, should, filter и must_not. multi_match может искать по многим полям с бустами полей и различными режимами выполнения, такими как best_fields, most_fields, cross_fields, phrase и bool_prefix. Агрегации, подсветка и фильтры могут находиться рядом с основным запросом в том же запросе. BM25 является моделью сходства по умолчанию.
Модель свежести также явна. Elasticsearch работает в режиме, близком к реальному времени, а не обеспечивает мгновенную поисковую согласованность. Недавние операции становятся видимыми для поиска, когда обновление открывает новый сегмент, и по умолчанию это обновление происходит каждую секунду для индексов, которые недавно были запрошены. Документация Elastic также предупреждает, что обновления ресурсоемки и рекомендует ждать периодических обновлений или использовать refresh=wait_for, когда рабочему процессу нужна видимость поиска «запись-потом-чтение». Это совершенно другая контрактная модель по сравнению с PostgreSQL.
Почему Elasticsearch обычно лучше ранжирует сложный поиск
Это самая глубокая техническая причина, по которой многие команды в конечном итоге переходят от поиска по полному тексту PostgreSQL к Elasticsearch. Встроенные функции ранжирования PostgreSQL не используют глобальную информацию, в то время как Elasticsearch по умолчанию использует BM25 и предоставляет настройки сходства для конкретных полей, анализаторы, формы запросов для нескольких полей и DSL поиска, разработанный вокруг настройки релевантности. Как только поиск становится меньше о «совпало ли это» и больше о «почему эти десять результатов победили», у Elasticsearch обычно больше пространства для выражения.
У Elasticsearch также есть явная предвзятость в сторону денормализованных документов. Документация по полям джойнов явно предупреждает против моделирования нескольких уровней отношений для репликации реляционной схемы и рекомендует денормализацию для лучшей производительности поиска. Этот дизайнерский выбор объясняет много сил и фрустраций Elasticsearch. Он не пытается быть PostgreSQL с более быстрым LIKE. Он пытается быть поисковым движком, который может быстро оценивать и извлекать большие коллекции документов.
Поиск по полному тексту PostgreSQL против Elasticsearch на реальных функциях
Толерантность к опечаткам — это место, где две системы резко расходятся. Elasticsearch предоставляет фазовые запросы (fuzzy queries) на основе расстояния редактирования Левенштейна и также предлагает специальные типы полей для предложений и ввода на ходу. Нативный поиск по полному тексту PostgreSQL сам по себе не толерантен к опечаткам. Обычный ответ PostgreSQL — pg_trgm, который добавляет операторы сходства и поддержку индексов для триграммного сходства, LIKE и ILIKE. Это работает хорошо, но это стратегия композиции, а не один интегрированный набор функций поискового движка.
Подсветка существует в обеих стеках, но детали реализации рассказывают историю. PostgreSQL использует ts_headline, который может возвращать полезные сниппеты, однако документация отмечает, что он использует оригинальный документ, может быть медленным и не гарантирован безопасным для прямой вставки на веб-страницы. Подсветка в Elasticsearch может использовать смещения постингов или векторы терминов, что особенно ценно для больших полей, поскольку это позволяет избежать повторного анализа полного текста для каждого запроса подсветки. Кратко: PostgreSQL может подсвечивать, в то время как Elasticsearch построен для подсветки в масштабе.
Фасеты и поисковая аналитика — это еще одна линия разлома. Elasticsearch рассматривает агрегации как часть модели поиска первого класса, с метрическими, групповыми и конвейерными агрегациями, доступными непосредственно в ответе поиска. PostgreSQL, очевидно, может агрегировать, потому что это SQL, но как только подсчитанные группы, гистограммы и композитная поисковая аналитика становятся частью самого поискового продукта, Elasticsearch ощущается гораздо более нативным. Разница не в возможностях в принципе. Это вопрос того, сколько эргономики запросов и политики производительности движок посвящает этой нагрузке.
Автодополнение следует той же схеме. PostgreSQL может выполнять префиксное сопоставление в to_tsquery, что полезно и часто достаточно для внутренних инструментов. Elasticsearch идет дальше с полями search_as_you_type, которые автоматически создают несколько проанализированных подполей для завершения префиксов и инфиксов, плюс предложения завершения, созданные специально для быстрых подсказок. Этот разрыв незначителен на панели администратора и значителен на пользовательской поверхности открытия.
Операционные расходы важнее, чем скриншоты бенчмарков
Искушающий вопрос поискового движка: «Быстрее ли Elasticsearch, чем PostgreSQL для поиска?» Честный ответ: «Для какой формы поиска?» Elasticsearch спроектирован вокруг шардов, реплик, пакетной индексации, политики обновлений и управления жизненным циклом. Собственная документация Elastic по продакшену глубоко погружается в стратегию шардов, размер пакетных запросов, пропускную способность индексирования, интервалы обновлений и ILM. PostgreSQL избегает второго кластера, но обслуживание GIN не бесплатно. Документация PostgreSQL предупреждает, что вставки GIN могут быть медленными, что очистка ожидающего списка может вызывать колебания времени отклика и что стратегия autovacuum имеет значение, если индекс обновляется интенсивно.
Это делает историю производительности более нюансированной, чем признают большинство сравнительных постов. У Elasticsearch обычно есть больше запаса для больших лексических поисков top-N, фасетирования, автодополнения и распределенного объема чтения, потому что его архитектура посвящена этим задачам. PostgreSQL часто ощущается быстрее для реляционных прикладных запросов со строгими требованиями к свежести, потому что нет второго хранилища данных, нет границы обновления и нет пути синхронизации для отладки. Победителем обычно становится форма рабочей нагрузки, а не скриншот бенчмарка. Это частично умозрительное рассуждение, но оно напрямую следует из транзакционной модели MVCC PostgreSQL и шардной архитектуры Elasticsearch, работающей в режиме, близком к реальному времени.
Должны ли транзакционные данные и поисковые индексы жить в одной системе? Когда релевантность поиска умеренна, но свежость, разрешения и транзакционная истинность критичны, дизайн одной системы имеет очевидные преимущества. Когда качество поиска, фасетирование, политика синонимов, толерантность к опечаткам и горизонтальное масштабирование поиска становятся первоклассными заботами продукта, вторая система начинает выглядеть оправданной. Собственные рекомендации Elasticsearch по размеру шардов говорят, что не существует стратегии «одна подходит всем», и рекомендуют проводить бенчмаркинг производственных данных на производственном оборудовании. Это предложение идеально отражает компромисс. Elasticsearch покупает запас за счет того, что просит вас управлять более специфичной для поиска архитектурой.
Практический вердикт
Поиск по полному тексту PostgreSQL удивительно часто выигрывает первые 80 процентов. Он поддерживает токенизацию, стоп-слова, стемминг, запросы фраз, веса, ранжирование, подсветку, сгенерированные поисковые векторы, GIN-индексы и вспомогательные средства сходства на основе триграмм. В сочетании с транзакционной семантикой PostgreSQL он дает многим приложениям поисковый стек, который прост, актуален и близок к данным. Для бэкофисов SaaS, внутренних инструментов, умеренных контентных сайтов и нативного поиска приложений эту комбинацию трудно игнорировать.
Elasticsearch становится убедительным, когда поиск — это не просто фильтр, а поверхность продукта. BM25 по умолчанию, пользовательские анализаторы, фильтры синонимов, фазовые запросы, ранжирование по нескольким полям, агрегации, опции автодополнения, стратегии подсветки больших полей и распределенное шардное масштабирование — это не побочные функции. Это причина существования движка. Вот почему сравнения Elasticsearch, сосредоточенные только на сырой задержке, обычно упускают суть. Большая разница заключается в том, сколько логики поискового продукта движок готов взять на себя.
Самая чистая ментальная модель такова. Поиск по полному тексту PostgreSQL отлично подходит, когда поиск принадлежит базе данных. Elasticsearch отлично подходит, когда база данных должна питать поисковую платформу. Большинство команд чрезмерно фокусируются на скорости и недооценивают режимы отказа. Реальная торговля заключается в том, где разрешено находиться настройке релевантности, свежести данных и операционной сложности.