Статьи
AI-статья 17 мин 771

Elasticsearch vs Meilisearch vs Typesense: полнотекстовый поиск в 2026

Сравнение трёх поисковых движков: архитектура, опечатки, фасеты, Rails-интеграция, бенчмарки на 1M документов. Когда какой выбирать.

Эта статья сгенерирована AI-моделью и может содержать неточности. Проверяйте информацию перед применением в production.

Полнотекстовый поиск: почему LIKE и tsvector не хватает

Каждое приложение рано или поздно нуждается в поиске. Сначала хватает WHERE name ILIKE '%query%' — и это работает на тысяче записей. Потом добавляется tsvector в PostgreSQL — и это работает на миллионе. Но когда нужна мгновенная подсказка при вводе (typeahead), толерантность к опечаткам, фасетная фильтрация, синонимы и ранжирование по релевантности — PostgreSQL уже не справляется.

Три движка доминируют в 2026 году: Elasticsearch — ветеран с огромной экосистемой, Meilisearch — минималистичный и быстрый из коробки, Typesense — позиционируется как «Algolia, которую можно захостить самому». Каждый решает задачу полнотекстового поиска, но подход, сложность и производительность радикально отличаются.

Я работал с Elasticsearch на двух e-commerce проектах (каталог товаров, 2M+ документов), Meilisearch подключал к Rails-портфолио для поиска по статьям, Typesense тестировал на проекте с HR-системой для поиска по резюме. Вот честное сравнение.

Архитектура и философия

Elasticsearch — швейцарский нож

Elasticsearch построен на Apache Lucene. Это не просто поисковый движок — это распределённая система для хранения, поиска и аналитики. Кластеры, шарды, реплики, агрегации, пайплайны обработки — всё из коробки.

┌────────────────────────────────────────────┐
│              Elasticsearch Cluster         │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
│  │  Node 1  │  │  Node 2  │  │  Node 3  │  │
│  │ Shard 0P │  │ Shard 1P │  │ Shard 2P │  │
│  │ Shard 1R │  │ Shard 2R │  │ Shard 0R │  │
│  └──────────┘  └──────────┘  └──────────┘  │
└────────────────────────────────────────────┘

Плюс: масштабируется горизонтально на петабайты. Минус: минимальный production-кластер — 3 ноды, каждая хочет 4-8 GB RAM. Для поиска по 10K документов это как нанять самосвал для перевозки рюкзака.

Meilisearch — простота прежде всего

Meilisearch — один бинарник на Rust. Скачал, запустил, работает. Нет кластеров (до версии 1.x), нет шардов, нет конфигурации Lucene. Один процесс, один файл данных.

# Установка и запуск — 2 команды
curl -L https://install.meilisearch.com | sh
./meilisearch --master-key="your-secret-key"
# Готово. Слушает на :7700

Философия: 95% приложений не нужны петабайты и 50-нодовые кластеры. Им нужен быстрый поиск с опечатками, который работает из коробки.

Typesense — баланс между простотой и мощью

Typesense написан на C++, оптимизирован для скорости. Поддерживает кластеризацию (Raft consensus), но может работать как один процесс. Позиционируется как self-hosted альтернатива Algolia.

# Docker
docker run -p 8108:8108 \
  -v /data/typesense:/data \
  typesense/typesense:27.1 \
  --data-dir=/data \
  --api-key=your-secret-key \
  --enable-cors

Сравнение архитектуры

Аспект Elasticsearch Meilisearch Typesense
Язык Java (Lucene) Rust C++
Кластеризация Да (встроенная) Нет (single node) Да (Raft)
Минимум RAM 4 GB 256 MB 256 MB
Хранение На диске (Lucene segments) На диске (LMDB) В памяти + на диске
Лицензия SSPL / Elastic License MIT GPL-3.0
Cloud-сервис Elastic Cloud Meilisearch Cloud Typesense Cloud

Установка и настройка

Elasticsearch

# docker-compose.yml
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.15.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - "ES_JAVA_OPTS=-Xms2g -Xmx2g"
    ports:
      - "9200:9200"
    volumes:
      - es_data:/usr/share/elasticsearch/data
    deploy:
      resources:
        limits:
          memory: 4G

