Skip to content
ExponaBLOG
ELASTICSEARCH & SEARCH ENGINEERING · DAY 016 / 100

Relevance tuning: Elasticsearch best practices explained

Make the best results appear first.

Expona Admin6 min read

Introduction

Getting search results right is half the battle in building a discovery experience that users love. Relevance tuning: Elasticsearch best practices explained covers the techniques we've refined across dozens of production systems—from e-commerce platforms ranking products by intent, to CRM tools surfacing the most important leads first. Raw query matching isn't enough; we need to blend keyword relevance with business signals like popularity, recency, and user behaviour to make the best results appear first. This article walks you through the core strategies that separate mediocre search from search that converts.

Field Boosts — Prioritizing Where Matches Occur

Not all fields are created equal. A product name match should rank higher than a match buried in the product description. In Elasticsearch, field^N syntax lets you amplify the relevance score of matches in specific fields, where N is the boost multiplier.

Consider a typical e-commerce scenario: a user searches for "leather jacket". You want exact matches in the product title to dominate, followed by category matches, then description matches. Here's how we structure that:

json
{
  "query": {
    "multi_match": {
      "query": "leather jacket",
      "fields": [
        "title^3",
        "category^2",
        "description^1"
      ],
      "type": "best_fields",
      "operator": "and"
    }
  }
}

The title^3 means matches in the title field get three times the score boost compared to the base relevance calculation. This is simple, fast, and works well for most use cases. However, field boosts alone won't solve complex ranking problems—they're a starting point, not the full answer.

A practical tip: start with modest boosts (2–4 range) and measure click-through rates before cranking them to extreme values. We've seen teams boost category fields to 10+ and accidentally bury relevant results. Test incrementally.

Function Score — Blending Relevance with Business Signals

Once keyword relevance is dialled in, you often need to blend it with secondary signals: how popular a product is, when it was last updated, inventory levels, or custom user scores. This is where function_score query shines.

function_score wraps your base query and applies mathematical functions to the relevance score before final ranking. The most common functions we use in production are field_value_factor, decay, and random_score.

Imagine you're running an e-commerce platform and want to rank products by relevance, but also boost products with higher ratings and more recent sales velocity:

json
{
  "query": {
    "function_score": {
      "query": {
        "multi_match": {
          "query": "leather jacket",
          "fields": ["title^3", "category^2"]
        }
      },
      "functions": [
        {
          "field_value_factor": {
            "field": "rating",
            "modifier": "sqrt",
            "factor": 1.2
          }
        },
        {
          "field_value_factor": {
            "field": "sales_count",
            "modifier": "log1p",
            "factor": 0.8
          }
        },
        {
          "gauss": {
            "last_sale_timestamp": {
              "origin": "now",
              "scale": "30d",
              "decay": 0.5
            }
          }
        }
      ],
      "score_mode": "sum",
      "boost_mode": "multiply"
    }
  }
}

Let's break this down. The base multi_match query calculates the text relevance score. Then:

  • field_value_factor on rating uses the square root modifier (to dampen extreme values) and a factor of 1.2 to smoothly boost highly-rated products.
  • field_value_factor on sales_count uses log1p modifier (log of 1 + value) to avoid runaway boosts from outlier bestsellers.
  • gauss decay function gives recency credit: products sold within the last 30 days get a boost, which decays to 50% strength beyond that window.

The score_mode: "sum" adds all function scores together, and boost_mode: "multiply" multiplies the base query score by the total function score. This ensures that a product with poor text match won't rank high just because it's popular—relevance remains the floor.

In a trading platform context, you might use function_score to surface active, liquid instruments over stale tickers. In a SaaS CRM, you'd boost recently-touched leads over cold ones, conditional on keyword match quality.

Phrase Boosting — Rewarding Exact Phrase Matches

Users often type multi-word queries, and when all words appear adjacently in the correct order, that's a strong signal. A query for "machine learning" should rank documents with the exact phrase "machine learning" above documents that contain both words scattered across paragraphs.

