Gem Chewy trong rails
Bài đăng này đã không được cập nhật trong 8 năm
Elasticsearch cung cấp một giao tiếp chuẩn RESTful HTTP hỗ trợ việc đánh chỉ mục (index) và truy vấn data, được xây dựng dựa trên thư viện Apache Lucene. Nó cung cấp khả năng tìm kiếm mở rộng, mạnh mẽ và hiệu quả, lập chỉ mục và truy vấn với số lượng lớn dữ liệu có cấu trúc, hỗ trợ ngay cả với bộ mã UTF-8. Ở trong Rails, chúng ta cũng có một số gem hỗ trợ việc index và query data như: elasticsearch-ruby, chewy...
Ở bài viết này, mình sẽ giới thiệu về gem Chewy và cách sử dụng nó.
Tại sao chúng ta nên dùng chewy?
Vì những tính năng nổi bật đáng chú ý của chewy như sau:
1. Mỗi chỉ số đều được quan sát bởi các model liên quan
Hầu hết các model được đánh index đều liên quan đến nhau. Đôi khi, cần giữ nguyên liên kết dữ liệu và ràng buộc nó với một đối tượng (chẳng hạn như khi chúng ta muốn đánh chỉ mục một mảng các tags cùng với article liên quan tới nó). Chewy cho phép đánh index có thể cập nhật đối với mỗi model. Vì vậy, mỗi article tương ứng sẽ được đánh index lại mỗi khi một tag được cập nhật.
2. Các lớp index độc lập với mô hình ORM/ODM
Với tính năng này, việc cài đặt liên kết giữa các model trở nên linh động và dễ dàng hơn. Chúng ta có thể định nghĩa một index và làm việc với nó một cách hướng đối tượng. Chewy vận hành tự động, loại bò các cài đặt index classes, data import callbacks và các component khác bằng tay.
3. Import dữ liệu lớn
Chewy sử dụng API Elasticsearch hỗ trợ việc cập nhật và đánh lại từ đầu chỉ mục với dữ liệu lớn. Ngoài ra, chewy còn sử dụng atomic updates tìm kiếm những phần tử thay đổi trong một block và đánh lại chỉ mục cho chúng cùng một lúc.
4. Cung cấp phương thức truy vấn DSL
Bằng việc kết nối, kết hợp linh động làm cho các truy vấn trở nên hiệu quả và đơn giản hơn rất nhiều.
Cài đặt Chewy:
Thêm gem 'chewy'
vào Gemfile:
//Gemfile
gem "chewy"
Và sau đó, chạy bundle install
trên commandline:
$ bundle install
Hoặc, ta có thể chay trực tiếp bằng lệnh sau trên terminal
:
$ gem install chewy
Cách dùng
Trước hết, chúng ta sẽ thực hiện Client settings
Đại ý kiểu như là để thao tác và sử dụng với gem Chewy thì cần config một mớ họ hàng gì liên quan đến nó ấy =)) Tóm lại, muốn dùng được thì cứ làm theo đúng các bước được hướng dẫn thôi (hoho)
Đại khái nó như sau:
Tạo một file thủ công bằng tay hoặc chạy lệnh rails g chewy:install
.
Cụ thể là được như này:
# config/chewy.yml
# separate environment configs
test:
host: localhost:9250
prefix: test
development:
host: localhost:9200
Index definition
Ở phần này, ta sẽ đi cài đặt cấu trúc index.
Elasticsearch có nhiều khái niệm document liên quan. Đầu tiên là index
(có thể hiểu như một cơ sở dữ liệu trong RDBMS), nó bao gồm một tập các document
chứa nhiều types
(trong đó type được xem như là một loại của table trong RDBMS).
Mỗi document
bao gồm một tập hợp các field
. Mỗi field
được phân tích một cách độc lập và tùy chọn phân tích của nó được lưu trữ mapping
tương ứng với type
của nó. Chewy sử dụng cấu trúc này "như là" trong mô hình đối tượng của nó.
=> Ta có thể hiểu đơn đơn gian là: Mỗi một bảng trong database sẽ chứa nhiều field và mỗi field thì được lưu trữ khác nhau, nên cần được phân tích và đánh index theo một cách riêng. Và nhờ Chewy mà ta thiết lập nên cấu trúc định nghĩa phương thức index đó.
Ví dụ: Ta có model User, User có quan hệ với các bảng country, badges, projects và ta thiết lập cấu trúc index như sau:
i, Tạo /app/chewy/users_index.rb
ii, Thêm một hoặc nhiều type mapping:
class UsersIndex < Chewy::Index
end
iii, thêm nhiều loại mapping
class UsersIndex < Chewy::Index
define_type User.active.includes(:country, :badges, :projects) do
field :first_name, :last_name # multiple fields without additional options
field :email, analyzer: 'email' # Elasticsearch-related options
field :country, value: ->(user) { user.country.name } # custom value proc
field :badges, value: ->(user) { user.badges.map(&:name) } # passing array values to index
field :projects do # the same block syntax for multi_field, if `:type` is specified
field :title
field :description # default data type is `string`
# additional top-level objects passed to value proc:
field :categories, value: ->(project, user) { project.categories.map(&:name) if user.active? }
end
field :rating, type: 'integer' # custom data type
field :created, type: 'date', include_in_all: false,
value: ->{ created_at } # value proc for source object context
end
end
Để hiểu hơn về phần mapping, bạn có thể tham khảo thêm taị đây: https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html
iv, Kết qủa của 3 bước trên, ta được:
class UsersIndex < Chewy::Index
settings analysis: {
analyzer: {
email: {
tokenizer: 'keyword',
filter: ['lowercase']
}
}
}
define_type User.active.includes(:country, :badges, :projects) do
root date_detection: false do
template 'about_translations.*', type: 'string', analyzer: 'standard'
field :first_name, :last_name
field :email, analyzer: 'email'
field :country, value: ->(user) { user.country.name }
field :badges, value: ->(user) { user.badges.map(&:name) }
field :projects do
field :title
field :description
end
field :about_translations, type: 'object' # pass object type explicitly if necessary
field :rating, type: 'integer'
field :created, type: 'date', include_in_all: false,
value: ->{ created_at }
end
end
end
Ở ví dụ trên, ta định nghĩa một index cho Elasticsearch tên là user.
Sau khi định nghĩa UsersIndex, việc tiếp theo cần phải làm là khởi tao các index và import dữ liệu:
UsersIndex.create!
UsersIndex.import
//Hoặc
UsersIndex.reset!
v, thêm model-observing code:
class User < ActiveRecord::Base
update_index('users#user') { self } # specifying index, type and back-reference
end
class Country < ActiveRecord::Base
has_many :users
update_index('users#user') { users } # return single object or collection
end
class Project < ActiveRecord::Base
update_index('users#user') { user if user.active? }
end
class Badge < ActiveRecord::Base
has_and_belongs_to_many :users
update_index('users') { users } # if index has only one type
end
class Book < ActiveRecord::Base
update_index(->(book) {"books#book_#{book.language}"}) { self } # dynamic index and type with prend
end
Ngoài ra, ta có thể dùng như sau:
update_index('users#user', :self)
update_index('users#user', :users)
Và ta có thể thực hiện truy vấn như sau chăng hạn:
UsersIndex.query(match: {first_name: "Trang Dep Trai"})
Index update strategies
Automicity:
Gỉa dụ, ta có một đoạn mã sau:
class City < ActiveRecord::Base
update_index 'cities#city', :self
end
class CitiesIndex < Chewy::Index
define_type City do
field :name
end
end
Có một vấn đề, nếu ta cập nhập một số đối tượng cùng một lúc, ta phải yêu cầu cập nhập index với từng đối tượng. Điều này làm ảnh hương tới performance của ứng dụng => dùng automic để giải quyết vấn đề này, vì nó hỗ trợ việc cập nhật một số lượng lớn bản ghi đồng thời.
Khi đó, ta có thể:
Chewy.strategy(:atomic) do
City.popular.map(&:do_some_update_action!)
end
Sử dụng automic trì hoãn yêu cầu cập nhật chỉ số cho đến khi kết thúc block. Update bản ghi được tổng hợp và cập nhật chỉ số sẽ xảy ra với các API số lượng lớn. Vì vậy, chiến lược này được tối ưu hóa cao.
Bài viết này mình không đi sâu tìm hiểu mà chỉ đưa ra một cái nhìn chung nhất, đơn thuần về gem Chewy, một công cụ hữu dụng cho các ứng dụng Rails trong việc tìm kiếm và truy xuất dữ liệu từ database.
Hi vọng bài viết sẽ hữu ích với các bạn.
Bài viết được dịch và tham khảo từ:
https://www.toptal.com/ruby-on-rails/elasticsearch-for-ruby-on-rails-an-introduction-to-chewy
All rights reserved