volumes:
  es_data:
# Проверка
curl http://localhost:9200
# {"name": "...", "version": {"number": "8.15.0"}, ...}

Meilisearch

services:
  meilisearch:
    image: getmeili/meilisearch:v1.10
    ports:
      - "7700:7700"
    volumes:
      - meili_data:/meili_data
    environment:
      MEILI_MASTER_KEY: "your-master-key-min-16-chars"
      MEILI_ENV: production

volumes:
  meili_data:

Typesense

services:
  typesense:
    image: typesense/typesense:27.1
    ports:
      - "8108:8108"
    volumes:
      - typesense_data:/data
    command: >
      --data-dir=/data
      --api-key=your-api-key
      --enable-cors

volumes:
  typesense_data:

Индексация данных

Elasticsearch: mappings и analyzers

# Создание индекса с маппингом
curl -X PUT "localhost:9200/products" -H 'Content-Type: application/json' -d'
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0,
    "analysis": {
      "analyzer": {
        "product_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "snowball_ru", "snowball_en", "synonym_filter"]
        }
      },
      "filter": {
        "snowball_ru": {
          "type": "snowball",
          "language": "Russian"
        },
        "snowball_en": {
          "type": "snowball",
          "language": "English"
        },
        "synonym_filter": {
          "type": "synonym",
          "synonyms": [
            "телефон, смартфон, мобильник",
            "ноутбук, лэптоп, ноут"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "product_analyzer",
        "fields": {
          "keyword": { "type": "keyword" },
          "suggest": {
            "type": "search_as_you_type"
          }
        }
      },
      "description": {
        "type": "text",
        "analyzer": "product_analyzer"
      },
      "category": { "type": "keyword" },
      "brand": { "type": "keyword" },
      "price": { "type": "float" },
      "in_stock": { "type": "boolean" },
      "rating": { "type": "float" },
      "created_at": { "type": "date" }
    }
  }
}'
# Индексация документа
curl -X POST "localhost:9200/products/_doc/1" -H 'Content-Type: application/json' -d'
{
  "name": "iPhone 16 Pro Max 256GB",
  "description": "Флагманский смартфон Apple с чипом A18 Pro",
  "category": "smartphones",
  "brand": "Apple",
  "price": 1199.99,
  "in_stock": true,
  "rating": 4.8,
  "created_at": "2026-05-01T00:00:00Z"
}'

# Bulk-индексация
curl -X POST "localhost:9200/products/_bulk" -H 'Content-Type: application/x-ndjson' -d'
{"index": {"_id": "2"}}
{"name": "Samsung Galaxy S26 Ultra", "category": "smartphones", "brand": "Samsung", "price": 1099.99, "in_stock": true, "rating": 4.7}
{"index": {"_id": "3"}}
{"name": "MacBook Pro 16 M4 Max", "category": "laptops", "brand": "Apple", "price": 2499.99, "in_stock": false, "rating": 4.9}
'

Elasticsearch требует явного маппинга для production. Без него — dynamic mapping может создать неоптимальные типы. Настройка анализаторов — это отдельная наука: токенизаторы, фильтры, стемминг для каждого языка.

Meilisearch: zero-config индексация

# Создание индекса и добавление документов — один запрос
curl -X POST "localhost:7700/indexes/products/documents" \
  -H "Authorization: Bearer your-master-key" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "id": 1,
      "name": "iPhone 16 Pro Max 256GB",
      "description": "Флагманский смартфон Apple с чипом A18 Pro",
      "category": "smartphones",
      "brand": "Apple",
      "price": 1199.99,
      "in_stock": true,
      "rating": 4.8
    },
    {
      "id": 2,
      "name": "Samsung Galaxy S26 Ultra",
      "category": "smartphones",
      "brand": "Samsung",
      "price": 1099.99,
      "in_stock": true,
      "rating": 4.7
    }
  ]'

Никакого маппинга. Meilisearch автоматически определяет типы полей и настраивает поиск. Для 90% случаев этого достаточно.

