0

Elasticsearch Bài học 27: Query Context vs Filter Context - Khi nào tính điểm Scoring, khi nào dùng Cache?

Ở Bài 26, khi lắp ráp khối bool query, chúng ta đã lướt qua một nguyên tắc ngầm: Nhét lệnh tìm kiếm chữ vào must, còn nhét lệnh lọc Hãng/Giá tiền vào filter.

Tại sao lại có sự phân biệt đối xử này? Tại sao không nhét tất cả vào must cho nhanh?

Lý do nằm ở "hai bộ não" xử lý dữ liệu hoàn toàn khác biệt của Elasticsearch: Query Context (Ngữ cảnh truy vấn) và Filter Context (Ngữ cảnh lọc). Việc hiểu sai và dùng nhầm 2 ngữ cảnh này là nguyên nhân số 1 làm sập các cụm Elasticsearch trên Production vì cạn kiệt CPU.

1. Query Context: Câu hỏi "Tuyệt vời đến mức nào?" (How well?)

Khi một câu lệnh (như match, multi_match) được đặt trong Query Context, Elasticsearch không chỉ tìm xem Document có chứa từ khóa hay không, mà nó còn tự hỏi: "Document này khớp với từ khóa đến mức độ nào?".

Đặc điểm nhận diện:

  • Hoạt động ở đâu? Nằm trong khối must hoặc should của mệnh đề bool.

  • Mục tiêu: Tính toán ra một con số gọi là Relevance Score (Điểm liên quan - trường _score). Document nào có điểm cao hơn sẽ được xếp lên đầu.

  • Hiệu năng: Đây là quá trình "lao động chân tay" cực kỳ nặng nhọc. Elasticsearch phải dùng các thuật toán toán học (BM25) phân tích độ dài chuỗi, tần suất xuất hiện của từ khóa trong Document, và tần suất trong toàn bộ Index.

  • Điểm yếu chí mạng: Vì điểm số này mang tính tương đối và phụ thuộc vào từng câu search cụ thể, Query Context KHÔNG BAO GIỜ ĐƯỢC CACHE (Lưu bộ nhớ đệm). Lần 1 bạn search tốn 50ms, lần 2 bạn search y chang câu đó, nó vẫn tốn 50ms để tính lại từ đầu.

2. Filter Context: Câu hỏi "Có hay Không?" (Yes or No?)

Ngược lại, khi đặt câu lệnh (như term, range) vào Filter Context, Elasticsearch trở thành một cỗ máy nhị phân lạnh lùng. Nó chỉ hỏi: "Document này có thỏa mãn điều kiện hay không?".

Đặc điểm nhận diện:

  • Hoạt động ở đâu? Nằm trong khối filter hoặc must_not.

  • Mục tiêu: Chỉ lọc dữ liệu. Bỏ qua hoàn toàn việc chấm điểm. Giá trị _score của mọi Document lọt qua khe cửa này đều bằng 0 (hoặc bị bỏ qua).

  • Hiệu năng vô địch: Vì không phải làm toán chấm điểm, nó chạy cực nhanh.

  • Sức mạnh lõi - Tự động Cache: Đây là điểm ăn tiền. Những câu Filter thường xuyên được sử dụng (ví dụ: status = "ACTIVE", brand = "Cetaphil") sẽ được Elasticsearch tự động đưa vào bộ nhớ RAM (Node Query Cache). Lần 2 bạn hoặc khách hàng khác gọi lại câu Filter này, tốc độ trả về gần như là 0ms (Zero-latency).

Tiêu chí Query Context (must, should) Filter Context (filter, must_not)
Bản chất câu hỏi Khớp tốt đến mức nào? (How well?) Có khớp không? (Yes/No)
Tính điểm (_score) Có tính toán Bỏ qua (Không chấm điểm)
Cơ chế Caching Tuyệt đối Không Tự động Cache trên RAM
Tài nguyên tiêu thụ Ngốn CPU Ngốn rất ít CPU
Khi nào sử dụng? Tìm kiếm văn bản tự do (Full-text) Lọc chính xác (ID, trạng thái, khoảng giá, ngày tháng)

Giả sử Segment của bạn có 5 Document. Bạn chạy một câu filter: Tìm thương hiệu "Cetaphil".

  • Doc 1: Cetaphil -> Match (1)

  • Doc 2: L'Oreal -> Mismatch (0)

  • Doc 3: Cetaphil -> Match (1)

  • Doc 4: Vichy -> Mismatch (0)

  • Doc 5: Cetaphil -> Match (1)

Elasticsearch sẽ tạo ra một dải bit nhị phân: [1, 0, 1, 0, 1] và ném dải bit siêu nhỏ này vào RAM, đặt tên là "Cache cho brand=Cetaphil". Lần tới, nếu khách hàng tìm "Sữa rửa mặt" và lọc brand="Cetaphil", hệ thống chỉ cần lấy dải bit [1, 0, 1, 0, 1] này thực hiện phép toán Toán tử AND ở mức bit (Bitwise operation) với kết quả search. CPU xử lý phép toán Bitwise nhanh gấp hàng triệu lần so với duyệt mảng thông thường.

4. Best Practices cho Backend Engineer (Hasaki / E-commerce)

Từ việc hiểu 2 context này, code backend của bạn phải tuân thủ 3 nguyên tắc tối thượng sau:

Nguyên tắc 1: Nếu không cần sắp xếp theo độ liên quan, hãy tống hết vào Filter. Nếu API của bạn lấy danh sách sản phẩm để hiển thị theo "Giá từ thấp đến cao" hoặc "Ngày tạo mới nhất", thì _score trở nên hoàn toàn vô nghĩa. Đừng dùng must, hãy bọc mọi câu query của bạn vào trong khối filter để hệ thống chạy tốc độ ánh sáng.

Nguyên tắc 2: Phễu lọc - Filter đi trước, Query theo sau. Elasticsearch đủ thông minh để ưu tiên chạy Filter Context trước. Nó dùng Cache gạt bỏ hàng triệu Document không liên quan, sau đó mới mang số ít Document còn sót lại đi qua Query Context để tính điểm CPU-heavy. Code backend của bạn thiết kế càng nhiều điều kiện Filter càng tốt.

Nguyên tắc 3: Cạm bẫy phá Cache của now (Date Math) Rất nhiều kỹ sư dùng range query trong filter để lấy danh sách sản phẩm đang trong thời gian Sale:

JSON

"filter": {
  "range": {
    "sale_end_date": { "gte": "now" }
  }
}

Lỗi sai: Biến now trỏ đến mili-giây hiện tại. Vì mỗi mili-giây trôi qua là một mốc thời gian khác nhau, câu query này liên tục thay đổi. Elasticsearch nhận định "Câu này mới, chưa từng thấy" -> Cache Miss 100%. CPU sẽ cày ải liên tục. Cách sửa (Làm tròn): Hãy dùng now/m (làm tròn đến phút) hoặc now/d (làm tròn đến đầu ngày).

JSON

"sale_end_date": { "gte": "now/m" }

Lúc này, trong suốt 1 phút đó (hoặc 1 ngày đó), câu query không hề thay đổi. Elasticsearch sẽ hit Cache hoàn hảo.

Đến đây, bạn đã hiểu tại sao điểm số _score lại tiêu tốn tài nguyên đến vậy. Vậy chính xác con số _score (ví dụ: 3.45612) mà Elasticsearch trả về cho bạn ở mỗi Document được tính ra từ công thức Toán học nào?


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í