match_phrase queries find exact phrases, but they don't return a score—they're binary. To reward phrases in a flexible way, we use a bool query with should clauses:

json
{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "machine learning",
            "fields": ["title", "content"],
            "type": "best_fields"
          }
        }
      ],
      "should": [
        {
          "match_phrase": {
            "title": {
              "query": "machine learning",
              "boost": 3
            }
          }
        },
        {
          "match_phrase": {
            "content": {
              "query": "machine learning",
              "boost": 2
            }
          }
        }
      ]
    }
  }
}

Here, the must clause ensures documents contain both words (the base relevance match). The should clauses add significant bonus points if the exact phrase appears, with higher weight on title matches. This pattern works well for long-tail queries and technical content where phrasing matters.

A real-world example: in a travel search engine, a query for "new york city" should reward documents with that exact phrase. A loose match where "new" is in the title and "york city" is in a description is weaker and should rank lower.

Measure — Tracking Clicks and Testing with Real Queries

The ranking strategies above are starting points. Every team's data distribution is different. What works for products won't work for articles, and what works for one region may not work for another. Measurement is non-negotiable.

We recommend implementing a click-tracking pipeline that captures:

  • Query: The raw search string.
  • Result position: Which rank (1st, 2nd, 3rd, etc.) was clicked.
  • Document ID: Which result was clicked.
  • Timestamp: When the click occurred.
  • Dwell time: How long the user spent on the result page (if available).

Stream these events to a data warehouse and calculate metrics like:

  • Click-through rate (CTR): Clicks on position 1 vs. position 10 should differ significantly. A flat CTR curve suggests poor relevance.
  • Mean reciprocal rank (MRR): The average inverse rank of the first clicked result. Higher is better; it means users find what they want quickly.
  • Query success rate: What fraction of queries result in at least one click? A low rate signals irrelevant results.

Once you have baseline metrics, run A/B tests: deploy a new boost configuration to 10% of traffic for a week, then compare click metrics. Changes should move the needle by at least 2–5% to be worthwhile.

In production, we've seen teams reduce position-1 CTR drops by 20% just by tuning field boosts based on query logs. For recency-sensitive domains (news, travel, ride-hailing), adding time decay functions typically lifts CTR by 10–15% without harming precision.

Common Mistakes

Over-boosting leading to irrelevance

Setting a field boost too high (e.g., title^10) can rank a marginally relevant title match above a highly relevant description match. Always validate with real user queries before deploying extreme boosts.

Ignoring score normalization

Different fields have different average scores depending on field length and distribution. A short title field naturally produces higher scores per word than a long description. Use modifiers like sqrt or log1p in field_value_factor to normalize wild value ranges.

Treating function_score as a ranking silver bullet

Too many teams add dozens of functions and tweak factors in the dark. function_score is a tool, not magic. Start with 2–3 well-motivated functions, measure, and only add more if A/B tests show improvement.

Skipping phrase boosting for user-facing queries

Multi-word queries are common in real search. Ignoring phrase matching means missing easy relevance wins. A small phrase boost cost almost nothing and catches legitimate exact-match intent.

Not tracking queries that get zero clicks

It's tempting to focus on optimizing rank 1 CTR, but queries that yield zero clicks are often the biggest problem. These are typically low-relevance or no-result scenarios. Set alerts for queries with low click rates and investigate.

Key Takeaways

  • Use field boosts to weight matches in high-signal fields (title, category) above lower-signal fields (description), starting with modest multipliers (2–4) and testing before production deployment.
  • Layer function_score queries to blend text relevance with business signals like popularity, recency, or custom scores, using modifiers to prevent outliers from dominating.
  • Apply phrase boosting with match_phrase in should clauses to reward multi-word queries that match exactly, especially on high-value fields.
  • Implement click-tracking and measure CTR, mean reciprocal rank, and query success rate to validate ranking changes with real user behaviour.
  • Avoid over-aggressive boosts, ignore zero-click queries at your peril, and run A/B tests before treating any ranking change as the final answer.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles