Scripted Field trong Elasticsearch: Tăng cường khả năng tìm kiếm và phân tích dữ liệu

Lời mở đầu

Scripted Field là một tính năng mạnh mẽ của Elasticsearch, cho phép người dùng định nghĩa một trường (field) mới bằng cách sử dụng kịch bản (script). Khi được định nghĩa, Scripted Field cho phép người dùng thực hiện các hoạt động phức tạp như tính toán, chuyển đổi định dạng, hoặc kết hợp các trường khác.

Trong bài viết này, chúng ta sẽ tìm hiểu về Scripted Field trong Elasticsearch và các lợi ích của việc sử dụng chúng. Chúng ta cũng sẽ đề cập đến các cách để định nghĩa và sử dụng Scripted Field trong Elasticsearch.

Bài viết này bao gồm các chủ đề sau:

  1. Giới thiệu về Scripted Field và tại sao nó quan trọng trong Elasticsearch
  2. Các ví dụ về Scripted Field và cách sử dụng chúng để tìm kiếm và phân tích dữ liệu
  3. Các lưu ý tốt nhất khi sử dụng Scripted Field

Với bài viết này, hy vọng bạn sẽ có thêm kiến thức về Scripted Field và có thể sử dụng tính năng này để tăng cường khả năng tìm kiếm và phân tích dữ liệu trong Elasticsearch.

I. Giới thiệu về Scripted Field và tại sao nó quan trọng trong Elasticseach

Trong Elasticsearch, Scripted Field là một tính năng quan trọng cho phép người dùng định nghĩa các trường (fields) mới bằng cách sử dụng các kịch bản (scripts) như expression, mustache hoặc Painless để tính toán hoặc biến đổi dữ liệu. Bạn có thể tham khảo thêm tại đây .

Tính năng này cho phép người dùng thực hiện các tính toán phức tạp, chuyển đổi định dạng, hoặc kết hợp các trường khác. Với Scripted Field, người dùng không cần phải lưu trữ các trường mới hoặc dữ liệu mới trong Elasticsearch, giúp tiết kiệm dung lượng lưu trữ và giảm thời gian tìm kiếm dữ liệu.

Một số lợi ích của Scripted Field trong Elasticsearch bao gồm:

  1. Tăng cường khả năng tìm kiếm và phân tích dữ liệu: Scripted Field cho phép người dùng thực hiện các tính toán phức tạp, chuyển đổi định dạng, hoặc kết hợp các trường khác, giúp tìm kiếm và phân tích dữ liệu một cách hiệu quả hơn.
  2. Tiết kiệm dung lượng lưu trữ: Sử dụng Scripted Field giúp giảm thời gian và dung lượng lưu trữ khi không cần lưu trữ các trường mới hoặc dữ liệu mới trong Elasticsearch.
  3. Tiết kiệm dung lượng lưu trữ: Sử dụng Scripted Field giúp giảm thời gian và dung lượng lưu trữ khi không cần lưu trữ các trường mới hoặc dữ liệu mới trong Elasticsearch.

Scripted Field có thể được tính toán trên một trường đơn (single-value fields) hoặc cũng có thể được tính toán trên nhiều trường (multi-value fields). Các Scripted Field này cũng có thể được sử dụng để tìm kiếm, phân tích, và hiển thị dữ liệu trong Elasticsearch.

Vì vậy, Scripted Field là một tính năng quan trọng trong Elasticsearch, giúp người dùng tăng cường khả năng tìm kiếm và phân tích dữ liệu một cách hiệu quả hơn, tiết kiệm dung lượng lưu trữ, và linh hoạt trong việc định nghĩa và sử dụng các trường mới.

II. Ví dụ về Scripted Field và cách sử dụng chúng để tìm kiếm và phân tích dữ liệu

Tiền đề:

Bạn vừa thực hiện việc cài đặt một đoạn Javascript vào trang Web của bạn. Đoạn Javascript này thay đổi trạng thái disabled của 1 button nào đó từ true thành false (Đại khái là cho phép button đó được click).