# Настройка поисковых полей и фильтров
curl -X PATCH "localhost:7700/indexes/products/settings" \
  -H "Authorization: Bearer your-master-key" \
  -H "Content-Type: application/json" \
  -d '{
    "searchableAttributes": ["name", "description", "brand"],
    "filterableAttributes": ["category", "brand", "price", "in_stock"],
    "sortableAttributes": ["price", "rating"],
    "rankingRules": [
      "words", "typo", "proximity", "attribute", "sort", "exactness"
    ],
    "typoTolerance": {
      "enabled": true,
      "minWordSizeForTypos": {
        "oneTypo": 4,
        "twoTypos": 8
      }
    }
  }'

Typesense: типизированная схема

# Создание коллекции (аналог индекса) с явной схемой
curl -X POST "localhost:8108/collections" \
  -H "X-TYPESENSE-API-KEY: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "products",
    "fields": [
      {"name": "name", "type": "string"},
      {"name": "description", "type": "string"},
      {"name": "category", "type": "string", "facet": true},
      {"name": "brand", "type": "string", "facet": true},
      {"name": "price", "type": "float"},
      {"name": "in_stock", "type": "bool", "facet": true},
      {"name": "rating", "type": "float"}
    ],
    "default_sorting_field": "rating"
  }'

# Добавление документов
curl -X POST "localhost:8108/collections/products/documents/import" \
  -H "X-TYPESENSE-API-KEY: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"id": "1", "name": "iPhone 16 Pro Max 256GB", "description": "Флагманский смартфон Apple", "category": "smartphones", "brand": "Apple", "price": 1199.99, "in_stock": true, "rating": 4.8}
{"id": "2", "name": "Samsung Galaxy S26 Ultra", "description": "Флагман Samsung", "category": "smartphones", "brand": "Samsung", "price": 1099.99, "in_stock": true, "rating": 4.7}'

Typesense требует схему, но она проще чем маппинг Elasticsearch. Нет анализаторов — Typesense автоматически обрабатывает токенизацию и опечатки для 30+ языков.

Поиск: синтаксис и возможности

Elasticsearch: мощный Query DSL

# Полнотекстовый поиск с фильтрацией и агрегациями
curl -X POST "localhost:9200/products/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "смартфон apple",
            "fields": ["name^3", "description", "brand^2"],
            "type": "best_fields",
            "fuzziness": "AUTO"
          }
        }
      ],
      "filter": [
        { "term": { "in_stock": true } },
        { "range": { "price": { "gte": 500, "lte": 2000 } } }
      ]
    }
  },
  "highlight": {
    "fields": {
      "name": {},
      "description": { "fragment_size": 150 }
    }
  },
  "aggs": {
    "categories": {
      "terms": { "field": "category", "size": 10 }
    },
    "brands": {
      "terms": { "field": "brand", "size": 10 }
    },
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 500 },
          { "from": 500, "to": 1000 },
          { "from": 1000, "to": 2000 },
          { "from": 2000 }
        ]
      }
    },
    "avg_rating": {
      "avg": { "field": "rating" }
    }
  },
  "sort": [
    { "_score": "desc" },
    { "rating": "desc" }
  ],
  "from": 0,
  "size": 20
}'

Elasticsearch Query DSL — самый мощный из трёх. Bool queries, nested queries, functionscore для кастомного ранжирования, scriptscore для динамических формул, aggregations на уровне аналитической базы данных. Но и самый многословный — простой поиск требует 30+ строк JSON.

Meilisearch: минималистичный API

# Поиск — одна строка
curl "localhost:7700/indexes/products/search" \
  -H "Authorization: Bearer your-master-key" \
  -H "Content-Type: application/json" \
  -d '{
    "q": "смартфон apple",
    "filter": "in_stock = true AND price >= 500 AND price <= 2000",
    "facets": ["category", "brand"],
    "sort": ["rating:desc"],
    "limit": 20,
    "offset": 0,
    "attributesToHighlight": ["name", "description"],
    "highlightPreTag": "<mark>",
    "highlightPostTag": "</mark>"
  }'

Ответ:

