Gem Chewy trong rails
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ộ ...
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ữ mappingtươ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