Phần 2: CRUD và Search trong ElasticSearch

Nếu chưa cài đặt ES bạn có thể tìm kiếm trên google hàng tá cách install ES, mình đang dùng Ubuntu10.04 nên dùng theo chỉ dẫn này

install ES ubuntu14.04

Ở phần 1 chúng ta đã đề cập đến những khái niệm cơ bản, tuy nhiên vẫn chưa hình dung ES có thể làm được gì, làm thế nào để đánh index cho dữ liệu, tìm kiếm dữ liệu như nào. Để search được dữ liệu trước tiên chúng ta phải biết Create, Read, Update và Delete dữ liệu trong ES. Có 2 cách để làm được điều này:

  • Qua native API: Hỗ trợ phía client nhiều ngôn ngữ lập trình: java, ruby, php ..., sử dụng trực tiếp socket để kết nối giữa client và server qua default port 9300=> ưu điểm về performance
  • Qua Restful API: base trên HTTP giao thức, được build trên native API để connect tới ES server qua default port 9200 => không phụ thuộc vào nền tảng, ngôn ngữ lập trình, hiệu năng không bằng sử dụng trực tiếp native API

Trong bài viết này vì mục đích dễ hiểu các ví dụ sẽ sử dụng Restful API

1. CRUD qua Restful API

Cấu trúc một request tới ES:

curl -X<VERB> '<PROTOCOL>://<HOST>/<PATH>?<QUERY_STRING>' -d '<BODY>'

Các phần được đặt trong dấu < > :

  • VERB: là method của giao thức HTTP như: GET , POST , PUT , HEAD hay DELETE .
  • PROTOCOL: có thể là http hoặc https
  • HOST: host name hay địa chỉ IP của ES server
  • PORT: port chạy ES service, default là 9200.
  • QUERY_STRING: là query-string parameters (ví dụ ?pretty sẽ in ra màn hình cấu trúc JSON dễ đọc hơn)
  • BODY: là body của request có định dạng JSON object

Ví dụ nế muốn count số lượng documents trong cluster, chúng ta có thể gửi một request như dưới:

curl -XGET 'http://localhost:9200/_count?pretty' -d '
{
    "query": {
    "match_all": {}
    }
}
'

kết quả trả về:

{
  "count" : 8, => số lượng documents
  "_shards" : {
    "total" : 11, => số lượng shards, một index có thể có 1 hay nhiều shards
    "successful" : 11,
    "failed" : 0
  }
}

Dữ liệu trong ES được biểu diễn thành các Document, các document có cấu trúc như một JSON object, một user có thể được biểu diễn như sau:

{
    "email": "[email protected]",
    "first_name": "John",
    "last_name": "Smith",
    "info": {
        "bio": "Eco-warrior and defender of the weak",
        "age": 25,
        "interests": [ "dolphins", "whales" ]
    },
    "join_date": "2014/05/01"
}

Hãy thử build một ứng dụng cho phòng HR của công ty google để quản lý các employee.

  • Đánh index cho các Employee document: Trong đó: google là tên index, employee là mapping type. Tất nhiên là index google có thể có các mapping type khác vì một index có thể có nhiều mapping type
POST /google/employee/1  
{
    "first_name" : "John",
	"last_name" : "Smith",
    "age" : 25,
	"about" : "I love to go rock climbing",
	"interests": [ "sports", "music" ]
}

POST /google/employee/2
{
    "first_name" : "Jane",
	"last_name" : "Laura",
    "age" : 30,
	"about" : "I love to go swimming",
	"interests": [ "music" ]
}


POST /google/employee/3
{
    "first_name" : "Messi",
	"last_name" : "Leonel",
    "age" : 40,
	"about" : "I love to go playing soccer",
	"interests": [ "forestry" ]
}
  • Bây giờ các employee đã được đánh index, để lấy ra một employee bất kỳ dùng request:
GET /google/employee/1

kết quả trả về:

{
   "_index": "google",
   "_type": "employee",
   "_id": "1",
   "_version": 1,
   "found": true,
   "_source": {
      "first_name": "John",
      "last_name": "Smith",
      "age": 25,
      "about": "I love to go rock climbing",
      "interests": [
         "sports",
         "music"
      ]
   }
}
  • Để update employee document, sử dụng method PUT:
PUT /google/employee/1  
{
    "first_name" : "John",
	"last_name" : "Smith",
    "age" : 20,  => change from 25 -> 20
	"about" : "I love to go rock climbing",
	"interests": [ "sports", "music" ]
}
  • Delete một employee: dùng Delete method

Xóa employee có id=3:

DELETE /google/employee/3

Sau đó list ra tất cả các danh sách employee còn lại sử dụng enpoint _search:

GET /google/employee/_search

Kết quả trả về sẽ chỉ còn lại 2 employee:

{
   "took": 3,
   "timed_out": false,
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "hits": {
      "total": 2, => hits là số lượng kết quả trả về
      "max_score": 1,
      "hits": [
         {
            "_index": "google",
            "_type": "employee",
            "_id": "2",
            "_score": 1,
            "_source": {
               "first_name": "Jane",
               "last_name": "Laura",
               "age": 30,
               "about": "I love to go swimming",
               "interests": [
                  "music"
               ]
            }
         },
         {
            "_index": "google",
            "_type": "employee",
            "_id": "1",
            "_score": 1, => score đánh giá độ chính xác kết quả trả về so với keyword tìm kiếm, score càng cao kết quả trả về càng chính xác.
            "_source": {
               "first_name": "John",
               "last_name": "Smith",
               "age": 20,
               "about": "I love to go rock climbing",
               "interests": [
                  "sports",
                  "music"
               ]
            }
         }
      ]
   }
}

Ok đến đây chúng ta đã biết cơ bản các tạo index trong ES thông qua Restful APIs, giờ là lúc tìm kiếm dữ liệu

3. Search

Có 2 cách để tìm kiếm dữ liệu là sử dụng Search Lite và Search với Query DSL

*** Search Lite**: Là cách tiếp cận đơn giản nhất, có thể build các câu query cơ bản(Query String) bằng cách truyền các parameter trong query string. Như trong ví dụ trên để lấy ra tất cả các employee chúng ta dùng request:

GET /google/employee/_search

Bây giờ hãy thử tìm kiếm employee với last_name= "Smith" và age = 20:

GET /google/employee/_search?q=last_name:Smith AND age:20

Kết quả trả về:

{
   "took": 2,
   "timed_out": false,
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "hits": {
      "total": 1,
      "max_score": 0.4339554,
      "hits": [
         {
            "_index": "google",
            "_type": "employee",
            "_id": "1",
            "_score": 0.4339554,
            "_source": {
               "first_name": "John",
               "last_name": "Smith",
               "age": 20,
               "about": "I love to go rock climbing",
               "interests": [
                  "sports",
                  "music"
               ]
            }
         }
      ]
   }
}
  • Search với Query DSL

Với những câu query phức tạp hơn: ví dụ toán tử aggregation thì việc sử dụng query string không đáp ứng được, hơn nữa khi build các query string dài rất dễ mắc lỗi syntax như thiếu các ký tự đặc biệt và khó debug Đó là lý do vì sao mà Query DSL ra đời giúp bạn giải quyết các vấn đề trên. DSL(domain-specific language) là một ngôn ngữ đặc tả sử dụng cú pháp của JSON trong request body để diễn đạt các câu query.

Nếu muốn tìm nhân viên có last_name="Smith" thì request sẽ là:

GET /google/employee/_search
{
    "query" : {
        "match" : {
        "last_name" : "Smith"
        }
    }
}

Câu query khác phức tạp hơn: sẽ lấy ra nhân viên cólast_name="smith"age > 19:

GET /google/employee/_search
{
    "query" : {
        "filtered" : {
            "filter" : {
                "range" : {
                    "age" : { "gt" : 19 }
                 }
            },
            "query" : {
                "match" : {
                "last_name" : "smith"
                }
            }
        }
    }
}

Hay tính độ tuổi trung bình của nhân viên:

GET /google/employee/_search
{
   "aggs": {
       "avg_age": {
           "avg": {"field": "age"}
       }
   }
}

Lấy ra nhân viên có sở thích leo núi:

GET /google/employee/_search
{
   "query": {
       "match": {
          "about": "rock climbing"
       }
   }
}

Tóm lại là bạn có thể làm được rất nhiều thứ với query DSL, đây là một topic lớn cần nhiều thời gian để làm quen và tìm hiểu Bài viết này chỉ giúp bạn có hình dung cơ bản về những điều mà ES có thể làm được, những chủ đề sâu hơn sẽ được tiếp tục trong các bài viết tiếp theo (to be continued)