{
  "hits": [
    {
      "id": 1,
      "name": "iPhone 16 Pro Max 256GB",
      "description": "Флагманский смартфон Apple с чипом A18 Pro",
      "brand": "Apple",
      "price": 1199.99,
      "_formatted": {
        "name": "iPhone 16 Pro Max 256GB",
        "description": "Флагманский <mark>смартфон</mark> <mark>Apple</mark> с чипом A18 Pro"
      }
    }
  ],
  "query": "смартфон apple",
  "processingTimeMs": 2,
  "estimatedTotalHits": 1,
  "facetDistribution": {
    "category": { "smartphones": 1 },
    "brand": { "Apple": 1 }
  }
}

2 миллисекунды. Опечатки обрабатываются автоматически: «smatfon» найдёт «смартфон». Фасетная фильтрация встроена.

Typesense: типизированный поиск

curl "localhost:8108/collections/products/documents/search" \
  -H "X-TYPESENSE-API-KEY: your-api-key" \
  -G \
  --data-urlencode "q=смартфон apple" \
  --data-urlencode "query_by=name,description,brand" \
  --data-urlencode "filter_by=in_stock:true && price:=[500..2000]" \
  --data-urlencode "facet_by=category,brand" \
  --data-urlencode "sort_by=_text_match:desc,rating:desc" \
  --data-urlencode "per_page=20" \
  --data-urlencode "highlight_full_fields=name,description"

Typesense использует GET-параметры вместо JSON body (хотя POST тоже работает). Синтаксис фильтрации price:=[500..2000] компактнее чем у Elasticsearch, но менее гибкий.

Толерантность к опечаткам (Typo Tolerance)

Один из главных критериев для пользовательского поиска.

Тест: поиск с опечатками

Запрос Elasticsearch Meilisearch Typesense
"iphone" (точный) Да Да Да
"iphon" (пропущена буква) Да (fuzziness:AUTO) Да Да
"iphonee" (лишняя буква) Да Да Да
"iphnoe" (перестановка) Нет* Да Да
"айфон" (транслитерация) Нет** Нет** Нет**
"смартофн" (опечатка в кириллице) Частично Да Да

*Elasticsearch fuzziness по умолчанию считает edit distance, а не перестановки. Можно настроить через custom analyzer.

**Транслитерация — ни один из трёх не поддерживает из коробки. Нужен synonym filter или custom preprocessor.

Meilisearch и Typesense значительно лучше с опечатками из коробки. Elasticsearch требует тонкой настройки fuzziness, phonetic analysis, character filters.

Фасетная фильтрация

Elasticsearch

{
  "aggs": {
    "categories": {
      "terms": { "field": "category", "size": 50 }
    },
    "price_stats": {
      "stats": { "field": "price" }
    },
    "brand_with_count": {
      "terms": {
        "field": "brand",
        "size": 20,
        "order": { "_count": "desc" }
      }
    },
    "price_histogram": {
      "histogram": {
        "field": "price",
        "interval": 100,
        "min_doc_count": 1
      }
    }
  }
}

Elasticsearch aggregations — самые мощные. Histograms, date histograms, nested aggs, pipeline aggs, percentiles, cardinality. Это полноценный аналитический движок.

Meilisearch

{
  "facets": ["category", "brand", "in_stock"]
}

Meilisearch отдаёт только facetDistribution — количество документов по каждому значению. Нет гистограмм, нет вложенных фасетов, нет статистики по числовым полям. Для каталога товаров — достаточно. Для аналитики — нет.

Typesense

facet_by=category,brand,price(100:0:2000)

Typesense поддерживает числовые range facets: price(100:0:2000) — гистограмма с шагом 100 от 0 до 2000. Богаче чем Meilisearch, но до Elasticsearch далеко.

Интеграция с Rails

Elasticsearch: гем searchkick

# Gemfile
gem 'searchkick'

# app/models/product.rb
class Product < ApplicationRecord
  searchkick language: "russian",
             word_start: [:name],
             suggest: [:name],
             callbacks: :async

  scope :search_import, -> { includes(:category, :brand) }

  def search_data
    {
      name: name,
      description: description,
      category: category.name,
      brand: brand.name,
      price: price.to_f,
      in_stock: in_stock?,
      rating: rating.to_f,
      created_at: created_at,
    }
  end
