0

Elasticsearch Bài học 32: Wildcard, Prefix và Regexp - Cạm bẫy hiệu năng và cách dùng an toàn

Trong quá trình phát triển tính năng cho hệ thống Hasaki hoặc các cổng soát vé AFC, chắc chắn sẽ có lúc team Business yêu cầu bạn: "Hãy làm cho tôi tính năng tìm tất cả các mã đơn hàng bắt đầu bằng ORD-2026-", hoặc "Tìm tất cả mã SKU có chứa đuôi -VN".

Nếu là MySQL, bạn sẽ nhắm mắt gõ ngay LIKE 'ORD-2026-%' hoặc LIKE '%-VN'. Trong Elasticsearch, để giải quyết bài toán khớp chuỗi một phần (partial matching) này, chúng ta có bộ ba quyền lực thuộc nhóm Term-level queries: Prefix, Wildcard, và Regexp.

Tuy nhiên, nếu sử dụng sai cách, bộ ba này chính là "kẻ hủy diệt" khét tiếng nhất làm sập toàn bộ Cluster của bạn chỉ bằng 1 cú click chuột.

1. Prefix Query: Tìm kiếm tiền tố (Bắt đầu bằng...)

prefix query dùng để tìm các Document có chứa một từ khóa (token) bắt đầu bằng một chuỗi ký tự cụ thể.

Cú pháp:

JSON

GET orders/_search
{
  "query": {
    "prefix": {
      "order_code.raw": {
        "value": "ORD-2026-"
      }
    }
  }
}

Bản chất hoạt động:

Inverted Index lưu các từ khóa theo thứ tự bảng chữ cái. Khi nhận lệnh prefix: "ORD-2026-", Elasticsearch sẽ nhảy thẳng đến đoạn bắt đầu bằng chữ "O", lướt qua các từ thỏa mãn, và dừng lại ngay lập tức khi gặp một từ không còn bắt đầu bằng "ORD-2026-".

Hiệu năng: Khá nhanh, tương đương với mức O(log⁡n)O(\log n) của B-Tree.

2. Wildcard Query: Tìm kiếm tự do (* và ?)

wildcard cho phép bạn dùng các ký tự đại diện:

  • ?: Thay thế cho đúng 1 ký tự bất kỳ.

  • *: Thay thế cho 0 hoặc nhiều ký tự bất kỳ.

Ví dụ (Tìm tất cả các đuôi VN):

JSON

GET products/_search
{
  "query": {
    "wildcard": {
      "sku.raw": {
        "value": "*-VN"
      }
    }
  }
}

3. Regexp Query: Biểu thức chính quy

Nếu wildcard không đủ mạnh, regexp cho phép bạn kiểm tra các mẫu (pattern) phức tạp. Ví dụ: Tìm các mã SKU có chứa ít nhất một chữ số.

JSON

GET products/_search
{
  "query": {
    "regexp": {
      "sku.raw": {
        "value": ".*[0-9].*"
      }
    }
  }
}

4. Cạm bẫy chí mạng: Sát thủ mang tên "Leading Wildcard"

Hãy nhìn kỹ lại câu wildcard và regexp ở trên. Bạn có thấy chúng đều bắt đầu bằng dấu * hoặc .* không? Đây chính là Leading Wildcard - điều tối kỵ trong Elasticsearch.

Tại sao nó làm sập server?

Inverted Index được thiết kế giống mục lục từ điển (A, B, C...).

Nếu bạn tìm từ bắt đầu bằng chữ C (Prefix query), nó lật ngay đến vần C và quét.

Nhưng nếu bạn tìm từ có cấu trúc *VN (Bắt đầu bằng gì cũng được, miễn kết thúc bằng VN), Elasticsearch hoàn toàn mù phương hướng. Nó không có cách nào khác ngoài việc QUÉT TOÀN BỘ INVERTED INDEX TỪ ĐẦU ĐẾN CUỐI (Full Index Scan) để xem từng từ một có tận cùng bằng "VN" hay không.

Nếu Index của bạn có 50 triệu từ vựng (tokens), một câu query *VN sẽ ép CPU chạy qua 50 triệu phép thử vòng lặp. Nếu 100 khách hàng cùng gõ câu đó, Cluster của bạn sẽ nghẽn 100% CPU và treo toàn hệ thống.

5. Cách dùng an toàn (Best Practices)

Để làm chủ bộ ba này mà không tự bắn vào chân, Backend Engineer cần áp dụng 3 quy tắc sau:

Quy tắc 1: Tuyệt đối cấm Leading Wildcard trên Production

Không bao giờ cho phép API Backend truyền câu lệnh bắt đầu bằng * hoặc ? xuống Elasticsearch. Hãy viết logic chặn ở tầng code Golang/PHP của bạn:

PHP

if (str_starts_with($userInput, '*') || str_starts_with($userInput, '?')) {
    throw new Exception("Không hỗ trợ tìm kiếm bắt đầu bằng ký tự đặc biệt.");
}

Quy tắc 2: Nếu BẮT BUỘC phải tìm từ khóa "nằm giữa" -> Đảo ngược chuỗi (Reverse String)

Nếu team Business ép bạn phải làm tính năng "Tìm hậu tố mã đơn hàng", hãy dùng thủ thuật Reverse:

  1. Lúc Index (Lưu dữ liệu): Tạo thêm một trường order_code_reversed. Mã gốc là ORD-123-VN, bạn đảo ngược thành NV-321-DRO và lưu xuống.

  2. Lúc Search: Khi khách gõ *VN, bạn dùng code Backend đảo ngược chuỗi thành NV*.

  3. Lúc này, từ một lệnh wildcard ngốn CPU (*VN), bạn đã hô biến nó thành một lệnh prefix siêu nhanh (NV*).

Quy tắc 3: Tăng tốc Prefix bằng index_prefixes (Từ bản 7.x)

Nếu tính năng "Tự động gợi ý" (Autocomplete) của bạn sử dụng prefix quá nhiều, Elasticsearch cho phép bạn đánh đổi dung lượng ổ đĩa lấy tốc độ CPU.

Bằng cách cấu hình Mapping:

JSON

"sku": {
  "type": "text",
  "index_prefixes": {
    "min_chars": 2,
    "max_chars": 5
  }
}

Elasticsearch sẽ chủ động băm mã SKU CETAPHIL thành các token cắt sẵn: CE, CET, CETA, CETAP. Khi gọi prefix, nó lôi trực tiếp token này ra thay vì quét bảng chữ cái. Tốc độ sẽ tăng lên hàng trăm lần.

Kết thúc bài học này, bạn đã biết cách phong ấn sát thủ hiệu năng wildcard. Tuy nhiên, nếu bạn thực sự muốn làm tính năng Autocomplete (Hiển thị danh sách gợi ý rơi xuống ngay khi user đang gõ từng chữ cái) cho ô tìm kiếm, dùng prefix hay wildcard vẫn chưa phải là giải pháp "chuẩn bài".


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í