Legan Studio
Все статьи
~ 6 мин чтения

Полнотекстовый поиск: Meilisearch vs Elastic vs OpenSearch

Сравниваем поисковые движки для сайта в 2026: Meilisearch, Typesense, Elasticsearch, OpenSearch — типичные сценарии, лимиты, релевантность, стоимость.

  • веб
  • разработка
  • поиск

SELECT * WHERE name LIKE '%запрос%' — это не поиск, это медленное и неудобное «нашли что-то с подстрокой». Настоящий поиск понимает опечатки, синонимы, морфологию русского, релевантность, фасеты. Для этого нужен отдельный движок. Разбираем, что выбрать в 2026.

Когда нужен поисковый движок

Размер каталога / контентаРешение
100-1000 записей, простые запросыPostgres FTS (tsvector)
1k-100k, нужны опечатки и фасетыMeilisearch / Typesense
100k-10M, сложная релевантностьOpenSearch / Elasticsearch
> 10M, аналитика и логиElasticsearch / OpenSearch
Семантический поиск (по смыслу)pgvector + LLM-embeddings

Часто на сайте достаточно Postgres FTS — не лезьте в Elasticsearch на 5000 товарах.

Postgres FTS — самое недооценённое

Встроенный полнотекст в Postgres — отлично работает для простых сайтов. Поддерживает русскую морфологию через pg_russian или pg_morph.

ALTER TABLE products ADD COLUMN search tsvector
  GENERATED ALWAYS AS (
    setweight(to_tsvector('russian', coalesce(name, '')), 'A') ||
    setweight(to_tsvector('russian', coalesce(description, '')), 'B')
  ) STORED;

CREATE INDEX idx_products_search ON products USING GIN(search);

SELECT id, name, ts_rank(search, q) AS rank
FROM products, plainto_tsquery('russian', 'красное платье') q
WHERE search @@ q
ORDER BY rank DESC
LIMIT 20;

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

Meilisearch — простой и быстрый

Российские разработчики любят за «поставил и работает». Написан на Rust, ставится одним бинарником.

docker run -d -p 7700:7700 -v meili_data:/meili_data \
  -e MEILI_MASTER_KEY=secret getmeili/meilisearch:v1.10

Индексирование:

import { MeiliSearch } from "meilisearch";
const client = new MeiliSearch({ host: "http://localhost:7700", apiKey: "secret" });
const index = client.index("products");

await index.addDocuments([
  { id: 1, name: "Платье красное", price: 4990, category: "Одежда" },
  { id: 2, name: "Брюки чёрные", price: 3490, category: "Одежда" },
]);

Поиск:

const results = await index.search("плате крас", {
  filter: ["category = 'Одежда'", "price < 6000"],
  facets: ["category", "brand"],
  limit: 20,
});

Опечатки — из коробки («плате» найдёт «платье»). Синонимы — настраиваются:

await index.updateSettings({
  synonyms: { "куртка": ["парка", "анорак"] },
  stopWords: ["и", "в", "на"],
});

Минусы: не подходит для миллионов документов с тяжёлой аналитикой. Не настолько гибок, как Elastic.

Typesense — альтернатива Meilisearch

Очень похож, тоже C++. Чуть быстрее на больших объёмах, чуть менее удобный API. В РФ менее популярен.

docker run -p 8108:8108 -v ts_data:/data \
  typesense/typesense --api-key=xyz --data-dir=/data

Хорош там, где много фасетов и пользователь крутит фильтры в реальном времени.

Elasticsearch / OpenSearch

Сильнее всех, сложнее всех. Elasticsearch с 2021 закрыл лицензию (SSPL), OpenSearch — форк от AWS на Apache 2.0. Технически почти идентичны.

docker run -d -p 9200:9200 -e "discovery.type=single-node" \
  -e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=Strong123!" \
  opensearchproject/opensearch:2.15.0
import { Client } from "@opensearch-project/opensearch";
const client = new Client({ node: "https://localhost:9200" });

await client.indices.create({
  index: "products",
  body: {
    mappings: {
      properties: {
        name: { type: "text", analyzer: "russian" },
        price: { type: "integer" },
        category: { type: "keyword" },
      },
    },
  },
});

Поиск с релевантностью, фасетами, suggesters, geo-фильтрами, агрегациями. Можно строить аналитику логов, ELK-стек, дашборды.

Но: жрёт RAM (минимум 4 ГБ под небольшой кластер), сложно администрировать, требует понимания шардирования.

Сравнение

КритерийPostgres FTSMeilisearchTypesenseOpenSearch
Установкауже есть1 бинарник1 бинарниксложная, JVM
RAM минимум256 МБ512 МБ512 МБ4 ГБ
Опечаткинетдадада
Морфология русскогода (с расширением)хорошосреднеда (analyzer russian)
Фасетырукамидадада
ГеопоискPostGISбазовыйдатоп
Сложные запросысреднесреднесреднетоп
Объём данныхдо 10Мдо 1-5Мдо 5-10Мот 10М до миллиардов
Кривая обученияпологаяпологаяпологаякрутая
Лицензияopen sourceMITApache 2.0Apache 2.0

Семантический поиск

Когда пользователь ищет «дешёвая куртка для зимы», обычный полнотекст ищет точные слова. Семантический — понимает смысл.