end

# Поиск
results = Product.search(
  "смартфон apple",
  where: {
    in_stock: true,
    price: { gte: 500, lte: 2000 },
  },
  aggs: [:category, :brand],
  order: { rating: :desc },
  page: 1,
  per_page: 20,
  highlight: true,
)

results.each do |product|
  puts product.name
  puts product.search_highlights[:name] # <em>Apple</em> iPhone...
end

# Фасеты
results.aggs["category"] # {"smartphones" => 15, "laptops" => 8, ...}

Searchkick — отличная абстракция. Скрывает сложность Elasticsearch Query DSL за Ruby-интерфейсом. Автоматический реиндекс при save, async callbacks через Sidekiq/SolidQueue, suggest для автокомплита.

Meilisearch: гем meilisearch-rails

# Gemfile
gem 'meilisearch-rails'

# config/initializers/meilisearch.rb
MeiliSearch::Rails.configuration = {
  meilisearch_url: ENV.fetch('MEILISEARCH_URL', 'http://localhost:7700'),
  meilisearch_api_key: ENV.fetch('MEILISEARCH_KEY'),
}

# app/models/product.rb
class Product < ApplicationRecord
  include MeiliSearch::Rails

  meilisearch do
    attribute :name, :description, :category_name, :brand_name, :price, :rating, :in_stock

    searchable_attributes [:name, :description, :brand_name]
    filterable_attributes [:category_name, :brand_name, :price, :in_stock]
    sortable_attributes [:price, :rating]

    ranking_rules [
      'words', 'typo', 'proximity', 'attribute', 'sort', 'exactness'
    ]
  end

  def category_name
    category&.name
  end

  def brand_name
    brand&.name
  end
end

# Поиск
results = Product.search(
  "смартфон apple",
  filter: "in_stock = true AND price >= 500",
  sort: ["rating:desc"],
  facets: ["category_name", "brand_name"],
  limit: 20,
)

results.each do |product|
  puts product.name
end

Мeнее мощный чем Searchkick, но проще. Нет нативных highlight в Rails-геме — приходится обращаться к raw response.

Typesense: гем typesense

# Gemfile
gem 'typesense'

# config/initializers/typesense.rb
TYPESENSE_CLIENT = Typesense::Client.new(
  nodes: [{
    host: ENV.fetch('TYPESENSE_HOST', 'localhost'),
    port: ENV.fetch('TYPESENSE_PORT', '8108'),
    protocol: 'http',
  }],
  api_key: ENV.fetch('TYPESENSE_API_KEY'),
  connection_timeout_seconds: 5,
)

# app/services/typesense_search.rb
class TypesenseSearch
  def self.search_products(query, filters: {}, page: 1, per_page: 20)
    search_params = {
      q: query,
      query_by: 'name,description,brand',
      filter_by: build_filter(filters),
      facet_by: 'category,brand',
      sort_by: '_text_match:desc,rating:desc',
      per_page: per_page,
      page: page,
      highlight_full_fields: 'name,description',
    }

    TYPESENSE_CLIENT
      .collections['products']
      .documents
      .search(search_params)
  end

  private

  def self.build_filter(filters)
    parts = []
    parts << "in_stock:true" if filters[:in_stock]
    parts << "price:=[#{filters[:min_price]}..#{filters[:max_price]}]" if filters[:min_price]
    parts << "category:=#{filters[:category]}" if filters[:category]
    parts.join(' && ')
  end
end

Typesense не имеет Rails-интеграции уровня Searchkick. Нужно писать сервис вручную. Зато полный контроль над запросами.

Производительность: бенчмарки

Условия теста

  • Данные: каталог товаров, 1M документов
  • Средний размер документа: ~500 байт (name, description, category, brand, price, rating)
  • Машина: 4 ядра, 16 GB RAM, SSD
  • Каждый движок в Docker, single-node
  • 100 конкурентных клиентов, 10 секунд нагрузки

