Building Smart Search Suggestions in Golang using Elasticsearch

VARDAAN MITTAL
Saltside Engineering
4 min readOct 3, 2023

Search suggestions have revolutionized user experience. Remember the last time you typed something in a search bar? It probably offered suggestions before you finished typing. Behind the scenes, technologies like Elasticsearch combined with robust languages like Golang make this magic happen.

Let’s dissect the journey of building an intelligent, real-time, context-aware suggestion system using Golang and Elasticsearch.

Understanding Elasticsearch’s Completion Suggester

At the heart of Elasticsearch’s suggestion capabilities is the completion suggester. While Elasticsearch offers a suite of suggesters like term, phrase, and context, it's the completion suggester that stands out for instantaneous, search-as-you-type experiences. This suggester races ahead because of its ability to load the entire Finite State Transducer (FST) into memory.

Why Golang?

Golang isn’t just another language; it’s a beacon of efficiency in the software realm. Golang’s inherent concurrency model, manifested through goroutines and channels, facilitates swift, uninterrupted communication with Elasticsearch. Such rapid communication is non-negotiable for real-time suggestion systems.

Mapping: The Blueprint in Elasticsearch

Your data structure in Elasticsearch, termed ‘mapping’, forms the backbone of your search outcomes. Let’s delve into a detailed mapping example tailor-made for a hypothetical e-commerce platform.

{
"settings": {
...
},
"mappings":{
"properties":{
"inputs": {
"type": "keyword",
"copy_to": "suggest"
},
"metadata": {
...
},
"suggest" : {
"type" : "completion",
...
}
}
}
}

Here, the highlights include:

  • An inputs field, a straightforward keyword type, which is duplicated to the suggest field.
  • A detailed metadata section capturing data about the product to be used later in querying for Search.
  • The suggest field, the center of our suggestion mechanism, structured for the completion suggester and enriched with contextual information.

Molding Data with Golang

To make our data understandable for Golang, we model it using relevant structs which echo our Elasticsearch mappings.

type Suggestions struct {
Inputs string `json:"inputs"`
Metadata Metadata `json:"metadata"`
Suggest []Suggest `json:"suggest"`
}

...

Building Bridges: The Elasticsearch Client in Golang

Our next move is to ensure Golang and Elasticsearch converse seamlessly.

package main

import (
"context"
"log"
"github.com/olivere/elastic/v7"
)

func main() {
client, err := elastic.NewClient(elastic.SetURL("http://localhost:9200"))
if err != nil {
log.Fatalf("Connection hurdle: %v", err)
}
// Next steps: indexing, data retrieval, and more...
}

With this foundational client setup, doors swing open to indexing data, retrieving suggestions, and performing a bucket of operations.

Dynamically Refining with Query-time Boosting

The power to adjust the prominence of suggestions dynamically is a game-changer. Consider the example of promoting a freshly launched product line in search suggestions. Boosting lets you modulate this prominence temporarily, ensuring users see what you want them to see.

Using the Elasticsearch Golang client, you can create sophisticated suggestion queries that harness the power of contexts and boosts.

func getSuggestionsWithBoost(client *elastic.Client, input string)
([]string, error) {
boostValue := 5

suggester := elastic.NewCompletionSuggester("suggest").
Text(input).
ContextQuery(elastic.NewSuggesterCategoryQuery("brand",
childCategoryBoost).Boost(boostValue)).
Size(10)

searchResult, err := client.Search("suggestions").
Suggester("suggestions", suggester).
Do(context.Background())

if err != nil {
return nil, err
}

suggestions := searchResult.Suggest["suggestion"]
var results []string
for _, entry := range suggestions {
for _, option := range entry.Options {
results = append(results, option.Text)
}
}
return results, nil
}

Here’s a practical example of boosting a specific category:

{
"_source": ["suggest"],
"suggest": {
"product-suggestion": {
"prefix": "Nike",
"completion": {
"field": "suggest",
"contexts": {
"category_type": [
{
"context": "brand",
"boost": 5
}
]
}
}
}
}
}

The result? Products under the brand category will be prioritized due to the boost, ensuring they dominate the suggestions.

Multiple Inputs for the Same Context

Often, different users might employ diverse terminologies or phrasings for the same product or category. To cater to this variability, Elasticsearch allows us to index multiple inputs for the same context.

Consider our PlayStation scenario. Some users might search for PS4, while others might just type in Playstation or even Sony Playstation.

Indexing Data with Multiple Inputs for a Single Context:

{
"inputs": ["ps4", "playstation", "Sony Playstation"],
...
"suggest": {
"input": ["ps4", "playstation", "Sony Playstation"],
"contexts": {
"type": "brand",
"value": "Sony Playstation",
}
}
}

In this data structure, the product “Sony Playstation” has been associated with multiple inputs but belongs to the single context.

Fetching Suggestions with Varied Inputs:

Now, regardless of how a user starts their search — be it “ps4”, “playstation”, or even “Sony Playstation” — our system will recognize the intent and suggest the “Sony Playstation”.

{
"_source": ["suggest"],
"suggest": {
"bike-suggestion": {
"prefix": "ps4",
"completion": {
"field": "suggest",
"contexts": {
"value": "Sony Playstation"
}
}
}
}
}

Executing this query will yield “Sony Playstation” as a suggestion, even if the user started typing only a fragment of the name. Embracing multiple inputs for the same context ensures your search system is robust, understanding, and user-centric. It recognizes the natural variability in human search patterns and provides relevant suggestions, irrespective of the terminology employed.

Conclusion:

Crafting an efficient suggestion system transcends merely displaying possible matches. It’s an art of understanding user intent, the context of their requirements, and delivering apt suggestions instantaneously. By harmonizing Elasticsearch’s capabilities with Golang’s power, developers can architect a suggestion ecosystem that’s not just smart and responsive, but also deeply contextual.

--

--

VARDAAN MITTAL
Saltside Engineering

Golang developer specializing in microservices. Building robust, scalable backends and delving deep into concurrent system design.