What Is k-Nearest Neighbor (kNN) Search in Elasticsearch?
A complete, beginner-friendly guide to searching by meaning instead of by exact words: how text and images become lists of numbers, how Elasticsearch finds the closest matches quickly, and how to tune, secure and operate it in production.
Imagine you walk into an enormous library and ask the librarian for “a book about a lonely robot who learns to love gardening.” A very literal librarian would hunt for books whose titles contain those exact words and come back empty-handed. A wise librarian, however, understands what you mean. She remembers a story about a rusty machine that tends a rooftop garden and hands it to you at once, even though not one of your words appears in its title. Ordinary keyword search behaves like the literal librarian. k-nearest neighbor search behaves like the wise one.
In this tutorial we start from the very beginning. We will see why matching words is sometimes not enough, how computers turn meaning into numbers, what it means for two things to be “near” each other, and how Elasticsearch finds the closest matches among millions of candidates in a few milliseconds. Then we will learn how to set it up, tune the trade-offs between speed and accuracy, combine it with ordinary search, keep memory under control, secure it, monitor it and avoid the mistakes that catch most beginners.
1The Big Picture: Searching by Words Versus Searching by Meaning
To appreciate kNN search, we first need to see what ordinary search does well and where it runs out of road.
How keyword search works
Traditional Elasticsearch search is built on the inverted index, which lists, for every word, the documents that contain it. When you search for “cheap laptop,” Elasticsearch finds documents containing those words and ranks them with a scoring formula that rewards rare words, frequent occurrences and short fields. This approach, often called lexical search, is fast, precise and easy to explain. If a document contains your words, it can be found.
The weakness is that lexical search only understands words, not ideas. A document about an “inexpensive notebook computer” describes exactly what the user wants, yet it shares no important words with the query. Synonyms, paraphrases, spelling variations, questions phrased in everyday language, and images all fall outside what an inverted index can handle without heavy manual effort.
Think of two ways to find a friend in a crowded school hall. Keyword search is shouting their exact name and hoping they hear it. Meaning-based search is looking for someone who wears the same kind of jacket, walks the same way and stands with the same group. You may find them even if you forgot their name.
The idea behind searching by meaning
The trick is to describe every document, and every question, as a point in a kind of map where similar meanings sit close together. A sentence about laptops lands near other sentences about laptops. A photo of a beach lands near other beach photos. Once everything is a point on this map, finding relevant results becomes a geometry problem: given the point for the question, find the closest points. Those closest points are the nearest neighbors.
graph LR
subgraph Lexical["Keyword search"]
Q1["Query words"] --> II["Inverted index: word to documents"]
II --> R1["Documents containing the words"]
end
subgraph Semantic["kNN search"]
Q2["Query turned into a vector"] --> VI["Vector index: points on a map"]
VI --> R2["Documents whose meaning is closest"]
end
Lexical search
Finding documents by the words they contain, using the inverted index and relevance scoring.
Semantic search
Finding documents by meaning, so a query can match text that uses completely different words.
Vector
A list of numbers that describes something, such as a sentence or an image, as a point in space.
Nearest neighbor
The stored item whose vector is closest to the vector of the query.
kNN search does not replace keyword search. Each is strong where the other is weak, and the best production systems often use both together, as we will see in the chapter on hybrid search.
2Vectors and Embeddings: Turning Meaning into Numbers
Everything in kNN search depends on one idea: describing things with lists of numbers.
What is a vector?
A vector is simply an ordered list of numbers. You already know a tiny one: a location on a map can be described by two numbers, how far east and how far north. A point in a room needs three numbers. In machine learning, vectors are much longer, often hundreds or even thousands of numbers, and each number captures some faint aspect of the thing being described. Nobody labels the aspects by hand; the model discovers them during training.
What is an embedding?
An embedding is a vector produced by a machine learning model for a piece of data such as a sentence, a paragraph, a product photo or an audio clip. The model has been trained on huge amounts of examples, so that things with similar meaning receive similar lists of numbers. The word “car” and the word “automobile” get vectors that are very close, while “car” and “banana” get vectors that are far apart.
Imagine describing every fruit using a few sliders: how sweet, how sour, how crunchy, how juicy, how big. An apple might be sweet 6, sour 4, crunchy 9, juicy 6. A pear would have similar slider settings, so it sits near the apple. A lemon has very different settings, so it sits far away. An embedding is like this, but with hundreds of sliders chosen automatically by a model.
Dimensions
The number of values in a vector is called its number of dimensions. Common text embedding models produce vectors with a few hundred to a couple of thousand dimensions. More dimensions can capture finer detail but cost more memory and more computing effort. Every vector in the same field must have exactly the same number of dimensions, and the query vector must match too.
flowchart LR
T["Text: cheap laptop for students"] --> M["Embedding model"]
M --> V["Vector: 0.12, -0.53, 0.88, ... hundreds more"]
P["Photo of a beach"] --> M2["Image embedding model"]
M2 --> V2["Vector: 0.71, 0.02, -0.34, ..."]
Vectors are only comparable when they come from the same model. A vector from one model and a vector from another model live on entirely different maps, and measuring the distance between them is meaningless. Always create stored vectors and query vectors with the same model and the same settings.
3Measuring “Near”: Similarity and Distance
To find the nearest neighbors, we need a clear rule for how near two vectors are.
Three popular ways to measure
There are several ways to measure closeness, and Elasticsearch supports the main ones. Each is a small formula that takes two vectors and returns one number. Choosing the wrong one for your model gives poor results, so it is important to know what each means and which your embedding model expects.
Cosine similarity
Compares the direction two vectors point in, ignoring their length. Values near 1 mean very similar direction.
Dot product
Multiplies matching numbers and adds them up. For vectors scaled to unit length it behaves like cosine similarity and is faster.
L2 (Euclidean) distance
The straight-line distance between two points. Smaller means closer, like measuring with a ruler.
Maximum inner product
A dot product variant for vectors that are not scaled to unit length, used by some recommendation models.
Imagine two friends pointing at things from the middle of a field. Cosine similarity asks, “Are you both pointing in the same direction?” and does not care how far each arm reaches. Euclidean distance asks, “How far apart are your fingertips?” A long arm and a short arm pointing the same way are close under cosine, but not necessarily close under distance.
Which one should I use?
Use the metric recommended by the documentation of your embedding model. Many text models are trained with cosine similarity or with dot product on normalized vectors. Normalized means each vector has been scaled to a length of exactly one. When vectors are normalized, cosine similarity and dot product give the same ranking, and dot product is cheaper to compute, so it is a favorite for performance.
| Metric | Bigger value means | Best when | Notes |
|---|---|---|---|
| Cosine | More similar | Model recommends cosine; vectors may vary in length | Safe default for most text models |
| Dot product | More similar | Vectors are normalized to unit length | Fast; requires unit-length vectors in Elasticsearch |
| L2 distance | Farther apart (smaller is better) | Model trained with Euclidean distance | Elasticsearch converts it into a score where closer is higher |
| Maximum inner product | More similar | Unnormalized vectors from recommendation models | Scores can be unbounded in raw form |
Choosing dot product for vectors that were not normalized gives results that favour vectors with large lengths rather than similar meaning. If in doubt, use cosine, which handles the scaling for you.
4So, What Exactly Is kNN Search?
Now we can put the pieces together into a precise definition.
A precise definition
k-nearest neighbor search, written kNN, is the task of finding the k stored items whose vectors are closest to a given query vector, according to a chosen similarity measure. The letter k is simply how many results you want, such as the ten nearest neighbors. In Elasticsearch, the stored items are documents with a vector field, the query vector represents the user’s question, and the result is a ranked list of the k most similar documents.
The name comes from an old and simple idea in data science: to decide something about a new item, look at the items nearest to it. If you want to guess what kind of film someone will like, look at the films closest to the one they just enjoyed. That basic idea powers modern semantic search, recommendations and question answering.
You arrive at a new school and want to find classmates with similar hobbies. You stand in the playground and look at the ten students standing closest to you. If the playground is arranged so that people with similar interests stand together, those ten nearest students are your best matches. The playground arrangement is the embedding, and looking at the ten nearest is kNN.
graph TD
Q["Query point"] --> N1["Neighbor 1: distance 0.10"]
Q --> N2["Neighbor 2: distance 0.14"]
Q --> N3["Neighbor 3: distance 0.19"]
Q -.-> F1["Far point: distance 0.90"]
Q -.-> F2["Far point: distance 1.10"]
What kNN search is used for
Semantic text search
Finding documents that answer a question even when they share no words with it.
Recommendations
Suggesting products, articles or songs similar to something a user liked.
Image and multimedia similarity
Finding photos that look like a sample photo, or matching an image to a text description.
Retrieval for language model applications
Finding the most relevant passages to hand to a language model so it can answer with facts from your own data.
kNN always returns k results, however poor the best matches are, unless you also set a similarity threshold. It answers “what is closest?” and not “what is truly relevant?” That difference matters when your data has no good answer for a query.
5Exact kNN and Why Brute Force Does Not Scale
The simplest way to find nearest neighbors is to check everything. It is accurate, and it is also slow at scale.
The brute-force method
Exact kNN, sometimes called brute force or flat search, compares the query vector with every stored vector, computes the similarity for each, and keeps the best k. There is no cleverness involved, which is its strength: the answer is always perfectly correct, and there is no index to build or tune. For small collections, or when a filter has already reduced the candidates to a few thousand documents, it is an excellent choice.
Why it breaks down
The work grows in direct proportion to the number of documents and the number of dimensions. Comparing one query against ten thousand vectors of 768 dimensions is quick. Comparing it against fifty million vectors means tens of billions of arithmetic operations for a single query, and that is far too slow if hundreds of users search at once. Reading all those vectors from memory or disk adds delay as well.
| Number of vectors | Dimensions | Approximate comparisons per query | Practical feel |
|---|---|---|---|
| 10,000 | 768 | About 7.7 million | Fast, fine for exact search |
| 1,000,000 | 768 | About 770 million | Noticeably slow for interactive use |
| 50,000,000 | 768 | About 38 billion | Impractical without an index |
Finding the ten shirts most like your favourite by walking through a warehouse and holding every single shirt against yours is perfectly accurate. It is fine for a wardrobe of twenty shirts. It is hopeless for a warehouse of ten million.
When exact search is the right tool
Small collections, filters that leave only a modest set of candidates, evaluation runs that need ground truth, and workloads where perfect accuracy matters more than speed.
When you need something smarter
Collections of hundreds of thousands to billions of vectors, interactive latency targets of tens of milliseconds, and many simultaneous users.
Even if you use approximate search in production, keep an exact search around for testing. Comparing the two on sample queries tells you how much accuracy the approximation costs.
6Approximate Nearest Neighbors and the HNSW Graph
To go fast, we accept a tiny chance of missing the perfect answer in exchange for looking at only a small part of the data.
The bargain: approximate search
Approximate nearest neighbor search, or ANN, finds neighbors that are very likely, though not guaranteed, to be the true nearest ones. By building a special index structure in advance, it can find good answers while examining only a tiny fraction of the vectors. The measure of how often it finds the true nearest neighbors is called recall. A recall of ninety-five percent means that, on average, ninety-five of the true top hundred neighbors are found.
How HNSW works
Elasticsearch uses an algorithm called HNSW, short for Hierarchical Navigable Small World. It organises vectors into a graph, a network in which each vector is connected to a handful of its nearby friends. Searching the graph means starting somewhere, looking at the friends of the current point, stepping to whichever friend is closer to the query, and repeating until no closer friend can be found.
The hierarchical part adds layers. The top layer contains only a few vectors with long-distance links, like motorways. Lower layers contain more vectors with shorter links, like main roads, and the bottom layer contains every vector with local links, like streets. A search starts at the top, takes big strides toward the right neighborhood, then drops down a layer at a time, taking smaller and smaller steps until it reaches the streets around the target.
Suppose you want to reach a specific house in a foreign city. You do not check every house. You take a plane to the right region, then a motorway to the right town, then a main road to the right district, and finally walk the streets to the door. HNSW does the same thing with vectors, using fewer and fewer steps at each level.
graph TD
L2["Layer 2 (few points, long links)"] --> L1["Layer 1 (more points, medium links)"]
L1 --> L0["Layer 0 (all points, short links)"]
L0 --> R["Closest neighbors found"]
Q["Query vector"] --> L2
The two build-time knobs
m
How many neighbors each point is linked to. Higher values build a richer graph with better recall but more memory and slower indexing.
ef_construction
How many candidates are examined when linking each new point during indexing. Higher values improve graph quality but slow down indexing.
The defaults for these knobs are chosen to work well for many datasets. Start with them, measure recall on your own data, and adjust only if you see a clear need.
7How Elasticsearch Stores and Searches Vector Graphs
The details of storage explain nearly every performance and memory question you will meet later.
One graph per segment
Elasticsearch stores each index in shards, and each shard is made of immutable pieces called segments. The HNSW graph is built separately for each segment. When new documents are written and a new segment is created, a new graph is built for it. When segments are merged in the background, the graphs of the merging segments are combined into a new graph for the merged segment, which is real work for the node.
This has two consequences. First, a search must visit the graph in every segment of every shard involved, so having many small segments makes searches slower. Second, indexing vectors is more expensive than indexing plain fields, because building and merging graphs takes processing time.
Vectors live outside the Java heap
The vectors and the graph structure are stored in files on disk and are read through the operating system’s file cache, not held in the Java heap. For fast searches, the parts of the graph that queries touch should be present in that cache, because reading them from disk for every hop would be slow. This is why memory planning for kNN is about leaving enough free memory outside the heap for the file cache, a point we will return to in the capacity planning chapters.
Each segment is like a small map booklet with its own road network. To find a place, you must look in every booklet. Merging booklets means drawing a fresh road network for the combined book, which takes effort. And the booklets should be open on your desk, the file cache, not locked in a distant cupboard, the disk.
graph LR
subgraph Shard["One shard"]
S1["Segment 1: vectors and HNSW graph"]
S2["Segment 2: vectors and HNSW graph"]
S3["Segment 3: vectors and HNSW graph"]
end
Q["kNN query"] --> S1
Q --> S2
Q --> S3
S1 --> M["Merge best candidates"]
S2 --> M
S3 --> M
Graphs are per segment
More segments mean more graphs to search and more merge work in the background.
Off-heap storage
Vectors and graphs are read through the operating system’s file cache rather than the Java heap.
Deletes are lazy
Updated or deleted documents leave stale entries until segments merge, which can slow searches slightly.
Indexing costs more
Building the graph makes vector indexing slower and more processor-hungry than ordinary fields.
Giving almost all of a machine’s memory to the Java heap leaves nothing for the file cache, so vector searches keep reading from disk and become slow. For vector workloads, a moderate heap and a large free memory area for the cache is the healthier arrangement.
8Mapping a Vector Field: The dense_vector Type
Before you can search vectors, you must tell Elasticsearch which field holds them and how to treat them.
The field type
Vectors are stored in a field of type dense_vector. The word dense means that every position in the list holds a value, as opposed to sparse representations in which most positions are empty. You declare this in the index mapping, along with a few properties that control how the field behaves.
dims
The number of values in each vector. It must match your model exactly, and every document in the field must use it.
similarity
The metric used to compare vectors: cosine, dot product, L2 distance or maximum inner product.
index
Whether vectors are indexed into an HNSW graph for fast approximate search. Without it, only exact search is possible.
index_options
Choice of index structure and its parameters, such as graph type, the m and ef_construction knobs, and quantization.
element_type
How each number is stored, for example as a decimal number, a byte, or a single bit, trading precision for space.
Decisions you cannot easily undo
Several choices in the mapping are effectively permanent for an index. The number of dimensions cannot be changed once documents exist. The similarity metric is part of how the graph is built, so changing it means reindexing. Because of this, it is wise to test with a small sample of data and your chosen model before creating a large production index.
Choosing dims and similarity is like deciding the size and layout of a filing cabinet before putting any files in. If the drawers are the wrong size or organised by the wrong rule, you must move every file into a new cabinet.
| Decision | Can it be changed later? | How to change it |
|---|---|---|
| Number of dimensions | No, not on an existing field with data | Create a new index and reindex with new vectors |
| Similarity metric | Generally no | Create a new index and reindex |
| Index structure and quantization | Limited, depending on version | Usually requires reindexing into a new mapping |
| Embedding model | Not without regenerating all vectors | Re-embed all data and reindex |
Keep the name of the embedding model and its version in your index metadata or in a field of each document. Months later, when somebody asks why results look odd, this note is invaluable.
9Getting Embeddings into Elasticsearch
Elasticsearch stores and searches vectors, but the vectors have to come from somewhere. There are several routes.
Route one: create vectors outside and send them in
You can run an embedding model in your own application, or call an external service, and then index each document together with its vector. This gives you complete freedom over the model, and it is the approach used when you have a specialised or fine-tuned model. The responsibility for keeping the model, its version and its dimensions consistent lies with you.
Route two: let Elasticsearch call a model
Elasticsearch has an inference feature that connects to models, either hosted inside the cluster on machine learning nodes or provided by external services. An ingest pipeline can call the model for each incoming document and store the resulting vector automatically. At query time, the same model can convert the user’s text into a vector on the fly, so the application sends plain text and never handles vectors at all.
Route three: semantic fields
Newer versions offer a semantic text field type that handles much of the work automatically. It splits long text into chunks, creates embeddings for each chunk using a configured inference endpoint, and stores them for searching. This reduces the amount of code needed for the common case of semantic search over text.
| Route | Who creates vectors | Strength | Trade-off |
|---|---|---|---|
| External application | Your code or an outside service | Full control over the model | You maintain consistency and the pipeline |
| Ingest pipeline with inference | Elasticsearch calling a model | Automatic embedding at index time and query time | Model load and latency on the cluster or a service |
| Semantic text field | Elasticsearch, with chunking handled for you | Least code for text search | Less low-level control; depends on version |
Think of translating documents into a secret code so they can be filed on a special map. You can do the translation yourself, hire a translator who works in the office, or install a machine at the front door that translates everything that comes in. Whichever you choose, the same translator must handle both the documents and the visitors’ questions.
sequenceDiagram
participant App as Application
participant ES as Elasticsearch
participant Model as Embedding model
App->>ES: Index document text
ES->>Model: Create embedding for the text
Model-->>ES: Vector
ES-->>App: Stored with vector
App->>ES: Search with question text
ES->>Model: Create embedding for the question
Model-->>ES: Query vector
ES-->>App: Nearest documents
Embedding models handle a limited amount of text at once. A whole book squeezed into one vector loses detail. Split long documents into sensible passages, embed each, and store them so that the best passage, not just the best document, can be found.
10Running a kNN Search: The Parts of the Request
A kNN search request has only a handful of parts. Understand each and you can read any example.
field
The name of the dense_vector field to search.
query_vector
The vector for the question, created by the same model as the stored vectors.
query_vector_builder
An alternative to a ready-made vector: give text and a model, and let Elasticsearch create the vector.
k
How many nearest neighbors to return overall.
num_candidates
How many candidates each shard examines before the best k are chosen. Larger values raise accuracy and cost.
filter
An optional condition that documents must meet, applied while the graph is searched.
similarity
An optional minimum similarity a result must reach to count as a match, in the units of the chosen metric.
Reading a request in words
In plain language, a typical request says: “In the field called description_vector, find the ten documents closest to this query vector, considering fifty candidates on each shard, and only among documents whose category is shoes.” The answer comes back in the usual search response format, with the hits ordered by score, and each hit carrying its document source. You can request only some fields, add highlighting to other parts of a combined request, and use all the familiar options for paging and sorting.
It is like asking a shop assistant: “From the shoes department only, show me the ten items most like this photo. Look through at least fifty of the closest-looking ones on each shelf before you decide.” Each part of the request matches a part of that sentence.
A knn option and a knn query
Elasticsearch offers kNN as a top-level option of the search request and also as a query type that can be placed inside the query section. The top-level option is the classic route and is well suited to standalone kNN or to combining kNN with ordinary queries. The query form makes it possible to use kNN inside compound queries and other places where queries are accepted. Both use the same underlying graph search.
Names and availability of some request forms vary between versions. When following an example from the web, check that the version matches yours, and consult the documentation for the exact syntax supported by your cluster.
11How a kNN Search Runs Across the Cluster
Following one request through the cluster shows where time and memory are spent.
The request arrives
A coordinating node receives the request. If a model is configured to embed the text, it first creates the query vector.
The query goes to every relevant shard
Each shard searches its own data, so the request is sent to one copy of each shard of the target indices.
Each shard searches its segments
Inside a shard, the graph of each segment is searched, applying any filter as it goes, and the best candidates from all segments are combined.
Each shard returns its top candidates
A shard returns up to the requested number of candidates, along with their similarity scores.
The coordinating node picks the global best
Candidates from all shards are merged and the best k overall are selected and ordered by score.
Documents are fetched and returned
The winners’ documents are fetched from their shards and sent back to the client in the usual response format.
sequenceDiagram
participant C as Client
participant N as Coordinating node
participant S1 as Shard 1
participant S2 as Shard 2
C->>N: kNN request (k=10, num_candidates=50)
N->>S1: Search graphs, return top candidates
N->>S2: Search graphs, return top candidates
S1-->>N: 50 candidates with scores
S2-->>N: 50 candidates with scores
N->>N: Merge 100 candidates, choose best 10
N->>S1: Fetch documents
N->>S2: Fetch documents
N-->>C: Top 10 hits
Imagine a national talent contest with regional heats. Each region sends its fifty best performers to the final round, where judges pick the overall top ten. The more regions there are, the more performers reach the final, and the more the final round costs.
Because every shard is searched and each returns its own candidates, the total work grows with the number of shards. Vector indices often perform best with fewer, larger shards, within the limits of your resilience and recovery needs.
12k Versus num_candidates: The Speed and Accuracy Dial
Two numbers in every request quietly control the balance between fast answers and accurate answers.
What each number does
The number k is the size of the final answer: how many results the user will receive. The number num_candidates is how many nearest neighbor candidates each shard considers internally before it hands its best ones to the coordinating node. Candidates are the pool from which the answer is picked. A larger pool means the search explores more of the graph and is more likely to include the true nearest neighbors, but it also visits more vectors and therefore takes longer.
The rule is that num_candidates must be at least as large as k, and there is an upper limit on how large it can be. In practice, a value several times larger than k is common. Raising it is the simplest lever for improving recall when results look poor, and lowering it is the simplest lever for improving speed when latency is too high.
Imagine picking the three best cakes at a fair. If you only taste the first three you see, you might miss the best one. If you taste twenty cakes, you are far more likely to find the true winners, but tasting takes longer. k is how many prizes you give. num_candidates is how many cakes you taste.
| Change | Effect on accuracy | Effect on speed | When to use |
|---|---|---|---|
| Raise num_candidates | Higher recall | Slower, more vectors visited | Results miss obviously relevant items |
| Lower num_candidates | Lower recall | Faster | Latency is too high and recall is comfortably good |
| Raise k | More results returned | Slightly slower | Need more results for paging or reranking |
| Raise the number of shards | More candidates overall | More work per query | Only for capacity, not for accuracy tuning |
A tuning recipe
Collect representative queries
Gather a few hundred real or realistic queries that reflect how users will search.
Compute ground truth
Run exact search for each query to find the true nearest neighbors.
Sweep values
Run approximate search with several values of num_candidates, measuring recall against the ground truth and latency at each value.
Pick the knee of the curve
Choose the smallest value that gives recall you can accept. Beyond that point, extra cost buys very little.
Recall usually rises quickly at first, then flattens. Doubling num_candidates from a low value may help a lot, while doubling it from an already high value may change almost nothing but the bill.
13Filtering: Combining Similarity with Conditions
Real applications almost never want “the nearest thing overall.” They want the nearest thing that also meets some condition.
Why filtering is tricky in vector search
Suppose a shopper wants shoes similar to a photo, but only in their size and only in stock. There are two obvious ways to combine similarity with conditions. One is post-filtering: find the nearest neighbors first, then throw away those that fail the condition. The trouble is that you may throw away most or all of them and end up with too few results, or none.
The other is pre-filtering: apply the condition first, and find the nearest neighbors only among the documents that pass. This guarantees that the requested number of matching results are returned, as long as enough documents match. Elasticsearch supports this by letting you place a filter inside the kNN search, so the graph search itself respects the condition as it walks.
Post-filtering is like asking for the ten prettiest flowers in a garden and only afterwards noticing that nine of them are the wrong colour. Pre-filtering is like first walking only along the row of red flowers and then choosing the ten prettiest ones there.
flowchart LR
subgraph Post["Post-filtering: risky"]
A1["Find 10 nearest"] --> A2["Remove non-matching"] --> A3["Maybe only 2 remain"]
end
subgraph Pre["Filter inside the kNN search: safer"]
B1["Search graph among matching documents only"] --> B2["Return 10 matching neighbors"]
end
What happens behind the scenes
While walking the graph, the search simply ignores documents that do not pass the filter when collecting results, though it may still pass through them on the way. If the filter is extremely restrictive and matches only a small number of documents, walking the graph can become less efficient than simply checking those few documents directly, and the engine may fall back to an exact comparison over them. This is often a pleasant surprise: highly selective filters can make searches both faster and perfectly accurate.
Filter inside kNN
Use for conditions that must hold, such as stock status, language, tenant or access rights.
Post filter
Use only when you deliberately want filtering to affect other parts of a combined request, such as facet counts, without changing which neighbors are found.
Separate indices per tenant
For strong isolation and very small filtered sets, separate indices or routing can be simpler than a filter.
If a condition protects information, such as which tenant may see which document, do not rely on post-filtering it away. Use proper access controls, and apply tenant conditions inside the search so that documents of other tenants never enter the candidate pool.
14Scores and Similarity Thresholds
Elasticsearch ranks all results by a relevance score, so raw similarity values are converted into scores that are always positive.
From similarity to score
Elasticsearch requires scores to be non-negative, and higher scores mean better matches. For cosine similarity and dot product, the raw value can be negative, so it is shifted and scaled: one is added to the similarity and the result is halved, mapping the range from minus one to plus one into the range from zero to one. For L2 distance, the score is one divided by one plus the squared distance, so a distance of zero gives a perfect score of one and greater distances give smaller scores.
These conversions keep the ordering the same as the raw metric. The closest vector always has the highest score. What changes is the scale, which matters when you combine kNN scores with scores from ordinary queries.
| Metric | Raw value range | How it becomes a score |
|---|---|---|
| Cosine | Minus 1 to 1 | One plus the similarity, divided by two |
| Dot product (unit vectors) | Minus 1 to 1 | One plus the similarity, divided by two |
| L2 distance | 0 upward | One divided by one plus the squared distance |
| Maximum inner product | Unbounded | Rescaled to stay non-negative |
The similarity threshold
Because kNN always returns the k closest items, you might receive poor matches when nothing good exists. The similarity option lets you set a minimum quality: documents less similar than the threshold are not counted as matches, even if they are among the k nearest. Note that the threshold is expressed in the raw units of the metric, not in the converted score. For cosine similarity it is a cosine value between minus one and one; for L2 distance it is a maximum distance.
If you ask a friend for the three restaurants nearest to you, they will always name three, even if the nearest is a two-hour drive away. Adding “but only if it is within ten minutes” is the similarity threshold. Sometimes the honest answer is “nothing close enough.”
Similarity values are not universal. A cosine of 0.8 may mean “very related” for one model and “loosely related” for another. Look at real examples from your data and choose thresholds by inspection, not by guesswork.
15Hybrid Search: Combining kNN with Keyword Search
Meaning and words each catch things the other misses, so the best systems use both.
Why hybrid?
Semantic search is excellent at finding paraphrases and related ideas, but it can stumble on exact terms such as product codes, names, error numbers and rare jargon, which an embedding model may blur. Lexical search nails exact terms but misses paraphrases. Hybrid search runs both and combines the results, so that a document that ranks well on either measure has a chance to appear, and documents strong on both rise to the top.
Ways to combine
The simplest method is to include both a kNN section and a normal query in one request. Elasticsearch adds the scores together, and each part can be given a boost to control how much it matters. Because the two scores are on different scales, tuning boosts takes some experimentation. A more robust method is reciprocal rank fusion, which ignores raw scores and combines results using only their positions in each ranked list: an item near the top of either list receives a good combined rank. Retrievers, a newer request style, let you describe such combinations clearly and even chain reranking steps.
Two judges score a talent show. One judge is strict about technical skill, the other about creativity. Adding their scores, or combining their rankings, gives a fairer winner than trusting either alone. A performer who impresses both judges beats one who impresses only one.
graph TD
Q["User question"] --> L["Keyword query on text fields"]
Q --> E["Embedding of the question"]
E --> K["kNN search on vector field"]
L --> F["Combine: weighted scores or rank fusion"]
K --> F
F --> R["Final ranked results"]
| Combination method | How it works | Strength | Watch out for |
|---|---|---|---|
| Weighted score sum | Adds the keyword score and the kNN score, each with a boost | Simple and direct | Scores differ in scale, so boosts need tuning |
| Reciprocal rank fusion | Combines based on rank positions from each list | No score calibration needed | Ignores how big the score gaps are; availability depends on version and subscription |
| Rerank after retrieval | A second model reorders the merged candidates | Can greatly improve top results | Adds latency and cost per query |
When keywords must win
Searching for an order number or an error code should rely mainly on exact matches, so give the keyword part more weight or a strict filter.
When meaning should lead
Natural-language questions such as “how do I reset my password when I lost my phone” benefit from semantic retrieval, with keywords as a supporting signal.
If you already have a good keyword search, adding kNN as a second retriever is usually a smaller risk than replacing your search entirely. Measure the change on real queries and increase the weight of the vector part as evidence supports it.
16Exact kNN with Scripted Scoring
Sometimes brute force is exactly what you want, and Elasticsearch supports it directly.
How it works
Besides the graph-based search, Elasticsearch can compute vector similarity inside a scripted scoring query. A normal query first selects a set of documents, and then a script computes the similarity between the query vector and each selected document’s vector, using built-in functions for cosine similarity, dot product and distances. Because every selected document is scored, the result is exact.
This approach does not need the graph index at all, so it works with vector fields that are not indexed for approximate search. Its cost is proportional to the number of documents scored, which means it is only sensible when the pre-selected set is modest, such as a few thousand documents narrowed down by a strong filter.
Choosing the best-fitting shoe from the five pairs on your shelf, by trying each on, is perfectly reasonable. Trying on every shoe in the city is not. The strong filter is what reduces the whole city to five pairs.
| Aspect | Approximate kNN (graph) | Exact kNN (scripted scoring) |
|---|---|---|
| Accuracy | High but not perfect | Perfect for the documents scored |
| Speed on large data | Fast | Slow, grows with documents scored |
| Needs a graph index | Yes | No |
| Best for | Large collections | Small or heavily filtered sets, and ground-truth testing |
| Flexibility of scoring | Standard similarity | Custom formulas mixing several signals |
A scripted similarity applied to a whole large index, with no filter, is one of the quickest ways to make a cluster crawl. Always confirm that the query narrows the documents before the script runs.
17Chunking and Multiple Vectors per Document
Real documents are long, and one vector rarely captures a whole long document well.
Why chunking helps
An embedding model compresses text into a fixed-size vector. For a short sentence, the vector can be rich and precise. For a long report, the single vector is a blurry average of many topics, and a question about one detail may not match it well. The remedy is chunking: split the document into passages of a sensible size, embed each passage, and search over passages.
Good chunks respect natural boundaries such as paragraphs and sections, are small enough for the model to represent faithfully, and often overlap slightly so that an idea split across a boundary is still captured whole in at least one chunk. Each chunk should keep a reference to its parent document, so that results can be grouped and displayed properly.
Ways to store chunks
One document per chunk
Each chunk is its own Elasticsearch document with a parent identifier. Simple, and it scales well, but results must be grouped by parent.
Nested chunks
Chunks are stored as nested objects inside the parent document, each with its own vector. The best chunk decides the parent’s rank, and matching chunks can be returned.
Semantic field handling
A semantic text field chunks and embeds for you, hiding much of the bookkeeping, where your version supports it.
A single sentence summarising an entire textbook is too vague to help you find a formula. Indexing every page separately, and remembering which book each page came from, lets you jump straight to the right page. Chunking is indexing by page instead of by book.
Question answering over manuals
Each manual is split into short sections. The user’s question retrieves the best three sections, which are shown with a link back to the whole manual.
Products with several photos
Each product has several image vectors. A search by photo matches the closest image of any product and ranks the product by its best match.
There is no single best chunk size. Try a few, measure retrieval quality on real questions, and choose the size that finds the right passage most often without splitting ideas.
18Shrinking Vectors: Quantization and Memory Savings
Vectors are big. Quantization is the main tool for making them smaller without ruining search quality.
The size of a vector
A typical vector stores each number as a four-byte decimal value. A vector with 768 dimensions therefore occupies about three thousand bytes, roughly three kilobytes. A collection of ten million such vectors holds around thirty gigabytes of raw vector data before counting the graph. To search quickly, the hot parts of that data should sit in the file cache, so large collections demand large amounts of memory.
What quantization does
Quantization stores each number with less precision, using fewer bits. Instead of a four-byte decimal, a number might be squeezed into a single byte, a half byte or even a single bit. Comparing the compressed vectors is much faster and needs far less memory, at the cost of slightly less precise similarity values. Elasticsearch typically keeps the original full-precision vectors on disk too, so that a final rescoring step can correct the small errors for the top candidates.
A photo taken at full resolution looks perfect but takes lots of space. A smaller version is nearly as recognisable and loads instantly. You could use the small copies to find likely matches quickly and only open the full-size originals for the final few. Quantization does the same with vectors.
| Storage style | Bytes per number (approximate) | Memory saving for hot data | Accuracy impact |
|---|---|---|---|
| Full-precision float | 4 | None | Highest |
| 8-bit integer quantization | 1 | About four times smaller | Small, often negligible with rescoring |
| 4-bit integer quantization | 0.5 | About eight times smaller | Noticeable on some data, often acceptable |
| Binary style quantization | 0.125 | Up to about thirty-two times smaller | Larger, best with rescoring and suitable models |
Quantization reduces the memory needed to keep the hot search data cached, but the full-precision originals are usually still stored on disk, so total disk use can even grow slightly. Judge the saving by memory footprint and query speed, not by index size alone.
The accuracy cost of quantization depends on your model and data. Compare recall with and without it on your own queries. Availability and defaults of the various quantization options depend on your Elasticsearch version.
19Capacity Planning: A Worked Memory Example
A little arithmetic tells you whether a plan is realistic before you buy any machines.
The scenario
A company wants semantic search over ten million product descriptions. Their embedding model produces 768-dimensional vectors, and each vector is stored as four-byte decimals. They want fast searches, meaning the vectors should live in the file cache. Let us estimate what that requires.
Raw vector size
Ten million vectors, times 768 numbers, times four bytes, is about thirty-one billion bytes, roughly thirty gigabytes.
Add the graph
Each point stores links to its neighbors, adding a modest overhead that grows with the m setting. As a rough guide, allow a small extra percentage on top of the raw size.
Account for replicas
With one replica, the index exists twice across the cluster, so total memory across nodes roughly doubles, although each node only needs to cache its own share.
Apply quantization
With 8-bit quantization, the hot data to cache falls to roughly a quarter, about eight gigabytes for the compressed vectors, plus the graph, with the full-precision copies on disk.
Spread across nodes
Divide the hot data among the data nodes, leaving room for the heap, the operating system and everything else the nodes do. Confirm each node’s free memory exceeds its share.
Planning a school trip, you count the students, add the teachers, and think about how many buses are needed. Planning kNN memory is the same: count the vectors, multiply by their size, add overheads, and check that your machines have enough seats for the popular data.
These figures are estimates. Real sizes depend on your settings and version. The reliable method is to index a representative sample, read the actual disk usage of the vector field with the disk usage analysis tool, and scale up carefully with a safety margin.
20Indexing Performance: Building Graphs Efficiently
Loading millions of vectors takes planning. A few habits make it much smoother.
Why indexing is slower
Each new vector must be linked into the graph of its segment by searching for its best neighbors, which is real computation. Later, when segments merge, graphs must be rebuilt or combined. Creating embeddings, if you do it during ingestion, adds further time because a model must run for every document. On big loads, these costs add up to hours rather than minutes.
Practical tips
Bulk in reasonable batches
Send documents in bulk requests of moderate size, and keep several requests in flight, watching for rejections.
Slow down refreshes during loads
A longer refresh interval means fewer tiny segments and fewer graphs to build and merge. Restore it afterward.
Use replicas later
For a big initial load, indexing with no replicas and adding them afterwards avoids doing the graph work twice.
Embed ahead of time
Creating vectors in a separate step, perhaps on machines with graphics accelerators, keeps model work off your search cluster.
Merge when the data is settled
After loading a stable index, merging segments down improves search speed, since fewer graphs need visiting.
Mind the build knobs
Higher graph quality settings improve recall but slow indexing. Choose the lowest quality that meets your recall target.
Building a road network takes longer than piling houses in a field. If you keep opening new tiny villages every second, each needing its own roads, and then redo the roads whenever villages merge, you spend all your time building. Fewer, larger settlements built in bulk are cheaper.
Problem
Updating vectors frequently, such as re-embedding and rewriting every document daily, on a very large index.
Why It’s Harmful
Each update is a delete plus a new insert, leaving stale entries and forcing continual graph building and merging. Indexing load and search latency both suffer.
Correct Approach
Update only what changed, batch updates into off-peak windows, and consider building a fresh index and switching an alias when a whole collection must be re-embedded.
21Query Performance: Keeping Searches Fast
Fast vector search comes from a few well-understood conditions. Check them one by one when latency disappoints.
Is the data cached?
If the vector and graph files are not in the file cache, searches read from disk and slow down dramatically. Check free memory and consider quantization or more nodes.
How many segments are there?
Many small segments mean many graphs to visit. Merge when data is stable and avoid needlessly frequent refreshes.
How many shards are involved?
Each shard adds work and candidates. Prefer fewer, larger shards where resilience allows.
Is num_candidates larger than needed?
Reduce it until recall starts to fall, then step back up a little.
Are filters helping?
Selective filters shrink the search space. Check that filter fields are indexed appropriately and that filters are inside the kNN search.
Is embedding at query time slow?
If the model call is the bottleneck, the graph search is innocent. Cache frequent query embeddings and size the model service properly.
flowchart TD
A["Search is slow"] --> B{"Is the time spent creating the query vector?"}
B -->|"Yes"| C["Cache embeddings, scale the model service"]
B -->|"No"| D{"Are vector files in the file cache?"}
D -->|"No"| E["Add memory, use quantization, reduce data per node"]
D -->|"Yes"| F{"Many segments or shards?"}
F -->|"Yes"| G["Merge segments, reduce shard count"]
F -->|"No"| H["Lower num_candidates or tighten filters"]
If a library assistant is slow, ask why. Is she waiting for the translator, walking to a distant storeroom for every book, visiting too many small rooms, or checking too many books before answering? Each cause has a different cure, so find the real one first.
Users experience total time, including creating the query vector, network hops and fetching documents, not only the graph search. Measure each stage separately so that you improve the stage that actually dominates.
22Measuring Quality: Recall and Relevance
A fast search that returns the wrong things is worthless. Quality needs to be measured, not assumed.
Two kinds of quality
The first kind is recall of the approximation: does the fast approximate search find the same neighbors that an exact search would? This measures the cost of the shortcut and can be computed automatically by comparing the two on sample queries. The second kind is relevance: are the nearest neighbors actually useful to the user? This depends on the embedding model, the chunking, the filters and the hybrid combination, and it must be judged by people or by labelled examples.
Recall at k
The share of the true top k neighbors that the approximate search returns.
Relevance judgments
Labelled pairs of queries and good answers, created by people, against which results are scored.
Ranking measures
Numbers that reward putting good answers near the top, such as how high the first correct result appears.
Online signals
Clicks, purchases, follow-up searches and feedback from real users after launch.
A treasure hunter with a fast metal detector might miss a few coins compared with digging up the whole beach. That is recall. But even if the detector finds every coin, they still need to check whether the coins are valuable. That is relevance. You need both.
A simple evaluation routine
Build a query set
Collect real user questions, or write realistic ones, covering easy, hard and unusual cases.
Label good answers
Have people mark which documents should be found for each query, even if only for a sample.
Score each configuration
Run the query set through each candidate setup, varying model, chunk size, hybrid weights and quantization, and record the scores.
Choose and keep testing
Select the best trade-off between quality, cost and speed, and re-run the tests whenever the model, data or settings change.
A handful of impressive demo queries proves little. Semantic search often shines on the queries a team tries first and struggles on the odd ones real users type. Evaluate on a broad, honest sample.
23Dense Vectors Versus Sparse Vectors
Elasticsearch supports two families of vector-based search, and it helps to know how they differ.
Dense vectors
Everything so far has used dense vectors, in which every position holds a number and the whole vector describes the overall meaning of the text. kNN search operates on dense vectors. They are compact and excellent at capturing broad semantic similarity, but individual numbers have no human-readable meaning, so it is hard to explain why two items matched.
Sparse vectors
A sparse vector has a huge number of possible positions, but only a few are filled. Each position usually corresponds to a word or word-like term, and the value says how important that term is to the text. Learned sparse models expand a text with related terms, so a document about “laptops” might also carry weights for “notebook” and “computer.” Sparse vectors are searched using the inverted index machinery rather than graph-based kNN, and they retain some of the explainability of keyword search, since you can see which terms contributed.
A dense vector is like describing a person with a paragraph of general impressions: friendly, energetic, artistic. A sparse vector is like a name tag listing a few key words: chess, violin, Tuesday. The paragraph captures the overall feel, while the tag is easy to read and check.
| Aspect | Dense vectors with kNN | Sparse vectors |
|---|---|---|
| Shape | Fixed length, all positions filled | Very long, mostly empty |
| Search structure | HNSW graph or exact comparison | Inverted index with term weights |
| Explainability | Low, numbers are opaque | Higher, contributing terms can be seen |
| Strength | Broad semantic similarity, images and other media | Text relevance with term expansion |
| Typical memory pattern | Large vectors held in the file cache | Similar to a normal inverted index |
Because the two approaches make different kinds of mistakes, combining dense retrieval, sparse retrieval and plain keyword matching in one hybrid search often gives the best results for text. Images and other non-text data rely on dense vectors.
24Security and Privacy Considerations
Vectors are derived from your data, so the usual rules of data protection apply, with a few extra points.
Access control still applies
Role-based access, document-level security and field-level security work with vector searches just as they do with ordinary searches. A user restricted to certain documents can only receive neighbors among those documents. Make sure that any restriction is enforced by the security features or by filters inside the kNN search, not by hiding results in the user interface.
Vectors are not anonymous
It is tempting to think that a list of numbers cannot reveal the original text. Research has shown that, in some circumstances, information about the source text can be partially recovered from embeddings. Treat vectors that come from sensitive content as sensitive too, and protect them with the same access rules, encryption and retention policies as the source documents.
Model and service credentials
When Elasticsearch calls an external model service, it needs credentials such as keys. Store them using the secure mechanisms provided, restrict who can create or change inference endpoints, and rotate keys regularly. Remember also that text sent to an external service leaves your cluster, which may matter for privacy and compliance rules.
Document and field security
Restrict which documents and fields each role can search and retrieve.
Filters for tenants
Apply tenant conditions inside the kNN search so other tenants’ data never enters the candidate pool.
Encryption
Encrypt data in transit and at rest, since vectors and source text are stored on disk.
Secrets management
Keep model service keys in secure storage and limit who can manage inference endpoints.
Data-sharing review
Check what text leaves your environment when calling external embedding services.
Deletion and retention
When a source document must be deleted, delete its vectors and chunks too, so nothing derived from it lingers.
A photocopy of a secret letter, even if the handwriting is smudged, is still a secret. Vectors are a kind of smudged copy of your documents. They deserve the same lock as the originals.
Problem
Applying access checks only after retrieval, in application code, to results that were gathered from all tenants or all confidentiality levels.
Why It’s Harmful
A bug or a missed check leaks other people’s documents. It also causes too few results, since many of the k neighbors are discarded.
Correct Approach
Enforce access at the search layer with document-level security or mandatory filters inside the kNN search.
25Monitoring and Metrics
Vector workloads have their own warning signs. Watch them and you will catch trouble early.
Search latency percentiles
Track the median and the slowest few percent. Tail latency often reveals cache misses first.
File cache health
Disk read rates and free memory show whether vector files are being served from memory or repeatedly read from disk.
Segment counts and merge activity
Many segments or constant merging point to indexing settings that need attention.
Vector storage size
Per-field disk usage shows how much the vectors and graphs cost, and how it grows with data.
Indexing throughput and rejections
Rejected bulk requests mean graph building is saturating the nodes.
Embedding service latency
Time spent creating vectors, at index time and at query time, and any errors from the model service.
Recall and relevance scores
Automated periodic checks against a labelled query set, to catch quality drops after changes.
Where to look
Node and index statistics give search and indexing rates and times, segment counts and merge activity. The disk usage analysis tool reports how much space each field, including vector fields, consumes. Operating system metrics reveal file cache behaviour and disk reads. Slow logs highlight the slowest searches. Your application should also log the time of each stage: creating the query vector, running the search and post-processing.
Alert on rising disk reads, growing segment counts and slow embedding calls, which are causes you can act on, in addition to alerting on slow user-facing searches, which are the symptoms.
26A Worked Example: Semantic Help-Centre Search
Let us follow a realistic project from idea to launch to see how the pieces fit together.
The scenario
A software company has a help centre with fifty thousand articles. Customers search with questions such as “why can’t I log in after changing my phone,” but the articles use titles like “Two-factor authentication device reset.” Keyword search returns poor results because few words overlap. The team decides to add semantic search with kNN and combine it with their existing keyword search.
Split the articles into chunks
Each article is divided into passages of a few paragraphs, with slight overlap. Each passage keeps its article identifier, title and language.
Choose the model and mapping
They select a text embedding model with a moderate number of dimensions, set the similarity metric recommended for it, and record the model name in the index metadata.
Index text and vectors
An ingest pipeline creates embeddings through an inference endpoint. The initial load runs with no replicas and a long refresh interval, then settings are restored.
Build a hybrid search
Each query runs a keyword search and a kNN search with a language filter, then merges the rankings. The kNN part uses a moderate number of candidates.
Evaluate on real questions
They collect two hundred real customer questions, label the right articles, and compare keyword-only, kNN-only and hybrid setups. Hybrid wins clearly.
Tune and launch
They raise the number of candidates until recall plateaus, merge segments after the load, set up dashboards and alerts, and launch to ten percent of users before rolling out fully.
graph TD
A["Help articles"] --> B["Split into overlapping chunks"]
B --> C["Ingest pipeline creates embeddings"]
C --> D["Index: text fields and vector field"]
Q["Customer question"] --> E["Embed the question"]
E --> F["kNN search with language filter"]
Q --> G["Keyword search"]
D --> F
D --> G
F --> H["Fuse rankings"]
G --> H
H --> I["Show best passages and article links"]
The team did not choose kNN blindly. They chunked sensibly, kept keyword search as a partner, measured on real questions, and rolled out gradually. The technology mattered, but the discipline around it mattered more.
27Design Patterns That Work Well
These habits have proved themselves across many production systems.
Hybrid by default for text
Run keyword and vector retrieval together and fuse the results. This protects you from both the blind spots of embeddings and the blind spots of exact matching.
Filter inside the search
Put must-hold conditions such as language, tenant, availability and permissions inside the kNN search so that results are complete and safe.
Chunk with care and keep parent links
Store passages with references to their parent documents so that results can be grouped, displayed and cited properly.
Version the embedding model
Record model name and version, and when changing models, build a new index and switch an alias rather than mixing vectors.
Quantize when memory is the constraint
Use compressed vectors with rescoring to fit large collections in memory, after verifying recall on your data.
Keep an exact-search yardstick
Maintain a small exact-search test harness to measure recall whenever settings, versions or data change.
Separate embedding from serving
Run heavy model inference on dedicated resources so that vector creation does not compete with search on your data nodes.
Rerank only the best few
When a more accurate but slower reranking model is available, apply it only to the top candidates from retrieval, keeping latency in check.
28Anti-Patterns to Avoid
Recognise these mistakes early, and you will avoid the most common kNN disappointments.
Problem
Mixing vectors from different models, or from different versions of a model, in the same field.
Why It’s Harmful
Vectors from different models are not comparable. Distances between them are meaningless, and results look random in ways that are very hard to diagnose.
Correct Approach
Use one model per field. When switching models, create a new index and reindex everything with the new vectors.
Problem
Dropping vector search into production after trying a few demo queries.
Why It’s Harmful
Demos flatter semantic search. Real queries include product codes, typos, short fragments and odd phrasing, where embeddings can underperform keyword matching.
Correct Approach
Evaluate on a broad, honest query set, and keep keyword search as a partner in a hybrid design.
Problem
Starving the file cache by giving nearly all memory to the Java heap.
Why It’s Harmful
Vectors and graphs are read through the file cache. Without room there, every search reads from disk, and latency rises sharply.
Correct Approach
Use a moderate heap, leave plenty of memory for the operating system, and consider quantization to shrink the hot data.
Problem
Embedding whole long documents as single vectors.
Why It’s Harmful
One vector becomes a blurry average of many topics, and specific questions fail to match well. Models also have input limits, and text beyond them may be ignored.
Correct Approach
Chunk documents into passages, embed each, and keep a link to the parent.
Problem
Choosing a similarity metric that does not match the model, such as dot product on unnormalized vectors from a cosine-trained model.
Why It’s Harmful
Ranking is skewed toward long vectors rather than related meaning, and quality suffers without any error message.
Correct Approach
Follow the model’s documentation, and if unsure, use cosine similarity.
Problem
Post-filtering results after retrieving only the top k neighbors.
Why It’s Harmful
Most of the k may be discarded, leaving few or no results, and unfiltered data has already entered the pipeline, which is dangerous for access control.
Correct Approach
Place the filter inside the kNN search so that the neighbors are chosen among matching documents only.
Problem
Setting num_candidates extremely high everywhere to be “safe.”
Why It’s Harmful
It multiplies work on every shard for very small gains in recall, raising latency and cost for all users.
Correct Approach
Measure recall against exact search and choose the smallest value that meets your target.
29Advantages, Disadvantages and Trade-offs
Every technology is a bargain. Here is what kNN search gives and what it costs.
Advantages
- Finds results by meaning, matching paraphrases and related ideas that share no words
- Works for images, audio and other data once they are embedded
- Integrates with the rest of Elasticsearch: filters, security, aggregations and hybrid ranking
- Approximate search is fast even on very large collections
- Quantization options make large collections affordable
- Powers retrieval for language model applications
Disadvantages / Trade-offs
- Needs an embedding model, adding cost, latency and a component to maintain
- Approximate search can miss the true nearest neighbors
- Vectors are memory-hungry and need a well-provisioned file cache
- Results are hard to explain, since vector numbers have no readable meaning
- Changing the model means re-embedding and reindexing everything
- Weaker than keywords on exact codes, names and rare terms
Keyword search and kNN search side by side
| Aspect | Keyword (lexical) search | kNN (semantic) search |
|---|---|---|
| What it matches | Words in the text | Meaning represented by vectors |
| Exact codes and names | Excellent | Often weak |
| Paraphrases and synonyms | Needs manual effort | Handled naturally |
| Explainability | High, matched terms are visible | Low |
| Extra components | None beyond analyzers | Embedding model and vector storage |
| Memory pattern | Modest | Large, off-heap file cache |
| Best used | As the backbone of most search | Alongside keywords, for semantic reach |
kNN is not a better keyword search. It is a different lens. Use the lens that fits the question, or combine both, and be honest about the costs of extra machinery.
30Real-World and Industry Examples
Here is how organisations in different fields put nearest neighbor search to work.
E-commerce discovery
Shoppers describe what they want in natural language or upload a photo, and the store finds visually or conceptually similar products. Filters for size, price and stock keep results purchasable.
Customer support and knowledge bases
Support portals retrieve the best answer passages for a customer’s question, even when it uses different words from the articles, reducing tickets and speeding up agents.
Retrieval for language model assistants
An assistant retrieves the most relevant passages from company documents and hands them to a language model, so answers are grounded in current, private information instead of guesses.
Media and content recommendation
Streaming and publishing platforms suggest films, songs or articles similar to what a person just enjoyed, using embeddings of content or of listening behaviour.
Duplicate and near-duplicate detection
Teams find documents, images or support tickets that are almost the same, to merge duplicates, detect plagiarism or group similar incidents.
Security and anomaly analysis
Analysts search for events or files whose embeddings are near a known suspicious example, or far from anything normal, to spot unusual behaviour.
Legal and research discovery
Researchers and lawyers find passages on a concept across huge collections of papers and contracts without guessing the exact phrases used.
The lesson across all these examples
In every case, the value came from letting users express what they mean, rather than forcing them to guess the words a document uses. And in every case, the successful teams paired the technology with filters, keyword matching, careful evaluation and sensible capacity planning, instead of relying on the vectors alone.
31Common Myths About kNN Search
A few misunderstandings appear again and again. Let us set them straight.
Myth: “kNN search replaces keyword search.”
Reality: it complements it. Exact terms, codes and names are often better served by keyword matching, and hybrid designs usually win.
Myth: “Approximate means inaccurate.”
Reality: well-tuned approximate search reaches very high recall. The small loss is measurable, controllable, and usually far outweighed by the speed gain.
Myth: “More dimensions always mean better results.”
Reality: quality depends on the model. Higher dimensions cost memory and time, and a good smaller model can beat a poorly matched larger one.
Myth: “The nearest neighbors are always relevant.”
Reality: kNN returns the closest items even when none is close. Use similarity thresholds and quality checks to avoid presenting weak matches as good ones.
Myth: “Vectors live in the Java heap.”
Reality: they are stored in files and read through the operating system’s file cache, so leaving free memory outside the heap is essential.
Myth: “I can swap the embedding model any time.”
Reality: a new model means new vectors for every document and query. Plan a reindex into a new index and switch over with an alias.
Myth: “Embeddings are anonymous.”
Reality: vectors can leak information about their source text. Protect them like the originals.
32How Vector Search Evolved in Elasticsearch
A short history helps you read older articles and understand why the feature looks the way it does.
Early vector support
The 7.x series introduced the dense vector field and scripted scoring, allowing exact similarity over selected documents, but with no graph index for large-scale search.
Approximate kNN arrives
The 8.x series brought approximate kNN search based on HNSW graphs built by the underlying Lucene library, with filtering support and a dedicated kNN section in the search request.
Hybrid and fusion features
Later 8.x releases added easier ways to combine kNN with keyword queries, including rank fusion and the retriever style of describing searches.
Compression and scale
Quantization options arrived and expanded, along with support for byte and bit vectors and larger dimension limits, making very large collections affordable.
Simpler semantic workflows
Inference endpoints and semantic text fields reduced the code needed to embed, chunk and search text, so that semantic search became accessible without hand-built pipelines.
This area of Elasticsearch has changed quickly. Default settings, available index options, request syntax and limits differ between releases. Confirm details in the documentation for your exact version and test on a non-production cluster first.
33Frequently Asked Questions
Short answers to the questions beginners ask most often about kNN search.
It finds the k stored items whose vectors are closest to the vector of a query, so results are chosen by similarity of meaning rather than by matching exact words.
An embedding is a list of numbers produced by a machine learning model that represents the meaning of a piece of data, so that similar items have similar lists.
Exact kNN compares the query with every candidate vector and is perfectly accurate but slow at scale. Approximate kNN uses an HNSW graph to examine only a small part of the data, trading a tiny chance of missing a true neighbor for much greater speed.
It sets how many candidates each shard considers before the best results are chosen. Higher values improve accuracy and slow the search; lower values do the opposite. It must be at least as large as k.
Use the one recommended for your embedding model. Cosine is a safe default. Dot product is fast when vectors are normalized to unit length. L2 distance suits models trained with Euclidean distance.
Not on an existing field that already holds data. Create a new index with the new mapping and reindex, generating vectors with the corresponding model.
If the condition is applied after retrieving the k neighbors, many may be discarded. Put the filter inside the kNN search so that the neighbors are chosen among matching documents only.
No. They are stored in files and read through the operating system’s file cache. Leave enough free memory outside the heap for the cache to hold the hot vector data.
It is storing vector numbers with fewer bits, which shrinks the memory needed and speeds comparisons at a small cost in precision. Full-precision originals are usually kept for rescoring.
For text, usually yes. Hybrid search handles paraphrases through vectors and exact terms through keywords, and typically beats either alone.
Because it returns the closest items whatever their distance. Use a similarity threshold, and check quality on real queries, so that weak matches are not presented as good ones.
Split them into passages, embed each passage, and keep a reference to the parent document. Search over passages and group or display results by parent.
Measure recall against exact search to check the approximation, and measure relevance against labelled queries to check usefulness. Repeat whenever the model, data or settings change.
Yes, but it requires re-embedding all documents and creating a new index. Vectors from different models must never be mixed. Use an alias to switch over smoothly.
No. Vectors can reveal information about their source text, so protect them with the same access controls, encryption and retention rules as the documents they came from.
34Summary and Key Takeaways
k-nearest neighbor search finds the items whose vectors lie closest to a query vector, letting Elasticsearch match by meaning instead of by words. Embedding models turn text, images and other data into vectors, similarity metrics define what near means, and an HNSW graph in each segment makes the search fast by examining only a small part of the data. The controls that matter most are the choice of model and metric, the values of k and num_candidates, filters applied inside the search, hybrid combination with keyword retrieval, and quantization for memory. Success depends as much on discipline as on technology: chunk documents sensibly, never mix models, leave memory for the file cache, protect vectors like the data they came from, and measure recall and relevance on honest queries before and after every change.
Key Takeaways
- kNN finds the closest vectors — the k items nearest to the query vector become the results, so matches are based on meaning rather than shared words.
- Embeddings must come from one consistent model — stored and query vectors need the same model, dimensions and similarity metric, or distances mean nothing.
- Approximate search trades a little accuracy for a lot of speed — HNSW graphs per segment let Elasticsearch skip most vectors, and recall can be measured against exact search.
- k and num_candidates form the speed and accuracy dial — raise num_candidates for better recall, lower it for lower latency, and choose the smallest value that meets your target.
- Filter inside the search and combine with keywords — filters keep results complete and safe, and hybrid retrieval covers both paraphrases and exact terms.
- Memory lives outside the heap — vectors and graphs rely on the file cache, so plan free memory, consider quantization, and keep segment and shard counts sensible.
- Evaluate, secure and version everything — test on real queries, protect vectors as sensitive data, record model versions, and reindex into a new index when models or key settings change.