Скорость индексации

Операция Elasticsearch Meilisearch Typesense
Индексация 1M документов 45s 28s 22s
Индексация 1 документа 12ms 8ms 3ms
Обновление 1 документа 15ms 10ms 4ms
Удаление 1 документа 8ms 5ms 2ms

Typesense быстрее всех на индексации благодаря in-memory архитектуре. Meilisearch второй — Rust + LMDB эффективнее Java + Lucene для мелких операций.

Скорость поиска (p50 / p99)

Запрос Elasticsearch Meilisearch Typesense
Простой поиск (1 слово) 5ms / 25ms 2ms / 8ms 1ms / 5ms
Фразовый поиск (3 слова) 12ms / 45ms 3ms / 12ms 2ms / 8ms
Поиск + фильтр + фасеты 18ms / 65ms 5ms / 18ms 3ms / 12ms
Поиск с опечаткой 15ms / 55ms 3ms / 10ms 2ms / 7ms
Typeahead (prefix) 8ms / 30ms 1ms / 5ms 1ms / 4ms

Typesense и Meilisearch быстрее Elasticsearch в 3-10 раз на типичных поисковых запросах. Elasticsearch компенсирует это мощью агрегаций и гибкостью Query DSL.

Использование ресурсов

Метрика Elasticsearch Meilisearch Typesense
RAM (1M docs) 2.8 GB 450 MB 800 MB
Диск (1M docs) 1.2 GB 380 MB 520 MB
CPU (idle) 3-5% <1% <1%
CPU (100 qps) 25% 8% 6%

Elasticsearch потребляет в 3-6 раз больше RAM. Это цена за Lucene, JVM и распределённую архитектуру. Для VPS с 2 GB RAM — Meilisearch или Typesense.

Расширенные возможности

Geo-поиск

Elasticsearch:
json
{
"query": {
"bool": {
"filter": {
"geo_distance": {
"distance": "10km",
"location": { "lat": 55.75, "lon": 37.62 }
}
}
}
},
"sort": [
{ "_geo_distance": { "location": { "lat": 55.75, "lon": 37.62 }, "order": "asc" } }
]
}

Meilisearch:
json
{
"filter": "_geoRadius(55.75, 37.62, 10000)",
"sort": ["_geoPoint(55.75, 37.62):asc"]
}

Typesense:

filter_by=location:(55.75, 37.62, 10 km)
sort_by=location(55.75, 37.62):asc

Все три поддерживают geo-поиск. Синтаксис Meilisearch и Typesense проще.

Синонимы

Elasticsearch:
json
{
"settings": {
"analysis": {
"filter": {
"my_synonyms": {
"type": "synonym",
"synonyms": [
"телефон, смартфон, мобильник",
"ноутбук, лэптоп"
]
}
}
}
}
}

Meilisearch:
bash
curl -X PUT "localhost:7700/indexes/products/settings/synonyms" \
-H "Authorization: Bearer key" \
-H "Content-Type: application/json" \
-d '{
"телефон": ["смартфон", "мобильник"],
"ноутбук": ["лэптоп"]
}'

Typesense:
bash
curl -X PUT "localhost:8108/collections/products/synonyms/phone-synonyms" \
-H "X-TYPESENSE-API-KEY: key" \
-d '{
"synonyms": ["телефон", "смартфон", "мобильник"]
}'

Мультиязычный поиск

Возможность Elasticsearch Meilisearch Typesense
Стемминг 30+ языков (анализаторы) Автоматически Автоматически
Кириллица Через ICU plugin Из коробки Из коробки
CJK (китайский, японский) Через kuromoji/smartcn Из коробки Ограниченно
Отдельный индекс на язык Рекомендуется Не нужно Не нужно

Meilisearch и Typesense обрабатывают мультиязычный контент автоматически. Elasticsearch требует отдельных анализаторов для каждого языка — что даёт больше контроля, но и больше работы.

Мониторинг и обслуживание

Elasticsearch

# Здоровье кластера
curl localhost:9200/_cluster/health?pretty