Bài toán:

Bạn cần kiểm tra xem thời gian kể từ khi trang Web được load đến khi Button này được kích hoạt tốn bao lâu. Liệu có người dùng nào không thể kích hoạt được button này (Có thể do file JS này tải quá lâu). Chúng ta cần thống kê thời gian active button này theo Min, Max, Avg.

Giải quyết:

Có nhiều cách để giải bài toán trên, tuy nhiên ở đây chúng ta sẽ sử dụng hàm fetch để ghi log vào web server , và sử dụng Elasticseach để phân tích sự kiện này

bước 1: Gửi request Head để ghi log vào web server

fetch(`/?event_name=button_active&duration=${elapsedTime}`, { method: 'HEAD' });

Log tổng hợp trên Elasticseach có dạng như sau

"_source": 
    "remote": "10.42.17.1",
    "host": "-",
    "user": "-",
    "method": "HEAD",
    "path": "/?event_name=button_active&duration=0.5248",
    "code": "200",
    "size": "0",
    "referer": "myserver.local",
    "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36",
    "http_x_forwarded_for": "-",
    "@timestamp": "2023-04-11T17:31:22.000000000+09:00"
  },..


Bước 2: Sau khi đã thu thập được dữ liệu, chúng ta phải viết đoạn Script để lấy được giá trị duration trong "path"

if (doc.containsKey('path.keyword') && doc['path.keyword'].size() > 0) 
  int startIndex = doc['path.keyword'].value.indexOf('duration=');
  if (startIndex != -1) {
    int endIndex = doc['path.keyword'].value.indexOf('&', startIndex);
    if (endIndex == -1) {
      endIndex = doc['path.keyword'].value.length();
    }
    try {
      return Double.parseDouble(doc['path.keyword'].value.substring(startIndex+9, endIndex));
    } catch (Exception e) {
      return 0.0;
    }
  }
}
return 0.0;

Đoạn Painless script trên được sử dụng để định nghĩa một Scripted Field trong Elasticsearch. Script này sẽ giúp trích xuất giá trị của tham số duration từ trường path.keyword trong mỗi tài liệu (document) và trả về giá trị đó dưới dạng số thực (double). Nếu không tìm thấy tham số duration hoặc giá trị của nó không phải là số thực, script sẽ trả về giá trị mặc định là 0.0.


Bạn cũng có thể sử dụng regrex, tuy nhiên phải active plugin này trong config của Elasticseach

if (doc.containsKey('path.keyword') && doc['path.keyword'].size() > 0) 
    String path = doc['path.keyword'].value;
    def pattern = /.*duration=(\d+(\.\d+)?).*/;
    def match = path =~ pattern;
    if (match.size() > 0) {
        return Double.parseDouble(match[0][1]);
    }
}
return 0.0;


