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.