Trong thực tế, không ai tìm kiếm chỉ với một điều kiện. Người dùng muốn "laptop gaming, hãng Dell hoặc Asus, giá dưới 20 triệu, còn hàng, không phải hàng trưng bày". Đó là năm điều kiện với các vai trò khác nhau: cái cần tính độ liên quan, cái chỉ cần lọc, cái loại trừ. Công cụ để ghép tất cả lại trong Elasticsearch là bool query — và hiểu đúng bốn mệnh đề của nó là kỹ năng tạo nên 90% truy vấn production.

Bài này (phần 5 loạt Elasticsearch) mổ xẻ must, should, filter, must_not — đặc biệt là khác biệt quan trọng nhất mà người mới hay bỏ qua: filter không tính điểm và được cache. Mọi con số dưới đây đo thật trên es-lab.

Bốn mệnh đề, hai nhóm

bool query gom các điều kiện vào bốn mệnh đề, chia làm hai nhóm theo việc có tính điểm hay không:

Mệnh đề Nghĩa Tính _score? Cache?
must bắt buộc khớp (AND) CÓ (cộng điểm) không
should nên khớp (OR mềm) CÓ (khớp nhiều = điểm cao) không
filter bắt buộc khớp (AND) KHÔNG CÓ
must_not bắt buộc không khớp KHÔNG CÓ

Bool query bốn mệnh đề trong Elasticsearch chia hai nhóm: must bắt buộc khớp AND có tính _score không cache; should nên khớp OR mềm có tính điểm khớp nhiều điểm cao không cache; filter bắt buộc khớp AND không tính điểm có cache; must_not bắt buộc không khớp không tính điểm có cache. Mẫu phổ biến nhất bool với must là match name wireless tìm toàn văn có score, filter là term brand logi và term in_stock true và range price nhỏ hơn hoặc bằng 50 lọc chính xác score bằng 0, should là match name gaming khớp thì cộng điểm, must_not là term brand dell loại trừ. Quy tắc vàng điều kiện yes no trạng thái khoảng giá tag dùng filter, điều kiện cần đo độ liên quan toàn văn dùng must should

Hình 1: must/should tính điểm BM25 (cho độ liên quan); filter/must_not không tính điểm và được cache (cho lọc yes/no). Mẫu phổ biến: must cho tìm toàn văn, filter cho điều kiện trạng thái/khoảng/tag.

{"query":{"bool":{
  "must":   [{"match":{"name":"wireless"}}],
  "filter": [{"term":{"brand":"logi"}},
             {"term":{"in_stock":true}},
             {"range":{"price":{"lte":50}}}],
  "should": [{"match":{"name":"gaming"}}],
  "must_not":[{"term":{"brand":"dell"}}]
}}}

Đo thật: bốn mệnh đề trên một shop nhỏ

Mình index 5 sản phẩm (name=text, brand=keyword, price=int, in_stock=bool) rồi chạy từng tổ hợp. Kết quả thật từ es-lab:

Bảng kết quả đo thật bool query trên shop 5 sản phẩm es-lab elasticsearch 8.15.3: must wireless cộng filter brand logi và in_stock true cho doc1 wireless mouse gaming score 0,5531 và doc2 wireless keyboard mechanical score 0,5531, chỉ 2 trên 5 qua filter _score chỉ từ must wireless filter không cộng điểm; filter-only price nhỏ hơn hoặc bằng 50 cho doc1 doc3 doc4 _score 0,0 cho tất cả vì filter không tính điểm và được cache bitset; should gaming hoặc mechanical với minimum_should_match 1 cho 4 kết quả khớp nhiều mệnh đề điểm cao hơn doc5 mechanical keyboard gaming rgb cả hai từ 1,2832 cao nhất doc2 mechanical 0,8984 doc1 gaming 0,5531 doc3 gaming 0,5531; must wireless cộng must_not brand dell cho 2 kết quả loại doc4 dell. Badge output thật màu xanh

Hình 2: Kết quả thật. must+filter: 2 kết quả, _score chỉ từ must. filter-only: _score=0. should+minimum_should_match: doc5 khớp cả hai từ nên điểm cao nhất (1,2832). must_not loại doc4 dell.

  • must + filter: tìm "wireless" (must) chỉ trong hàng logi còn hàng (filter) → 2 kết quả, cả hai _score = 0,5531. Điểm chỉ đến từ must "wireless"; filter brand/in_stock không cộng điểm dù là điều kiện bắt buộc.
  • filter-only: lọc giá ≤ 50 → 3 kết quả, _score = 0,0 cho tất cả. filter không tính điểm nên mọi tài liệu khớp đều điểm 0 — đúng khi bạn chỉ cần "khớp hay không", không cần xếp hạng.
  • should + minimum_should_match=1: "gaming" hoặc "mechanical" → 4 kết quả, và khớp nhiều mệnh đề should thì điểm cao hơn. doc5 "mechanical keyboard gaming rgb" khớp cả hai từ → 1,2832 (cao nhất), vượt doc2 (chỉ mechanical, 0,8984) và doc1/doc3 (chỉ gaming, 0,5531).
  • must_not: "wireless" nhưng loại brand dell → 2 kết quả (doc4 dell bị loại).

