12/08/2018, 18:04

Sử dụng redis counter và cách viết concern trong rails 4

1. Giới thiệu về redis counter tại sao lại sử dụng redis counter -- Đối với các project lớn với số lượng bản ghi nhiều, việc hiển thị count là tổng số các bản ghi đó tưởng chừng như đơn giản nhưng đôi khi lại ảnh hưởng rất lớn đến hiệu năng. Cụ thể: Chúng ta cần hiển thị danh sách list users, với ...

1. Giới thiệu về redis counter tại sao lại sử dụng redis counter

-- Đối với các project lớn với số lượng bản ghi nhiều, việc hiển thị count là tổng số các bản ghi đó tưởng chừng như đơn giản nhưng đôi khi lại ảnh hưởng rất lớn đến hiệu năng. Cụ thể: Chúng ta cần hiển thị danh sách list users, với mỗi user hiển thị thông tin tổng messages của user đó:

class User < ActiveRecord::Base
   has_many :messages
end

class Message < ActiveRecord::Base
   belongs_to :user
end

dưới view thường thì mọi người sẽ cho hiển thị như sau:

<% User.all.each do |user| %>
    <%= user.messages.count %>
<% end %>

Viết thế này thì vướng phải vấn đề query N+1 và để giải quyết thì chúng ta lại sử dụng 1 phương pháp thông thường khác đó là includes. Điều này sẽ chẳng có vấn đề gì nếu như số lượng user và messages của chúng ta là nhỏ, nhưng thực tế thì lại không như vậy, Khi includes(:messages) đồng nghĩa với việc sẽ phải preload ra toàn bộ messages, điều này đương nhiên sẽ làm giảm pefomance một cách rõ rêt. Vậy cách giải quyết ở đây là chúng ta sẽ cache lại số messages count của mỗi user lại bằng redis counter.

2. Cách dùng redis counter

redis counter sẽ sử dụng qua gem

gem 'redis-objects'

Việc cài đặt gem thế nào thì chúng ta sẽ không đề cập ở đây, chúng ta sẽ chỉ đề cập đến việc redis counter hỗ trợ điều gì, sử dụng nó như thế nào? Sau khi cài đặt gem, chúng ta sẽ include nó vào model User như sau:

class User
 ...
  include Redis::Objects
  counter :messages_count
end

messages_count là 1 biến chúng ta định nghĩa dùng để hiển thị số count messages của mỗi user, và hiển thị giá trị của nó như sau

User.first.messages_count.value

Để value này luôn được cập nhật được message count của user thì chúng ta cần viết call back trong model message để khi có bản ghi mới thì sẽ tăng value hoặc giảm value khi destroy message và sử dụng các method:

user.messages_count.increment
user.messages_count.decrement  

Ví dụ:

class Message < ActiveRecord::Base
   belongs_to :user
   
   before_create :increment_message_count!
   before_destroy :decrement_message_count!
   
   private
   
   def update_message_count!
      user.messages_count.increment
   end
   
   def decrement_message_count!
      user.messages_count.decrement
   end
end

Việc update value của message_count chúng ta có thể dùng method reset để chỉ định value là bao nhiêu

 user.messages_count.reset 5

Vậy là chúng ta giải quyết đc vấn đề về hiệu năng kể cả khi số lượng messages có lớn như thế nào.

3. Concern

Thực chất concern là chúng ta sẽ viết các module dùng chung ở nhiều model và sử dụng các module này bằng cách include. Điểm hạn chế của các module thông thường là chỉ có thể truy cập các instance methods của module mà không thể truy cập tới các class methods. Và module concern thì mạnh mẽ hơn thế.

Lấy ví dụ giống như trên, chúng ta cần sử dụng redis counter để lấy ra số lượng activities của 1 user

class User
  include Redis::Objects
  counter :messages_count
  counter :activities_count
end
class Activity < ActiveRecord::Base
   belongs_to :user
   
   before_create :increment_activities_count!
   before_destroy :decrement_activities_count!
   
   private
   
   def increment_activities_count!
      user.activities_count.increment
   end
   
   def decrement_activities_count!
      user.activities_count.decrement
   end
end

Chúng ta sẽ thấy việc viết call back trong model activity và model message là hoàn toàn có thể viết chung được. Và như vậy thì code đương nhiên là sẽ dễ hiểu và sạch sẽ hơn rất nhiều. Việc include callback trong concern khá đơn giản chúng ta có thể dùng

included do
     before_create :callback1
     before_destroy :callback2
 end

Do đó ta tạo được một module concern như sau:

# app/models/concerns/counter_module.rb

module CounterModule
    extend ActiveSupport::Concern

    included do
     before_create :increment_count!
     before_destroy :decrement_count!
    end

    def increment_count!
      send("increment_#{self.class.name.demodulize.downcase}_count!")
    end
    
    def decrement_count!
      send("decrement_#{self.class.name.demodulize.downcase}_count!")
    end
    
    private

    def increment_activities_count!
      ...
    end

    def decrement_activities_count!
      ...
    end

     def increment_messages_count!
      ...
     end

    def decrement_messages_count!
      ...
    end
  end

inlcude model này sẽ giúp model Activity và Message gọn gàng hơn rất nhiều

class Activity < ActiveRecord::Base
   belongs_to :user
   include CounterModule
end
class Message < ActiveRecord::Base
   belongs_to :user
   include CounterModule
end

Điều này mở rộng ra nếu cần lấy ra count của các model khác nữa thì đều có thể sử dụng module concern ở trên.

4. Kết luận

Qua bài viêt mình đã trình bày cách sử dụng redis couter, nguyên nhân vì sao phải sử dụng nó, và trình bày cách viết module concern để dùng chung các callback trong model. Hi vọng bài viết sẽ giúp ích cho mọi người để viết code thoáng và dễ hiểu.

5.

0