Решение — embeddings. Преобразуете каждый товар в вектор (через OpenAI Embeddings, российские аналоги вроде ai.gigachat или sbert-models локально), храните в pgvector.

CREATE EXTENSION vector;
ALTER TABLE products ADD COLUMN embedding vector(1536);
CREATE INDEX ON products USING hnsw (embedding vector_cosine_ops);
SELECT id, name FROM products
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 20;

В реальности комбинируют: Meilisearch для лексического поиска + pgvector для семантического + переранжирование результатов. Это hybrid search.

Индексация

Главная боль — синхронизация между основной БД и поисковым движком. Варианты:

  1. При записи: после INSERT/UPDATE сразу шлёте в индекс. Просто, но добавляет latency и может сломать запись, если индекс лежит.
  2. Очередь: пишете задачу в Redis/RabbitMQ, воркер индексирует асинхронно. Надёжнее, но сложнее.
  3. CDC (change data capture): Debezium слушает WAL Postgres и пишет в Meili/OS. Идеально для больших систем, сложно настроить.
  4. Полная переиндексация по cron: раз в час дамп БД → индекс. Дёшево и сердито для небольших каталогов.

Для большинства сайтов — вариант 2 (очередь) с fallback на вариант 4 (раз в сутки полная сверка для надёжности).

Фильтры с подсчётом «сколько товаров в каждой категории» — обязательны для e-commerce.

const results = await index.search("платье", {
  facets: ["brand", "color", "size"],
  filter: ["price 1000 TO 5000"],
});
// results.facetDistribution = { brand: { Mango: 12, Zara: 8 }, color: { ... } }

В Meilisearch и Typesense — из коробки. В OpenSearch — через aggregations. В Postgres — самописно через GROUP BY.

Подсветка совпадений

Все три движка умеют highlight:

await index.search("платье красное", {
  attributesToHighlight: ["name", "description"],
  highlightPreTag: "<mark>",
  highlightPostTag: "</mark>",
});

В UI выводите подсвеченный текст — пользователь видит, что совпало с его запросом.

Стоимость

РешениеХостингМесяц
Postgres FTSв основной БД0
Meilisearch self-hostedVPS 2 ГБ500-1000 ₽
Meilisearch Cloudmanaged$30-100
Typesense Cloudmanaged$30-300
Elastic Cloudmanaged$95-500
OpenSearch self-hostedVPS 4-8 ГБ2-5 тыс. ₽

Для российских проектов чаще self-hosted — оплата зарубежных managed сложна.

Итого

Для большинства сайтов с каталогом 1-100 тыс. записей — Meilisearch. Дешёво, быстро, простой API, хорошая поддержка русского. Postgres FTS — если данных мало и не нужны опечатки. OpenSearch — когда нужна аналитика, миллионы документов, сложные запросы, и есть человек, который умеет его готовить.

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

Можно ли использовать Postgres FTS вместо отдельного движка?

Да, для каталогов до десятков тысяч записей и простых запросов. Постгрес умеет морфологию русского (через pg_russian), фасеты можно собирать GROUP BY, ранжирование ts_rank. Минусы: нет нативных опечаток, ограниченная гибкость, на больших объёмах GIN-индекс становится тяжёлым. Если каталог растёт — выносите в Meilisearch.

Meilisearch или Typesense?

Очень похожи. Meilisearch — чуть проще API, лучше документация на русском, российские разработчики чаще его выбирают. Typesense — чуть быстрее на 5+ млн документов, лучше геопоиск. Если сомневаетесь — берите Meilisearch, на типичном e-commerce разницы не заметите.

Elastic или OpenSearch?

OpenSearch — open source форк, без ограничений лицензии, развивается AWS и сообществом. Elastic с 2021 — SSPL, не Apache. Для коммерческого использования и self-hosted предпочтителен OpenSearch. Совместимость кода ~95%, миграция в обе стороны несложная.

Как обновлять индекс при изменении товара?

Лучшая практика — асинхронно через очередь. После UPDATE products пишете задачу в BullMQ/RabbitMQ, воркер берёт и обновляет в Meili/OS. Время от изменения до видимости в поиске — секунды, при сбое индексатора основная БД не страдает. Плюс раз в сутки cron делает полную сверку, чтобы поймать пропущенные.

Сколько RAM нужно для Meilisearch на 100 тыс. товаров?

Около 1-2 ГБ оперативки и 1-2 ГБ диска. Это VPS за 500-1000 ₽/мес. Запросы в десятки миллисекунд. Для 1 млн товаров — 4-8 ГБ RAM. Для миллионов — задумайтесь о Typesense с шардированием или OpenSearch.

Можно ли искать по PDF и Word документам?

Сначала извлечь текст: pdf-parse, mammoth для DOCX, tesseract для сканов. Потом обычное индексирование текста. OpenSearch имеет ingest-pipeline с attachment processor, который сам это умеет. В Meilisearch — на стороне приложения.

Стоит ли делать семантический поиск через embeddings?

Если у вас контент-сайт (статьи, документация, FAQ) — да, hybrid search заметно улучшает релевантность. Для e-commerce — обычно избыточно, пользователи ищут конкретные слова и фильтруют. Embeddings дают +10-20% точности и стоят нагрузки на GPU/API. Сначала попробуйте обычный полнотекст, если запросы реально страдают на смысловом уровне — добавляйте семантику.