0

Elasticsearch Bài học 39: Filter & Global Aggregations - Cách lấy thống kê độc lập với kết quả search hiện tại

Trong các bài học trước, chúng ta đã ngầm hiểu một quy luật của Elasticsearch: Aggregations luôn luôn chạy trên tập kết quả trả về của khối query.

Nếu người dùng tìm kiếm chữ "áo thun", thì bảng thống kê số lượng thương hiệu bên tay trái màn hình sẽ CHỈ hiển thị các thương hiệu bán áo thun. Điều này rất hợp lý.

Nhưng trong thực tế thiết kế giao diện UI/UX của E-commerce, Backend Engineer thường xuyên nhận được những yêu cầu "ngược đời" từ Frontend:

  1. "Khi khách đang search 'áo thun', tôi muốn hiển thị Tổng số lượng sản phẩm của TOÀN BỘ cửa hàng ở góc trên cùng để khoe độ lớn của shop."

  2. "Khi khách đã bấm vào bộ lọc 'Giá dưới 200k', danh sách sản phẩm ở giữa màn hình chỉ hiện đồ dưới 200k. NHƯNG cái bảng chọn Mức giá ở bên trái vẫn phải giữ nguyên cấu trúc (Dưới 200k: 50 món, 200k-500k: 120 món) để khách biết mà bấm sang khoảng giá khác."

Làm sao để giải quyết? Nếu dùng cách thông thường, khi bạn query giá dưới 200k, rổ "200k-500k" ở khối aggs sẽ lập tức biến thành số 0 (vì bị query gốc chặn lại).

Đó là lúc chúng ta phải dùng đến các Aggregations "bẻ gãy" quy luật: Global và Filter.

1. Global Aggregation: Bỏ qua mọi rào cản

global aggregation là một cái rổ khổng lồ. Nó nói với Elasticsearch rằng: "Mặc kệ cái khối query kia đang lọc cái gì, hãy tính toán cho tôi trên TOÀN BỘ Index".

Kịch bản: Khách đang tìm "Cetaphil". Bạn muốn hiển thị: Top 5 sản phẩm Cetaphil (nhờ Query) VÀ Mức giá trung bình của toàn bộ cửa hàng (nhờ Global Aggs).

JSON

GET products/_search
{
  "query": {
    "match": { "brand": "Cetaphil" }
  },
  "aggs": {
    "toan_bo_cua_hang": {
      "global": {}, // Bẻ gãy giới hạn của query match phía trên
      "aggs": {
        "gia_trung_binh_tong": {
          "avg": { "field": "price" }
        }
      }
    }
  }
}

Cách nó hoạt động: Elasticsearch sẽ chẻ request này làm 2 luồng độc lập:

  • Luồng 1 (Query gốc): Tìm các sản phẩm "Cetaphil" và trả về mảng hits.

  • Luồng 2 (Global Aggs): Quét toàn bộ Index products (bất chấp brand là gì) để tính trung bình giá, và ném kết quả vào cục JSON aggregations.

2. Filter Aggregation: Tạo bộ lọc riêng cho từng giỏ

Đây là kỹ thuật nâng cao nhất để giải quyết bài toán "Sidebar Filter" (Bảng lọc bên trái màn hình) của Shopee, Tiki hay Hasaki.

Kịch bản hóc búa: Khách đang xem danh mục "Chăm sóc da". Bên trái có 2 bộ lọc: Thương hiệu và Giá. Khách tick chọn Thương hiệu = "Vichy".

  • Ở giữa màn hình (mảng hits), chỉ được hiện đồ Vichy.

  • Bên trái màn hình:

    • Bảng Thương hiệu: Vẫn phải hiện đủ L'Oreal (50), Vichy (30), Cetaphil (120) để khách có thể bỏ tick hoặc tick thêm L'Oreal.

    • Bảng Giá: Phải được cập nhật lại theo đúng đồ Vichy.

Vấn đề: Nếu bạn nhét điều kiện brand="Vichy" vào khối query gốc, cái bảng Thương hiệu bên trái sẽ lập tức mất hết L'Oreal và Cetaphil (doc_count về 0).

Giải pháp với Filter Aggregation & Post Filter:

Chúng ta sẽ KHÔNG đặt điều kiện brand="Vichy" vào khối query chính. Khối query chính chỉ lọc danh mục "Chăm sóc da". Mọi điều kiện khách hàng tick chọn ở Sidebar sẽ được đẩy vào một khối đặc biệt tên là post_filter.

JSON

GET products/_search
{
  "query": {
    "match": { "category": "Chăm sóc da" }
  },
  "post_filter": {
    "term": { "brand.raw": "Vichy" } // Lọc kết quả hits ở bước cuối cùng
  },
  "aggs": {
    "danh_sach_thuong_hieu": {
      "terms": { "field": "brand.raw" } // Bảng Brand: KHÔNG bị ảnh hưởng bởi post_filter
    },
    "danh_sach_gia_cua_vichy": {
      "filter": { 
        "term": { "brand.raw": "Vichy" } // Bảng Giá: Tự ép kiểu để giống hệt kết quả hiển thị
      },
      "aggs": {
        "phan_khuc_gia": {
          "range": {
            "field": "price",
            "ranges": [ { "to": 200000 }, { "from": 200000 } ]
          }
        }
      }
    }
  }
}

Phân tích luồng chạy (Cực kỳ quan trọng):

Đừng để đống JSON này làm bạn rối trí. Elasticsearch xử lý chúng theo thứ tự sau:

  1. Bước 1 (Query gốc): Lấy tất cả đồ "Chăm sóc da". Tập kết quả tạm thời là Set A (1000 món, đủ các hãng).

  2. Bước 2 (Chạy Aggregations):

    • Lấy Set A đưa vào danh_sach_thuong_hieu. Nó đếm được đủ cả Cetaphil, Vichy, L'Oreal. (Sidebar giữ nguyên được data!).

    • Lấy Set A đưa vào danh_sach_gia_cua_vichy. Nhưng lúc này nó đụng trúng cái rổ filter. Rổ này gạt hết Cetaphil, L'Oreal đi, chỉ cho Vichy đi qua để chia phân khúc giá.

  3. Bước 3 (Chạy Post Filter): Ở khâu cuối cùng trước khi trả mảng JSON hits về cho Frontend, Elasticsearch lấy Set A ra, ốp cái post_filter: "Vichy" vào. Set A bị rút gọn chỉ còn toàn đồ Vichy.

Bằng cách tách rời query, filter aggs và post_filter, bạn đã giải quyết trọn vẹn bài toán Faceted Search (Tìm kiếm đa chiều) phức tạp nhất của mọi sàn E-commerce mà chỉ tốn đúng 1 lần gọi API từ Frontend.

Kết thúc Bài 39, bạn đã chinh phục hoàn toàn sức mạnh tính toán của Elasticsearch. Trong bài tiếp theo, chúng ta sẽ tối ưu các query Aggregations này sao cho chúng chạy mượt mà ngay cả khi báo cáo Dashboard của team BI yêu cầu duyệt qua 100 triệu bản ghi.


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í