Elasticsearch: Tìm kiếm theo pattern

Vấn đề

Nửa đêm, có đứa bạn nhắn tin hỏi thăm về regex trong elasticsearch. Vấn đề của nó là dùng regex để tìm document chứa câu có dạng: unable ... file ... Câu regex sử dụng ở đây là .*unable.*file. Tuy nhiên không có kết quả nào được trả về mặc dù dữ liệu có rất nhiều, vd câu: The Program was unable to open file abc-123.log due to permission. Thấy là lạ nên tôi cũng thử mày mò xem nó như thế nào, mặc dù chưa sử dụng elasticsearch bao giờ =))

Phân tích

Sau một hồi thử google xem có ai có cùng vấn đề không và đọc qua tài liệu một cách chớp nhoáng mà không giải quyết được vấn đề. Tôi quyết định đọc lại document của Elasticsearch, có lẽ lần đọc trước tôi bỏ quên điều gì đó :-? Dòng đầu tiên trong tài liệu: Regexp Query

The regexp query allows you to use regular expression term queries. See Regular expression syntax for details of the supported regular expression language. The "term queries" in that first sentence means that Elasticsearch will apply the regexp to the terms produced by the tokenizer for that field, and not to the original text of the field.

⇒ Đúng là sai lầm tai hại khi không đọc kỹ tài liệu dẫn đến không hiểu bản chất. Khi sử dụng regexp, Elasticsearch tìm kiếm theo từng term chứ không tìm trong cả câu. Có nghĩa là đoạn regex được dùng để tìm các term trong index, sau đó nó sẽ trả về tập các document có chứa term đó. (Xem thêm cách đánh index → https://viblo.asia/namdn/posts/DXOGRjbPGdZ) Thường các term tương ứng với từng từ trong câu, tuy nhiên còn tùy theo cách mà một câu được phân tích (analyze) như thế nào, mặc định Elastichsearch sử dụng Standard Analyzer để phân tách câu thành các term dựa theo quy tắc chuyển tất cả thành chữ thường, mỗi từ trong câu là 1 term, trong đó các từ được phân cách với nhau bằng dấu cách, dấu gạch nganh,... VD: câu The Program was unable to open file abc-123.log due to permission. sẽ bao gồm các term [the, program, was, unable, to, open, file, abc, 123, log, due, to, permission], như vậy đoạn regex ở trên .*unable.*file sẽ không match bất cứ term nào, nhưng đoạn regex unable.* thì sẽ match.

{
  "query": {
    "regexp": {
      "message_field": {
        "value": ".*unable.*file.*"
      }
    }
  }
}

Các cách tìm kiếm khác như prefix hay wildcard cũng tìm kiếm theo term. Trong tài liệu về wildcard cũng có mô tả:

The prefix, wildcard, and regexp queries operate on terms. If you use them to query an analyzed field, they will examine each term in the field, not the field as a whole. For instance, let’s say that our title field contains “Quick brown fox” which produces the terms quick, brown, and fox. This query would match: { "regexp": { "title": "br.*" }} But neither of these queries would match: { "regexp": { "title": "Qu.*" }} { "regexp": { "title": "quick br.*" }} The term in the index is quick, not Quick. quick and brown are separate terms.

Giải quyết

1. Sử dụng full text search

Sau khi phân tích vấn đề thì giải pháp đầu tiên tôi nghĩ tới đó là sử dụng full text search. VD: câu query này sẽ tìm các document có chứa từ unable và từ file trong field message_field.

{
  "query": {
    "simple_query_string": {
      "fields": [
        "message_field"
      ],
      "query": "unable file",
      "default_operator": "AND"
    }
  }
}

Tuy nhiên, do full text search tìm kiếm không theo thứ tự các từ trong câu nên sẽ không đảm bảo được tiêu chí match đúng theo mẫu. VD: cả 2 câu sau đều match: The Program was unable to open file abc-123.log due to permissionThe file are unable to delete.

2. Sử dụng tìm kiếm span_near

Để đảm bảo việc tìm theo thứ tự các từ trong câu, tôi sử dụng query span_near với thuộc tính in_order: true

{
  "query": {
    "span_near": {
      "clauses": [
        {
          "span_term": {
            "message_field": "enable"
          }
        },
        {
          "span_term": {
            "message_field": "shell"
          }
        }
      ],
      "slop": 12,
      "in_order": true
    }
  }
}

Tuy nhiên, trường hợp này cũng có nhược điểm đó là chúng ta bắt buộc phải khai báo khoảng cách tối đa giữa 2 term qua thuộc tính slop (số từ ở giữa). Dù có thể đặt slop = 1000 hoặc 1000000 nhưng nó vẫn không phải là giải pháp trọn vẹn :-s

Kết luận

Hiện tại, tôi mới chỉ tìm ra được 2 cách để tìm kiếm theo một mẫu câu (pattern), trong đó có lẽ query span_near là phù hợp nhất. Rất mong nhận được phản hồi của các bạn nếu có phương pháp nào đó hay hơn. Cảm ơn các bạn.

Tham khảo


All Rights Reserved