Sử dụng SearchKick để search dữ liệu
Bài đăng này đã không được cập nhật trong 7 năm
Tại sao phải dùng SearchKick
Hiện nay trong thế giới database, sự tăng kích thước cũng như độ phức tạp của dữ liệu đang ngày càng tăng lên. Nhu cầu tìm kiếm thông tin của người dùng càng ngày càng nhiều. Hàng loạt search engine ra đời để đáp ứng điều đó. Chúng ta thường nghe nói đến một search engine rất nổi tiếng là Elasticsearch . Thường Elasticsearch sẽ được áp dụng trong các hệ thống lớn nhưng học , đọc để áp dụng có thể vất vả. Nói về độ implement nhanh chóng ta có thể dùng searchkick như là một lựa chọn thay thế hoàn hảo. Về hiệu năng chúng không có sự khác biệt nhiều
Làm nào để dùng SearchKick
Cài đặt
Để sử dụng, bạn cần phải cài đặt ElasticSearch
sudo apt-get update
sudo apt-get install elasticsearch
sudo service elasticsearch start
sudo service elasticsearch restart
Hoặc có thể cài đặt và chạy qua brew:
brew install elasticsearch
brew services start elasticsearch
Sau đó include gem searchkick vào trong Gemfile
gem "searchkick"
Khai báo mapping cho model muốn tích hợp ES
class Post < ApplicationRecord
searchkick
end
Sử dụng
Câu query cơ bản có dạng như sau:
Post.search "text"
Tất cả các trường của model Post sẽ được mapping theo default của ES:
curl -XGET 'localhost:9200/_all/_mapping/post?pretty'
{
"posts_development_20170202170757313" : {
"mappings" : {
"post" : {
"dynamic_templates" : [ ],
"properties" : {
"author_id" : {
"type" : "long"
},
"content" : {
"type" : "keyword",
"fields" : {
"analyzed" : {
"type" : "text"
}
},
"include_in_all" : true,
"ignore_above" : 256
},
"created_at" : {
"type" : "date"
},
"id" : {
"type" : "long"
},
"updated_at" : {
"type" : "date"
}
}
},...
Tuy nhiên trên đây chỉ là trường hợp đơn giản nhất khi model không có mối quan hệ nào, trong thực tế khi muốn search một bản ghi nào đó lại thường dựa rất nhiều vào thông tin cha con của nó. Giả sử có 3 models Comment > Post > Author có quan hệ 1-n với nhau và người dùng muốn search các Post dựa trên các thông tin của Author hay Comment. Khi đó ta cần phải khai báo custome mapping cho model như sau:
class Post < ApplicationRecord
belongs_to :author
has_many :comments
searchkick mappings : {
post: {
properties: {
content: {type: "string", index: "not_analyzed"}
}
}
}
def search_data
{
content: content
}
end
end
Để search post dựa trên tên của lớp cha author, bắt buộc phải tạo 1 method trung gian để lấy ra tên tác giả từ model post: app/models/post.rb
class Post < ApplicationRecord
belongs_to :author
has_many :comments
searchkick mappings: {
post: {
properties: {
content: {"type": "string", "index": "not_analyzed"},
author_name: {"type": "string","index": "not_analyzed"}
}
}
}
def search_data
{
content: content,
author_name: author_name
}
end
def author_name
author.try :name
end
end
Note: thay đổi data sẽ được đánh index bằng search_data method và ghi nhớ reindex lại document sau mỗi lần thay đổi mapping hoặc search_data bằng Post.reindex hoặc `rake searchkick:reindex CLASS
{
"posts_development_20170203091125396" : {
"mappings" : {
"post" : {
"properties" : {
"author_id" : {
"type" : "long"
},
"author_name" : {
"type" : "keyword"
},
"content" : {
"type" : "keyword"
},...
}
}
}
}
}
Kiểm tra lại mapping author_name đã được include vào properties của index post như một column thông thường. Tuy nhiên một khi đã dùng custom mapping, chúng ta bắt buộc phải dùng custom search với option body, điều đó cũng có nghĩa mọi option khác nằm ngoài body đều bị ignore.
Hãy cùng xem ví dụ tìm các post có tên tác giả là "Ruby On Rails":
Post.search body: {query: {bool: {must: [{term: {author_name: "Ruby On Rails"}}]}}}
Post Search (15.9ms) curl http://localhost:9999/posts_development/_search?pretty -d '{"query":{"bool":{"must":[{"term":{"author_name":"Ruby On Rails"}}]}}}'
=> <Searchkick::Results:0x00000004078f60 @klass=Post(id: integer, content: text, author_id: integer, created_at: datetime,
updated_at: datetime), @response={"took"=>5, "timed_out"=>false, "_shards"=>{"total"=>5, "successful"=>5, "failed"=>0},
"hits"=>{"total"=>4, "max_score"=>2.0794415, "hits"=>[{"_index"=>"posts_development_20170203095543938", "_type"=>"post",
"_id"=>"41", "_score"=>2.0794415, "_source"=>{"content"=>"Ut inventore voluptates. A magni nesciunt ex sunt nam.
Consequatur accusantium molestias eaque.", "author_name"=>"Nicolette Hintz"}}, {"_index"=>"posts_development_20170203095543938",
"_type"=>"post", "_id"=>"42", "_score"=>2.0794415, "_source"=>{"content"=>"Dolorem quis quam accusamus quae distinctio
velit. Soluta aut delectus ea quia itaque iusto consequatur. Quis natus quas pariatur repellendus nihil.",
"author_name"=>"Nicolette Hintz"}}, {"_index"=>"posts_development_20170203095543938", "_type"=>"post", "_id"=>"4", "_score"=>1.9924302, "_source"=>{"content"=>"Et labore quae quia eveniet et. Ratione qui eius dolores sequi. Excepturi aut sed sit odio doloribus alias.", "author_name"=>"Nicolette Hintz"}}, {"_index"=>"posts_development_20170203095543938", "_type"=>"post", "_id"=>"1", "_score"=>1.89712, "_source"=>{"content"=>"Consequatur laboriosam pariatur laborum voluptas ad velit. At eum accusamus quam doloribus. Inventore reprehenderit blanditiis omnis sed atque voluptatem optio.", "author_name"=>"Nicolette Hintz"}}]}}, @options={:page=>1, :per_page=>1000, :padding=>0, :load=>true, :includes=>nil, :json=>true, :match_suffix=>"analyzed", :highlighted_fields=>[]}>
Cú pháp trên đây tuân theo full Query DSL mà ES cung cấp cho phép định nghĩa câu lệnh tìm kiếm theo kiểu JSON.
Searchkich hay nói đúng hơn là ES cũng hỗ trợ search theo nested attributes hay nói cách khác search theo thuộc tính của lớp con. Trong ví dụ trên, để search Post dựa trên thuộc tính của Comment ta có thể làm như sau:
class Post < ApplicationRecord
belongs_to :author
has_many :comments
searchkick mappings : {
post: {
properties: {
content: {type: "string", index: "not_analyzed"},
comments: {
type: "nested",
properties: {
content: {
type: "string",
index: "not_analyzed"
}
}
}
}
}
}
def search_data
{
content: content,
author_name: author_name,
comments: comments
}
end
end
Trong mappings của Post chúng ta cần khai báo thêm kiểu "nested" cho thuộc tính comments cũng như các trường muốn search trong comments. Sau khi reindex lại trong mappings đã xuất hiện các trường của comments:
{
"posts_development_20170206093907338" : {
"mappings" : {
"post" : {
"properties" : {
"author_name" : {
"type" : "keyword"
},
"comments" : {
"type" : "nested",
"properties" : {
"content" : {
"type" : "keyword"
}
}
},
"content" : {
"type" : "keyword"
}
}
}
}
}
}
Câu query sẽ có dạng như sau:
Post.search body: {query: {bool: {must: [
{term: {author_name: "Nicolette Hintz"}},
{
nested: {
path: "comments",
query: {
bool: {
must: [{term: {content: "something"}}]
}
}
}
}
]}}}
Trong đó path trùng với nested attribute và field query phải nằm trong danh sách các trường đã được khai báo mapping ở trên.
Một yêu cầu thường gặp nữa đó là search đa ngôn ngữ. Để làm được điều này, cách đơn giản nhất là khai báo một trường với 2 kiểu analyzer khác nhau. Ví dụ:
{
properties: {
content: {
type: "string",
fields: {
ja: {type: "string", analyzer: "japanese analyzer"},
en: {type: "string", analyzer: "english analyzer"}
}
}
}
}
Câu quyery sẽ như sau:
Post.search body: {query: {bool: {must: [
{multi_match: {query: "some string", fields: ["content.en", "content.ja"], operator: "and"}}
]}}}
All rights reserved