Model caching trong Rails (Low-level caching)

Caching chính là một công cụ lợi hại để tối ưu hoá tốc độ của một ứng dụng web. Nếu được sử dụng đúng cách, thích hợp, caching sẽ giúp tăng tốc độ load trang lên một cách rất đáng kể.

Caching có rất nhiểu loại, ở nhiều tầng khác nhau. Từ Browser cache, server header(ETag, Not-Modified ), đến server cache.

Trong phạm vi bài này mình sẽ giới thiệu về Model caching hay còn gọi là Low-level caching.

1. Model caching là gì?

Khi bạn cần cache một giá trị nào đó mà nó làm ảnh hưởng tới tốc độ load trang web, giá trị đó ít bị thay đổi, nhưng cứ mỗi lần load trang web thì nó lại truy cập vào DB, cứ như vậy nó rất tốn thời gian. Cơ chế của model caching là việc sử dụng phương thức Rails.cache.fetch. Phương thức này sẽ làm cả việc đọc và ghi cache. Nếu cache đó chưa có trong kho lưu trữ thì nó sẽ tạo ra một cache mới, ngược lại thì nó sẽ đọc cache key và trả về giá trị tương ứng.

2. Cài đặt môi trường

Về việc cài đặt môi trường của Rails thường sẽ nằm trong phần config/enviroments và đối với model caching cũng không là ngoại lệ. Tùy vào việc bạn muốn thiết lập cache cho môi trường dev hay product thì việc setup cơ bản là hoàn toàn giống nhau. Và bạn chỉ cần lựa chọn duy nhất mộtkhu lưu trữ cache phù hợp với nhu cầu cache của bạn.

Ở đây mình sẽ setup cache trong môi trường product. Trong file **config/environments/production.rb **:

# Use a different cache store in production.
# config.cache_store = :mem_cache_store

Ngoài :mem_cache_store ra, cũng có một số hệ thống lưu trữ khác như: :memory_store, :file_store:null_store. Đối với :null_store thì bạn sẽ không thể sử dụng cache trong môi trường test. Với mem_cache_store, chúng ta sẽ phải sử dụng Memcached và cài đặt gem Dalli. Bạn có thể tham khảo nó từ đây: https://github.com/petergoldstein/dalli. Ngoài ra cũng có một hệ thống lưu trữ khá hay đó là :redis_store (gem 'redis-rails').

Để công việc trở nên đơn giản hơn, thì ActiveSupport::Cache sẽ hoạt động như một interface, vì thế bạn có thể thay đổi hệ thống lưu trữ mà không làm ảnh hưởng đơn app của bạn.

3. Các method cơ bản của Model caching

Các method cơ bản đó bao gồm read, write, fetch và delete. Tuy nhiên bạn sẽ phải biết một số mặt lợi, cũng như mặt hại đối với một số method đó và biết lựa chọn method phù hợp cho việc cache. Ví dụ:

Rails.cache.read('fib_30')                   #=> nil
Rails.cache.write('fib_30', fibonacci(30))   #=> true
Rails.cache.read('fib_30')                   #=> 832040
Rails.cache.delete('fib_30')                 #=> true
Rails.cache.read('fib_30')                   #=> nil
Rails.cache.fetch('fib_41'){fibonacci(41)}   #=> 165580141

Ở ví dụ trên mình đã sử dụng read là để đọc cache đã tồn tại, nếu chưa nó trả về kết quả nil, và write sẽ tạo ra một cache mới, để write một cache bạn phải cần key-value. Nhưng lựa chọn phù hợp nhất ở đây sẽ là sử dụng fetch, bằng việc sử dụng cách này, khi chưa có cache nó sẽ tạo, có rồi nó sẽ đọc, rất đơn giản phải không.

Trong thực tế, nó được implement từ method cached_find với fetch dữ liệu sẵn và cache nó lại để ActiveRecord tìm kiếm dễ hơn. Nói có vẻ phức tạp nhưng nó trông rất đơn giản, ngắn gọn như sau:

def self.cached_find(id)
  Rails.cache.fetch([name, id]) { find(id) }
end

def flush_cache
  Rails.cache.delete([self.class.name, id])
end

ActiveRecord sẽ tìm các method không phải chỉ có tham số id là duy nhất mà cũng có thể là một mảng id và nó sẽ trả về một mảng các record tương ứng. Nói rõ hơn là, nếu chúng ta truyền vào một mảng từ một id duy nhất thì chúng ta cũng có thể nhận lại một mảng với một record. Tuy nhiên, khi sử dụng cache chúng ta dựa vào các cache key đã tạo ra từ trước, chính vì thế nó sẽ sinh ra lỗi như sau:

User.cached_find( 1 )
# will result in
# key_cache => 'User/1'
# cache_value => #<User id:1>

User.cached_find( [1] ) 
# will result in
# key_cache => 'User/1'
# cache_value => [ #<User id:1> ]

Điều này có nghĩa là đều có một key 'User/1' nhưng lại có 2 kết quả khác biệt, một cái là bản ghi, còn cái kia là mảng, điều này sẽ ảnh hưởng đến kết quả của việc cache. Chính vì thế chúng ta sẽ có một method để chuyển nó sang một kết quả chung, và mình đã sửa method cached_find như sau:

def self.cached_find(id)
 Rails.cache.find([name, id.to_s]) { find(id) }
end

Trong bài viết tới, mình sẽ đề cập đến expritation của model caching.

Tài liệu tham khảo

  1. https://viblo.asia/p/tong-quan-ve-caching-trong-ruby-on-rails-ZjlvaldxkqJ