Bước 3: Query đến Elasticseach

 {
 "size": 0,
  "aggs": {
    "max_duration": {
      "max": {
        "script": {
          "inline": "if (doc.containsKey('path.keyword') && doc['path.keyword'].size() > 0) {\n  int startIndex = doc['path.keyword'].value.indexOf('duration=');\n  if (startIndex != -1) {\n    int endIndex = doc['path.keyword'].value.indexOf('&', startIndex);\n    if (endIndex == -1) {\n      endIndex = doc['path.keyword'].value.length();\n    }\n    try {\n      return Double.parseDouble(doc['path.keyword'].value.substring(startIndex+9, endIndex));\n    } catch (Exception e) {\n      return 0.0;\n    }\n  }\n}\nreturn 0.0;",
          "lang": "painless"
        }
      }
    },
    "min_duration": {
      "min": {
        "script": {
          "inline": "if (doc.containsKey('path.keyword') && doc['path.keyword'].size() > 0) {\n  int startIndex = doc['path.keyword'].value.indexOf('duration=');\n  if (startIndex != -1) {\n    int endIndex = doc['path.keyword'].value.indexOf('&', startIndex);\n    if (endIndex == -1) {\n      endIndex = doc['path.keyword'].value.length();\n    }\n    try {\n      return Double.parseDouble(doc['path.keyword'].value.substring(startIndex+9, endIndex));\n    } catch (Exception e) {\n      return 0.0;\n    }\n  }\n}\nreturn 0.0;",
          "lang": "painless"
        }
      }
    },
    "avg_duration": {
      "avg": {
        "script": {
          "inline": "if (doc.containsKey('path.keyword') && doc['path.keyword'].size() > 0) {\n  int startIndex = doc['path.keyword'].value.indexOf('duration=');\n  if (startIndex != -1) {\n    int endIndex = doc['path.keyword'].value.indexOf('&', startIndex);\n    if (endIndex == -1) {\n      endIndex = doc['path.keyword'].value.length();\n    }\n    try {\n      return Double.parseDouble(doc['path.keyword'].value.substring(startIndex+9, endIndex));\n    } catch (Exception e) {\n      return 0.0;\n    }\n  }\n}\nreturn 0.0;",
          "lang": "painless"
        }
      }
    }
  },
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "@timestamp": {
              "gte": 1680627600000,
              "format": "epoch_millis"
            }
          }
        },
        {
          "match_phrase": {
            "path": {
              "query": "event_name=button_active"
            }
          }
        }
      ]
    }
  }
}


Kết quả

Dữ liệu trả về thu được như sau:

  {
  "took": 285,
    "timed_out": false,
    "_shards": {
        "total": 140,
        "successful": 140,
        "skipped": 124,
        "failed": 0
    },
    "hits": {
        "total": 76,
        "max_score": 0.0,
        "hits": []
    },
    "aggregations": {
        "avg_duration": {
            "value": 3.713522368421053
        },
        "min_duration": {
            "value": 0.158
        },
        "max_duration": {
            "value": 10.7089
        }
    }
}


Với kết quả như trên chúng ta có thể kết luận thời gian cần thiết để Button này được active:

  • trong trường hợp xấu nhất: 10s
  • Trường hợp tốt nhất là: 0.158s
  • Và trung bình thì mất: 3.7s

Ngoài ra ta cũng có thể dùng graph của Kibana để biểu diễn Scripted Field

3. Các lưu ý khi sử dụng Scripted Field

Khi sử dụng Scripted Field trong Elasticsearch, có một số lưu ý và tốt nhất bạn nên xem xét để đảm bảo rằng bạn sử dụng chúng một cách hiệu quả và an toàn:

  1. Không sử dụng Scripted Field với các truy vấn tìm kiếm phức tạp hoặc các tập dữ liệu lớn, bởi vì chúng có thể làm giảm hiệu xuất của hệ thống Elasticseach
  2. Kiểm tra xem Scripted Field có đúng định dạng và kiểu dữ liệu mà bạn mong đợi không, và cập nhật chúng nếu cần.
  3. Nếu bạn đang sử dụng Scripted Field để tính toán giá trị mới từ các trường dữ liệu có sẵn, hãy đảm bảo rằng các trường đó đã được định dạng và có kiểu dữ liệu đúng.
  4. Cần cẩn thận khi sử dụng Scripted Field với dữ liệu nhập từ người dùng, vì có thể gây ra các lỗ hổng bảo mật.
  5. Kiểm tra thường xuyên và tối ưu hóa các Scripted Field để đảm bảo tối ưu hiệu suất của hệ thống.


Tóm lại, Scripted Field là một công cụ rất hữu ích để tạo ra các trường dữ liệu mới trong Elasticsearch, tuy nhiên nó cũng cần được sử dụng một cách cẩn thận và đúng đắn để đảm bảo tối ưu hiệu suất và an toàn cho hệ thống của bạn.

4. Tham khảo

  1. https://www.elastic.co/blog/using-painless-kibana-scripted-fields
  2. https://www.elastic.co/guide/en/elasticsearch/reference/7.17/search-fields.html#script-fields