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ộ 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


All Rights Reserved