Practical guide

How to implement full-text search with Elasticsearch

Start with user questions, not a cluster. Documents, analysis, and relevance must come from real searches.

30 minutes · Elasticsearch

In short

A search index is a derived model

Elasticsearch stores documents optimized for search. It analyzes text into terms and scores relevance while exact filters run without text scoring.

The primary database remains the source of truth. The search index must be fully rebuildable and incremental synchronization must tolerate delayed, repeated, and delete events.

Prepare

What you need

Before creating a mapping, list the most important queries and which results should rank first.

  • Real search phrases including typos, synonyms, diacritics, and zero-result cases.
  • A source of truth with an immutable document ID and a change version used to identify newer state.
  • Text fields, exact filters, sorting, and values displayed in each result.
  • Relevance and operational metrics: zero-result rate, clicks, conversions, p95 latency, and synchronization failures.

Steps 1 to 3

Build a dedicated search read model

One document should contain everything needed for a search result. Do not replace a relational join with dozens of follow-up queries.

1. Design mapping and text analysis

  1. Denormalize ID, name, description, category, brand, availability, and other values required for search and display.
  2. Map text as text and exact codes, filters, and aggregations as keyword or numeric fields. A name can have text and keyword multi-fields.
  3. Choose analysis for the content language. Test tokenization, lowercase, diacritics, stemming, and domain synonyms with real phrases.
  4. Version the mapping. A major analyzer change cannot fix existing terms without a new index and reindexing.
"name": {
  "type": "text",
  "analyzer": "english",
  "fields": {"raw": {"type": "keyword"}}
}
Official language analyzer documentation

2. Synchronize documents idempotently

  1. For initial indexing, read the database in batches and write with the Bulk API. Tune batch size to document size and cluster response.
  2. Publish changes through a reliable outbox or change feed. The consumer indexes by stable product ID, so a repeated event creates no duplicate.
  3. Handle ordering with a document version or updated_at comparison. An older message must never overwrite newer state.
  4. Deletion and hiding need explicit synchronization. A periodic reconciliation job compares the source of truth with the index and repairs gaps.
POST /products-v1/_bulk
Official Elasticsearch Bulk API

3. Compose text queries and filters

  1. Use full-text match or multi_match for name, brand, and description and weight each field by importance.
  2. Put availability, tenant, category, and price range in bool.filter so they do not distort text relevance.
  3. Set a maximum page size and use search_after instead of unbounded from for deep traversal.
  4. Return ID, display values, and a stable documented ordering strategy. Do not query the primary database once per hit.
multi_match: query=name^4, brand^2, description; filter: active=true
Official Elastic full-text documentation

Step 4

Test relevance as a feature

Returning something is not enough. Important phrases need the right documents in the right order.

  1. Build an expected-results set

    For dozens of real phrases, store relevant products and acceptable ordering. Run it whenever mappings or weights change.

    php bin/phpunit --filter SearchRelevance
  2. Verify an index rebuild

    Build a new index from the source of truth, switch the alias, and compare document counts and control queries without downtime.

    POST /_aliases
  3. Simulate a delayed event

    Deliver an older event after a newer update. The document must stay new and reconciliation must report no difference.

When it goes wrong

Common mistakes

An exact filter finds nothing

The value may be mapped as analyzed text. Use a keyword field for codes, states, and aggregations and inspect the actual mapping.

GET /products/_mapping
Results rank poorly

Create concrete relevance tests and adjust field weights, analysis, or synonyms. Do not judge quality from one hand-picked query.

The index contains stale data

Monitor synchronization lag, failed bulk items, and event ordering. Add reconciliation against the source of truth.

An analyzer change cannot be applied to the existing field

Build a new versioned index, populate and test it, then switch the alias atomically. Do not rewrite a production mapping blindly.

Done

Full-text search has a rebuildable index and measurable relevance.

Elasticsearch now serves as a derived search model: documents are rebuildable, changes synchronize, and relevance tests protect result order.

Request a call

I will call you on the next working day between 9:00 and 17:00.

You can also call me directly.

+420 605 181 728

Leave your phone number and send a callback request.

By sending, you agree to processing your data in order to handle your request.