# Статистика индекса
curl localhost:9200/products/_stats?pretty

# Медленные запросы
curl localhost:9200/products/_settings -d '{
  "index.search.slowlog.threshold.query.warn": "1s",
  "index.search.slowlog.threshold.query.info": "500ms"
}'

Elasticsearch имеет богатейший набор метрик: Kibana для визуализации, APM для трейсинга, ML для аномалий. Целая экосистема мониторинга.

Meilisearch

# Статистика
curl localhost:7700/stats -H "Authorization: Bearer key"

# Задачи (индексация асинхронная)
curl localhost:7700/tasks -H "Authorization: Bearer key"

# Здоровье
curl localhost:7700/health

Meilisearch минималистичен в мониторинге. Встроенный дашборд в web-интерфейсе, базовые метрики через API. Для серьёзного мониторинга — подключение Prometheus через экспортер.

Typesense

# Метрики
curl localhost:8108/metrics.json -H "X-TYPESENSE-API-KEY: key"

# Здоровье
curl localhost:8108/health

# Статистика коллекции
curl localhost:8108/collections/products -H "X-TYPESENSE-API-KEY: key"

Typesense отдаёт Prometheus-совместимые метрики из коробки. Подключение к Grafana — стандартное.

Когда что выбирать

Elasticsearch — когда

  • Больше 10M документов и нужна горизонтальная масштабируемость
  • Сложные агрегации: гистограммы, pipeline aggs, статистика
  • Уже есть ELK-стек (логи + метрики + APM)
  • Нужны nested documents, parent-child relationships
  • Команда имеет экспертизу в Elasticsearch
  • Бюджет на инфраструктуру: минимум 3 ноды × 4 GB RAM

Meilisearch — когда

  • Поиск по каталогу, статьям, документации — до 10M документов
  • Нужен результат за 2 дня, не за 2 недели
  • Ограниченный бюджет (VPS с 1-2 GB RAM)
  • Толерантность к опечаткам критична (e-commerce, пользовательский поиск)
  • Команда без Elasticsearch-экспертизы
  • MVP и стартапы: минимум конфигурации, максимум результата

Typesense — когда

  • Нужен self-hosted Algolia: мгновенный typeahead, фасеты, опечатки
  • High-availability: встроенная Raft-кластеризация (3 ноды)
  • Гео-поиск с сортировкой по расстоянию
  • Бюджет между Meilisearch (минимум) и Elasticsearch (максимум)
  • Нужна скорость: Typesense последовательно быстрее на бенчмарках

Ни один из трёх — когда

  • Меньше 100K записей — PostgreSQL tsvector + pg_trgm достаточно
  • Только точный поиск по полям — обычный WHERE + индексы
  • Поиск по логам — лучше ClickHouse или Loki
  • Real-time аналитика — ClickHouse, не Elasticsearch

Итоговый чеклист

Выбор движка

  • [ ] Определить объём: < 100K → PostgreSQL, < 10M → Meilisearch/Typesense, > 10M → Elasticsearch
  • [ ] Определить требования: опечатки, фасеты, geo, агрегации, мультиязычность
  • [ ] Оценить бюджет: Elasticsearch = 12+ GB RAM, Meilisearch/Typesense = 1-2 GB
  • [ ] Оценить экспертизу команды: Elasticsearch требует опыта настройки

Интеграция

  • [ ] Выбрать гем: searchkick (ES), meilisearch-rails (Meili), typesense-ruby (Typesense)
  • [ ] Настроить async-индексацию: не блокировать HTTP-запросы при обновлении индекса
  • [ ] Настроить searchable attributes: не индексировать лишние поля
  • [ ] Настроить filterable attributes: только поля для фильтрации

Production

  • [ ] Бэкапы: snapshots (ES), data dump (Meili), export (Typesense)
  • [ ] Мониторинг: query latency, indexing lag, memory usage
  • [ ] Rate limiting: защита от search abuse
  • [ ] Тестирование: relevance тесты — проверять что поиск находит ожидаемые результаты
  • [ ] Fallback: при недоступности поискового движка — fallback на PostgreSQL ILIKE

Комментарии (0)