Vì sao filter là chìa khóa hiệu năng

Khác biệt filter vs must không chỉ là ngữ nghĩa — nó là hiệu năng. filter mang hai lợi thế:

  1. Bỏ qua tính điểm: không chạy BM25, chỉ trả lời "khớp/không". Với điều kiện yes/no (trạng thái, khoảng giá, tag), tính điểm là vô nghĩa và lãng phí.
  2. Được cache: ES lưu một bitset (dãy bit đánh dấu tài liệu nào khớp) cho mỗi filter, tái dùng cho các truy vấn sau. Filter "in_stock=true" chạy một lần, các lần sau dùng lại bitset — gần như miễn phí.

Thành thật về quy mô lab: khi mình đo took của filter vs must cho một term khớp ~1000 tài liệu, cả hai đều ~0 ms — chênh lệch nằm dưới ngưỡng đo được. Lợi ích của filter không lộ ra ở truy vấn đơn giản quy mô nhỏ; nó rõ nhất khi (a) lọc lặp lại qua nhiều truy vấn (cache được tái dùng), và (b) khi tính điểm tốn (match toàn văn trên nhiều kết quả, nhiều mệnh đề). Nhưng quy tắc thiết kế thì luôn đúng: điều kiện không cần độ liên quan → đặt vào filter.

Đánh đổi cần cân nhắc

Quy tắc vàng: yes/no → filter, liên quan → must/should. Mọi điều kiện kiểu "đúng/sai" — trạng thái (còn hàng), khoảng (giá, ngày), thuộc về tập (brand, tag, danh mục) — nên vào filter. Chỉ đặt vào must/should những gì bạn muốn đo độ liên quan và cộng vào _score (thường là match toàn văn). Nhầm lẫn phổ biến: đặt điều kiện lọc vào must, khiến ES tính điểm thừa và mất cache — chậm hơn mà kết quả xếp hạng cũng không tốt hơn.

should có hành vi khác nhau tùy có must/filter hay không. Nếu bool chỉ có should (không must/filter), thì mặc định minimum_should_match=1 (phải khớp ít nhất một). Nhưng nếu bool đã có must hoặc filter, thì should trở thành tùy chọn (minimum_should_match=0 mặc định) — chỉ dùng để cộng điểm cho tài liệu đã qua must/filter, không bắt buộc. Đây là nguồn gây bối rối: cùng một khối should cho kết quả khác nhau tùy ngữ cảnh. Khai minimum_should_match tường minh khi muốn chắc chắn.

must_not và filter đều không tính điểm, nhưng dùng cho mục đích khác. must_not loại trừ (NOT), filter chọn (AND). Cả hai cùng được cache và bỏ tính điểm. Dùng must_not cho "loại bỏ spam, hàng ngừng bán, nội dung ẩn"; dùng filter cho "chỉ lấy đúng điều kiện này". Đừng dùng must với một điều kiện phủ định rồi tự đảo logic — must_not rõ ràng và hiệu quả hơn.

Ba ý mang về

  1. bool ghép điều kiện bằng bốn mệnh đề với vai trò khác nhau. Đo thật: must/should tính điểm BM25 (doc5 khớp cả 2 từ should được 1,2832 cao nhất), filter/must_not không tính điểm (filter-only cho _score=0 toàn bộ). Chọn đúng mệnh đề quyết định cả thứ tự lẫn tốc độ.
  2. filter là chìa khóa hiệu năng: bỏ tính điểm + được cache. Đo thật: filter brand/in_stock không cộng vào _score (chỉ must "wireless" tạo 0,5531). Ở quy mô lab chênh lệch tốc độ dưới ngưỡng đo, nhưng cache + bỏ scoring thắng lớn khi lọc lặp lại hoặc điểm tốn.
  3. Quy tắc vàng: yes/no vào filter, liên quan vào must/should. Trạng thái, khoảng, tag → filter (hoặc must_not để loại trừ); match toàn văn cần xếp hạng → must/should. Nhớ should đổi hành vi khi có must/filter — khai minimum_should_match tường minh.

Nguồn

Phần sau ta chuyển từ tìm kiếm sang thống kê: aggregation gom nhóm và tính toán trên hàng trăm nghìn bản ghi (terms, histogram, stats), và đo thật thời gian ES tổng hợp số liệu ở quy mô lớn.