Search is the feature most often implemented as a LIKE query and most often complained about afterwards. The complaints are rarely 'it is slow' — they are 'I searched for the thing I was looking at yesterday and it was not in the results', which is a relevance problem, and relevance is a design decision nobody made.
Analysis decides what can be found at all
Before ranking matters, tokenisation does. How text is split, lowercased, stemmed and normalised determines whether 'inspections' matches 'inspection', whether a product code with a hyphen is one token or two, and whether an accented name is findable by someone typing without accents. Most 'search is broken' reports trace back to this layer rather than to scoring.
Domain vocabulary deserves explicit handling. Enterprise users search with internal acronyms, abbreviations and old names for things that were renamed two years ago. A synonym list, maintained by someone close to the customer, is unglamorous and consistently one of the highest-impact improvements available.
{
"settings": {
"analysis": {
"filter": {
"domain_synonyms": {
"type": "synonym_graph",
"synonyms": ["poa, property owner association", "insp, inspection",
"wo, work order"]
},
"english_stems": { "type": "stemmer", "language": "light_english" }
},
"analyzer": {
"content": {
"tokenizer": "standard",
"filter": ["lowercase", "asciifolding", "domain_synonyms", "english_stems"]
}
}
}
},
"mappings": {
"properties": {
"title": { "type": "text", "analyzer": "content",
"fields": { "exact": { "type": "keyword" } } },
"reference": { "type": "keyword" },
"tenant_id": { "type": "keyword" },
"updated_at":{ "type": "date" }
}
}
}Keeping an unanalysed keyword sub-field alongside the analysed one matters: reference numbers, codes and exact phrases must match exactly, and stemming them produces confident nonsense.
Ranking is more than text similarity
Pure text scoring treats a document nobody has opened in three years the same as the one this user edited an hour ago. Real relevance blends signals: field weighting so a title match outranks a body match, recency, the user's own recent activity, and record status so archived items rank below active ones. Each of those is a product judgement, and each should be tunable without a deploy.
- Always scope by tenant inside the query. A permission filter applied after retrieval both leaks and silently truncates result counts.
- Handle the empty result deliberately — spelling correction, relaxing filters, and showing what was actually searched.
- Facets should reflect the current result set, not the whole corpus; counts that do not match what clicking them produces destroy trust quickly.
- Typeahead is a different problem from search: prefix matching, tight latency budget, and a small set of fields.
- Index asynchronously with an explicit staleness target, and expose it. A record that is not searchable ten minutes after creation is a bug report waiting to happen.
Users do not report bad ranking. They report that your product does not work, and then they stop using search.
Measure it, or you are tuning by anecdote. Click-through position, the proportion of searches with no results, the proportion abandoned without a click, and the queries most often refined immediately after. That last one is the highest-signal list you can look at — a query someone retypes differently within ten seconds is a relevance failure with the correction attached, handed to you by the user.