Câu chuyện từ support ticket
Một hôm tôi nhận được screenshot từ customer support: user tìm "áo đỏ size M" trên một hệ thống e-commerce tôi maintain. Kết quả đầu tiên hiện ra là... một chiếc váy xanh.
Tôi nhìn vào screenshot và nghĩ "impossible". Rồi tôi thử lại. Kết quả y hệt.
Debug 2 tiếng, tôi tìm ra nguyên nhân: Cái váy xanh đó có description dài gần 500 chữ, được staff viết rất kỹ - bao gồm câu "Phù hợp để mix với áo đỏ". Và product đó cũng có nhiều lượt view, lượt mua - được boost bởi popularity score.
Kết quả: Elasticsearch score của cái váy xanh cao hơn cái áo đỏ thực sự vì nó match nhiều terms hơn và có popularity cao hơn.
Đây không phải lỗi của Elasticsearch. Đây là lỗi của cách chúng tôi configure relevance.
Quay lại chuyện kỹ thuật: Elasticsearch scoring hoạt động như thế nào
Elasticsearch dùng BM25 algorithm mặc định để tính relevance score. Đơn giản hóa:
score(query, document) =
Σ(term_frequency × idf × field_length_norm)
× field_boost
× function_score (nếu có)
Trong đó:
- term_frequency: từ đó xuất hiện nhiều lần trong document → score cao hơn
- idf (inverse document frequency): từ hiếm trong corpus → score cao hơn
- field_length_norm: document ngắn chứa từ đó → score cao hơn (vì mật độ cao hơn)
- field_boost: bạn set weight cho các field
Vấn đề với cái váy xanh:
- Description dài 500 chữ, mention "áo đỏ" → term frequency cao
- Product có nhiều view → popularity boost
- Result: Score cao hơn "áo đỏ" thực sự có description ngắn
Cách fix: Tune relevance đúng cách
Bước 1: Xác định field weight hierarchy
Không phải mọi field đều quan trọng như nhau:
// Episerver Find - field boosting
SearchClient.Instance
.Search<ProductContent>()
.For(query, x => x
.InField(p => p.Name, boost: 10.0) // Tên sản phẩm - quan trọng nhất
.InField(p => p.ShortDescription, boost: 5.0) // Mô tả ngắn - quan trọng
.InField(p => p.Description, boost: 1.0) // Mô tả đầy đủ - ít quan trọng
.InField(p => p.Categories.Select(c => c.Name), boost: 8.0) // Category
.InField(p => p.Brand, boost: 7.0) // Brand
.InField(p => p.Tags, boost: 6.0) // Tags
)
Với cái váy xanh: Từ "áo đỏ" trong Description chỉ có boost 1.0. Cái áo đỏ thực sự có tên "Áo đỏ basic" trong Name field với boost 10.0 → sẽ thắng.
Bước 2: Giới hạn ảnh hưởng của popularity score
Popularity là useful signal, nhưng không nên override relevance:
// Dùng function_score với decay
var query = new FunctionScoreQuery
{
Query = baseRelevanceQuery,
Functions = new[]
{
new FieldValueFactorFunction
{
Field = "salesCount",
Factor = 0.1, // Nhỏ thôi - đừng để popularity dominate
Modifier = FieldValueFactorModifier.Log1P,
Missing = 1.0
}
},
ScoreMode = FunctionScoreMode.Sum,
BoostMode = FunctionBoostMode.Sum,
MaxBoost = 2.0 // Cap popularity boost ở mức 2x - không được phép exceed hơn
};
Bước 3: Semantic field matching - đừng để description dài gây nhiễu
Giải pháp: Dùng match_phrase cho tên product thay vì match:
{
"query": {
"bool": {
"should": [
{
"match_phrase": {
"name": {
"query": "áo đỏ",
"boost": 10,
"slop": 1
}
}
},
{
"match": {
"description": {
"query": "áo đỏ",
"boost": 1,
"minimum_should_match": "75%"
}
}
}
]
}
}
}
match_phrase đảm bảo "áo đỏ" trong description của váy xanh không được tính nếu nó không phải là mô tả trực tiếp của sản phẩm.
Kinh nghiệm từ dự án thực
Sau khi fix relevance cho dự án đó, chúng tôi implement một quy trình gọi là "Search Quality Review" - mỗi 2 tuần một lần:
- Lấy top 50 search queries từ analytics
- Check manual kết quả đầu tiên của mỗi query
- Log những query nào cho kết quả sai
- Tune scoring/boosting và re-test
Cái quy trình đơn giản này đã identify được hàng chục relevance issues mà chúng tôi không biết tồn tại.
Insight quan trọng: Search relevance không phải là "set và quên". Nó cần được maintain và tune liên tục khi catalog thay đổi.
Bảng tóm tắt các vấn đề phổ biến
| Triệu chứng | Nguyên nhân thường gặp | Fix |
|---|---|---|
| Kết quả không liên quan lên top | Popularity score quá cao | Cap popularity boost |
| Sản phẩm tìm theo tên không ra | Field boost Name quá thấp | Tăng Name boost lên 8-10x |
| Typo không tìm được | Fuzzy matching chưa bật | Enable fuzzy với edit distance 1-2 |
| Category không ảnh hưởng kết quả | Category field không được index | Thêm category vào indexed fields |
| Tìm brand sai chính tả không ra | Synonyms chưa có | Thêm brand synonyms dictionary |
Triết lý
Search relevance là UX problem trước khi là technical problem. User không care về BM25 hay TF-IDF - họ chỉ muốn tìm được sản phẩm cần mua.
Cái quan trọng nhất là: liên tục đo lường và cải thiện. Track zero-result rate, track click-through rate trên search results, track conversion từ search. Những số này sẽ chỉ cho bạn thấy cần tune cái gì.
Bạn đã gặp tình huống này chưa?
Bạn đã từng debug một search bug "kỳ lạ" và cuối cùng tìm ra nguyên nhân bất ngờ? Tôi rất muốn nghe :)
/Son Do - believe in basic
#1percentbetter #SearchRelevance #Elasticsearch #ecommerce #Episerver #dotnet