TL;DR
- Vector Search: Semantic-based search that finds similar content even with different keywords
- dense_vector: Field type for storing vectors, specify similarity calculation method
- kNN Query: Search method to find k nearest neighbor documents
- Hybrid Search: Combine kNN + keyword search for best results
- Embedding Model: Converts text/images to vectors (multilingual models recommended for Korean)
Target Audience: Developers implementing semantic search or recommendation systems Prerequisites: Core Components, Query DSL, Basic ML concepts
Learn how to implement semantic search and similar image search using Elasticsearch’s vector search (kNN).
Version Requirements
- Elasticsearch 8.0+ required (native kNN support)
- Versions before 8.x require script_score or plugins
What is Vector Search?#
Traditional search is keyword matching. Searching “puppy” only finds documents containing “puppy”.
Vector Search is semantic-based search:
- Search “puppy” → Also finds “dog”, “pet”, “canine”
- Image search → Find similar images
- Recommendation systems → Recommend similar products
How It Works#
flowchart LR
A["Text/Image"] --> B["Embedding Model"]
B --> C["Vector Conversion"]
C --> D["Store in Elasticsearch"]
E["Search Query"] --> F["Embedding Model"]
F --> G["Query Vector"]
G --> H["kNN Search"]
D --> H
H --> I["Return Similar Documents"]Diagram: Text/images are converted to vectors through an embedding model and stored in Elasticsearch. Search queries are vectorized the same way, and kNN finds similar documents.
- Embedding: Convert text/image to high-dimensional vector
- Storage: Store vector in Elasticsearch dense_vector field
- Search: Find closest documents to query vector using kNN algorithm
Key Points
- Vector Search finds documents by “meaning” not keywords, so it also finds synonyms and similar expressions
- The embedding model converts text to vectors; model selection is crucial for search quality
- Query must be vectorized using the same model during search
Index Configuration#
dense_vector Field Definition#
PUT /products-vector
{
"mappings": {
"properties": {
"name": {
"type": "text"
},
"description": {
"type": "text"
},
"description_vector": {
"type": "dense_vector",
"dims": 384,
"index": true,
"similarity": "cosine"
}
}
}
}Key Settings#
| Option | Description | Recommended |
|---|---|---|
dims | Vector dimensions | Depends on model (384, 768, 1536, etc.) |
index | Create kNN index | true (for search) |
similarity | Similarity calculation method | cosine (normalized vectors), dot_product, l2_norm |
similarity Options#
| Method | Description | When to Use |
|---|---|---|
cosine | Cosine similarity | Most text embeddings (default) |
dot_product | Inner product | Already normalized vectors (faster) |
l2_norm | Euclidean distance | Distance-based similarity |
Key Points
dimsmust match the embedding model’s dimension count- Set
index: trueto enable kNN search- Use
similarity: cosinefor most text embeddings
Document Indexing#
Embedding Generation (Python Example)#
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
text = "MacBook Pro 14-inch with Apple M3 Pro chip"
vector = model.encode(text).tolist() # 384-dimension vectorStore in Elasticsearch#
PUT /products-vector/_doc/1
{
"name": "MacBook Pro 14-inch",
"description": "Premium laptop with Apple M3 Pro chip",
"description_vector": [0.12, -0.34, 0.56, ...] // 384 floats
}Bulk Indexing#
POST /_bulk
{"index": {"_index": "products-vector", "_id": "1"}}
{"name": "MacBook Pro", "description_vector": [0.12, -0.34, ...]}
{"index": {"_index": "products-vector", "_id": "2"}}
{"name": "Galaxy Book", "description_vector": [0.08, -0.21, ...]}Key Points
- Embeddings must be generated by external service or library before indexing
- Vector field requires exactly
dimsnumber of float values- Use Bulk API for large-scale indexing
kNN Search#
Basic kNN Query#
GET /products-vector/_search
{
"knn": {
"field": "description_vector",
"query_vector": [0.15, -0.30, 0.52, ...], // Search vector
"k": 10,
"num_candidates": 100
}
}| Parameter | Description |
|---|---|
k | Number of nearest neighbors to return |
num_candidates | Number of candidate documents per shard (accuracy↑ = performance↓) |
kNN + Filter Combination#
GET /products-vector/_search
{
"knn": {
"field": "description_vector",
"query_vector": [0.15, -0.30, ...],
"k": 10,
"num_candidates": 100,
"filter": {
"bool": {
"must": [
{ "term": { "category": "laptop" } },
{ "range": { "price": { "lte": 2000000 } } }
]
}
}
}
}Hybrid: kNN + Keyword Search#
GET /products-vector/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"name": {
"query": "MacBook",
"boost": 0.3
}
}
}
]
}
},
"knn": {
"field": "description_vector",
"query_vector": [0.15, -0.30, ...],
"k": 10,
"num_candidates": 100,
"boost": 0.7
}
}Hybrid Search: Combines keyword matching (precision) with semantic search (relevance) for best results
Key Points
k: Number of results to return,num_candidates: accuracy vs speed trade-off- Use
filterto filter conditions before kNN search- When combining keyword + semantic search, adjust weights with
boost
Spring Boot Implementation#
Product.java#
@Document(indexName = "products-vector")
public class Product {
@Id
private String id;
@Field(type = FieldType.Text)
private String name;
@Field(type = FieldType.Text)
private String description;
@Field(type = FieldType.Dense_Vector, dims = 384)
private float[] descriptionVector;
@Field(type = FieldType.Keyword)
private String category;
@Field(type = FieldType.Integer)
private Integer price;
// getters, setters
}VectorSearchService.java#
@Service
public class VectorSearchService {
private final ElasticsearchOperations operations;
private final EmbeddingService embeddingService;
/**
* Semantic search
*/
public List<Product> semanticSearch(String query, int k) {
// 1. Convert query to vector
float[] queryVector = embeddingService.embed(query);
// 2. kNN search
NativeQuery nativeQuery = NativeQuery.builder()
.withKnnQuery(KnnQuery.builder()
.field("description_vector")
.queryVector(queryVector)
.k(k)
.numCandidates(100)
.build()
)
.build();
return operations.search(nativeQuery, Product.class)
.getSearchHits().stream()
.map(SearchHit::getContent)
.toList();
}
/**
* Hybrid search (kNN + keyword)
*/
public List<Product> hybridSearch(String query, int k) {
float[] queryVector = embeddingService.embed(query);
NativeQuery nativeQuery = NativeQuery.builder()
.withQuery(Query.of(q -> q
.bool(b -> b
.should(Query.of(sq -> sq
.match(m -> m
.field("name")
.query(query)
.boost(0.3f)
)
))
)
))
.withKnnQuery(KnnQuery.builder()
.field("description_vector")
.queryVector(queryVector)
.k(k)
.numCandidates(100)
.boost(0.7f)
.build()
)
.build();
return operations.search(nativeQuery, Product.class)
.getSearchHits().stream()
.map(SearchHit::getContent)
.toList();
}
/**
* Similar product recommendations
*/
public List<Product> findSimilar(String productId, int k) {
// Get reference product's vector
Product product = operations.get(productId, Product.class);
if (product == null || product.getDescriptionVector() == null) {
return List.of();
}
NativeQuery nativeQuery = NativeQuery.builder()
.withKnnQuery(KnnQuery.builder()
.field("description_vector")
.queryVector(product.getDescriptionVector())
.k(k + 1) // Exclude self
.numCandidates(100)
.build()
)
.build();
return operations.search(nativeQuery, Product.class)
.getSearchHits().stream()
.map(SearchHit::getContent)
.filter(p -> !p.getId().equals(productId)) // Exclude self
.limit(k)
.toList();
}
}EmbeddingService.java#
@Service
public class EmbeddingService {
private final RestTemplate restTemplate;
// Call external embedding API (e.g., OpenAI, HuggingFace)
public float[] embed(String text) {
EmbeddingRequest request = new EmbeddingRequest(text);
EmbeddingResponse response = restTemplate.postForObject(
"http://embedding-service/embed",
request,
EmbeddingResponse.class
);
return response.getVector();
}
// Batch embedding
public List<float[]> embedBatch(List<String> texts) {
// ... batch processing
}
}Key Points
- An EmbeddingService is needed to convert queries to vectors during search
- Similar product recommendations use existing product’s vector as query vector
- Map with Spring Data Elasticsearch’s
@Field(type = FieldType.Dense_Vector)
Embedding Model Selection#
| Model | Dimensions | Characteristics | Use Case |
|---|---|---|---|
all-MiniLM-L6-v2 | 384 | Fast, lightweight | General text |
all-mpnet-base-v2 | 768 | High quality | Precision search |
text-embedding-ada-002 (OpenAI) | 1536 | Highest quality | Production |
multilingual-e5-large | 1024 | Multilingual support | Korean search |
Korean Search Tip: Use multilingual models (
multilingual-e5-*) or Korean-specific models
Key Points
- Consider dimensions, quality, speed, and cost when selecting models
- Multilingual models (multilingual-e5-*) or Korean-specific models recommended for Korean search
- OpenAI API (text-embedding-ada-002) has high quality but incurs costs
Performance Optimization#
Indexing Performance#
PUT /products-vector
{
"mappings": {
"properties": {
"description_vector": {
"type": "dense_vector",
"dims": 384,
"index": true,
"similarity": "dot_product",
"index_options": {
"type": "hnsw",
"m": 16,
"ef_construction": 100
}
}
}
}
}| HNSW Parameter | Description | Trade-off |
|---|---|---|
m | Connections per node | Higher = more accurate↑, memory↑ |
ef_construction | Search range during index build | Higher = more accurate↑, indexing speed↓ |
Search Performance#
{
"knn": {
"field": "description_vector",
"query_vector": [...],
"k": 10,
"num_candidates": 50 // Accuracy vs speed trade-off
}
}- Lower
num_candidates→ Faster but less accurate - Higher
num_candidates→ More accurate but slower
Key Points
- Adjust indexing accuracy and speed with HNSW parameters (m, ef_construction)
- Adjust search accuracy vs speed trade-off with
num_candidatesdot_productis faster thancosinefor normalized vectors
Use Cases#
1. Semantic Search#
Search “lightweight work laptop” → Returns products related to weight, battery, performance
2. Similar Product Recommendations#
Display “Similar Products” on product detail page
3. Image Search#
Image embedding → Find similar images (fashion, interior)
4. FAQ Bot#
Question embedding → Return most similar FAQ answer
Key Points
- Semantic search: Natural language queries for meaning-based search
- Similar product/image recommendations: Search for similar items using existing item’s vector
- FAQ/chatbot: Vectorize questions to return most similar answers
Next Steps#
| Goal | Recommended Document |
|---|---|
| Improve search quality | Search Relevance |
| Basic search | Query DSL |
| Performance optimization | Performance Tuning |