0

Elasticsearch Bài học 34: Function Score & Script Score - Tự định nghĩa công thức rank kết quả bằng script

Ở Bài 33, chúng ta đã dùng boost để nhân điểm cho các sản phẩm Best Seller. Nhưng cách đó quá thô sơ (nhị phân): hoặc là Best Seller (x5 điểm), hoặc không (x1 điểm).

Trong một hệ thống E-commerce thực thụ như Hasaki, team Business sẽ mang đến cho bạn những yêu cầu tính điểm động (Dynamic Scoring) phức tạp hơn rất nhiều, ví dụ:

  • "Sản phẩm càng nhiều lượt xem (view_count), điểm càng cao."

  • "Sản phẩm càng mới ra mắt, điểm càng cao. Càng cũ, điểm càng giảm dần."

  • "Điểm xếp hạng = Điểm text (BM25) * (Biên độ lợi nhuận / 100)."

Lúc này, những câu query cơ bản đã chính thức bó tay. Bạn phải sử dụng đến "vũ khí tối thượng" của Elasticsearch: function_score và script_score.

1. Field Value Factor: Lấy dữ liệu làm điểm số

Khi bạn muốn điểm số tỷ lệ thuận với một trường dạng số (như số lượt mua, lượt xem, hoặc đánh giá sao), hãy dùng field_value_factor bên trong function_score.

Cú pháp:

JSON

GET products/_search
{
  "query": {
    "function_score": {
      "query": { "match": { "name": "sữa rửa mặt" } }, 
      "field_value_factor": {
        "field": "view_count",
        "factor": 1.2,
        "modifier": "log1p" 
      },
      "boost_mode": "multiply" 
    }
  }
}

Bí kíp sống còn: Sức mạnh của modifier (Hàm điều chỉnh) Nếu sản phẩm A có 1,000 view và sản phẩm B có 100,000 view (gấp 100 lần), nếu nhân trực tiếp, điểm của B sẽ đè bẹp hoàn toàn điểm BM25 gốc. Một sản phẩm tên là "Bông tẩy trang" nhưng có 100.000 view sẽ xếp trên "Sữa rửa mặt" 1.000 view khi khách tìm chữ "Sữa rửa mặt".

  • Để tránh điều này, ta dùng modifier: "log1p" (tính Logarit cơ số e của view_count + 1).

  • Với Logarit, sự khác biệt giữa 10 view và 1.000 view là rất lớn, nhưng sự khác biệt giữa 100.000 view và 200.000 view sẽ bị làm phẳng (đi ngang), giúp bảo vệ tính logic của văn bản gốc (BM25).

2. Decay Functions: Nghệ thuật giảm điểm mượt mà

Đây là nhóm hàm cực kỳ tuyệt vời dành riêng cho Ngày tháng (Date) và Tọa độ địa lý (Geo-point). Giả sử quy định của shop là: Hàng mới nhập trong 7 ngày được giữ 100% điểm thưởng. Càng để lâu, điểm thưởng càng "rơi rụng" dần.

Bạn sẽ dùng hàm gauss (Đường cong hình chuông Gauss), exp hoặc linear.

JSON

GET products/_search
{
  "query": {
    "function_score": {
      "query": { "match": { "category": "Chăm sóc da" } },
      "functions": [
        {
          "gauss": {
            "created_at": {
              "origin": "now",   // Điểm mốc thời gian (hiện tại)
              "offset": "7d",    // 7 ngày đầu không bị trừ điểm
              "scale": "30d",    // Sau 30 ngày (kể từ offset), điểm giảm đi một nửa (0.5)
              "decay": 0.5
            }
          }
        }
      ]
    }
  }
}

3. Script Score: Quyền năng vô hạn (và cái giá phải trả)

Nếu field_value_factor và gauss vẫn không đáp ứng nổi công thức của phòng kinh doanh, bạn có thể tự viết code tính toán vào thẳng Elasticsearch bằng ngôn ngữ Painless (có cú pháp gần giống Java).

Kịch bản: Điểm cuối = Điểm BM25 * (1 + Biên độ lợi nhuận / 100).

JSON

GET products/_search
{
  "query": {
    "script_score": {
      "query": {
        "match": { "name": "sữa rửa mặt" }
      },
      "script": {
        "source": "_score * (1 + (doc['profit_margin'].value / 100.0))"
      }
    }
  }
}

Giải phẫu Script:

  • _score: Là biến môi trường chứa điểm BM25 gốc từ khối query bên trong.

  • doc['tên_trường'].value: Là cú pháp bắt buộc để lôi giá trị của trường đó từ ổ cứng (Doc Values) lên RAM để tính toán. (Lưu ý: Chỉ áp dụng được cho trường dạng số, date, và keyword. KHÔNG THỂ lấy dữ liệu từ trường text vào script).

4. Cảnh báo Đỏ (Red Alert) về Hiệu năng

script_score là con dao sắc nhất nhưng cũng dễ đứt tay nhất. Việc chạy các đoạn script lặp đi lặp lại trên hàng triệu Document sẽ vắt kiệt CPU của hệ thống.

Best Practices tối thượng:

  1. Tuyệt đối KHÔNG BỌC script_score quanh một match_all query. Hãy để khối query bên trong lọc kết quả xuống mức nhỏ nhất có thể (dưới 10.000 docs) bằng Filter và Match, sau đó mới dùng script để tính điểm lại cho phần chóp (Top-K) này.

  2. Sử dụng kỹ thuật Rescore (Đánh giá lại): Một chiến thuật nâng cao là bạn cứ search bình thường (bằng Query DSL chuẩn), lấy ra 100 kết quả đầu tiên. Sau đó, dùng khối rescore để chỉ chạy thuật toán script_score nặng nề lên đúng 100 kết quả đó nhằm sắp xếp lại thứ tự trước khi trả về.

JSON

GET products/_search
{
  "query": { "match": { "name": "sữa rửa mặt" } },
  "rescore": {
    "window_size": 100, 
    "query": {
      "rescore_query": {
        "script_score": {
          "query": { "match_all": {} },
          "script": { "source": "doc['profit_margin'].value" }
        }
      }
    }
  }
}

Khép lại Bài 34, bạn đã thực sự tốt nghiệp Phần 5: Nghệ thuật tìm kiếm. Hệ thống của bạn không chỉ tìm đúng (Filter/Term), tìm mờ (Fuzzy/Match), mà giờ đây còn tính điểm thông minh như một Data Scientist.

Nhưng sức mạnh của Elasticsearch không dừng lại ở việc "tìm kiếm". Tại Hasaki, sếp của bạn sẽ muốn một Dashboard vẽ biểu đồ thời gian thực: Tháng này bán được bao nhiêu chai Cetaphil? Khung giá nào được mua nhiều nhất? MySQL sẽ "đứng hình" nếu bạn GROUP BY hàng chục triệu bản ghi.

Chúng ta sẽ bước sang một vũ trụ hoàn toàn mới: Phần 6: Phân tích & Thống kê (Aggregations).


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí