Rails Model Caching bằng Redis
Caching ở tầng model thường bị bỏ qua, thậm chí với những lập trình viên lâu năm. Phần lớn đó là do quan niệm sai lầm rằng, khi bạn cache dữ liệu, bạn không cần bộ nhớ cache ở các cấp thấp hơn. Trong khi sự thật là vấn đề thắt nút cổ chai trong Rails nằm trong lớp View, đó không phải luôn luôn ...
Caching ở tầng model thường bị bỏ qua, thậm chí với những lập trình viên lâu năm. Phần lớn đó là do quan niệm sai lầm rằng, khi bạn cache dữ liệu, bạn không cần bộ nhớ cache ở các cấp thấp hơn. Trong khi sự thật là vấn đề thắt nút cổ chai trong Rails nằm trong lớp View, đó không phải luôn luôn như vậy.
Cache ở cấp độ thấp thì linh hoạt và có thể làm việc bất cứ nơi nào trong ứng dụng. Trong hướng dẫn này, tôi sẽ chứng minh làm thế nào để cache model với Redis.
1. Caching làm việc như thế nào?
Theo truyền thống, truy cập vào ổ đĩa thì tốn kém. Thường xuyên truy cập dữ liệu từ ổ đĩa sẽ gây ra chậm, hiệu năng sẽ giam. Để chống lại điều này, chúng ta có thể thực hiện một lớp bộ nhớ đệm ở giữa ứng dụng và máy chủ cơ sở dữ liệu.
Lớp caching thì không lưu dữ liệu ở lần đầu. Khi nhận yêu cầu dữ liệu, nó sẽ truy cập cơ sở dữ liệu và lưu kết qủa vào trong bộ nhớ (gọi là cache). Những yêu cầu dữ liệu ở lần tiếp theo sẽ được cung cấp từ lớp dữ liệu cache này, vậy nên không cần thiết phải truy cập database, nên hiệu suất sẽ tăng lên.
2. Tại sao sử dụng Redis?
Redis là một in-memory, dạng cấu trúc lưu trữ dữ liệu, sử dụng như là databse, lưu trữ dạng key-value. Ưu điểm là tốc độ nhanh và phục hồi dữ liệu gần như tức thời. Redis hỗ trợ cấu trúc dữ liệu cao cấp như lists, hashes, sets, và có thể tồn tại vào đĩa.
Trong khi nhiều lập trinhf viên thích Memcache với dalli cho việc caching của họ, tôi tìm thấy Redis thì đơn gian cho việc cài đặt và dẽ dàng để quản trị. Nếu bạn sử dụng reque hoặc sidekiq cho việc quản lý backgroud jobs, bạn cũng cần dùng redis.
2.1 cài đặt redis
$ wget http://download.redis.io/releases/redis-2.8.18.tar.gz $ tar xzf redis-2.8.18.tar.gz $ cd redis-2.8.18 $ make
Sau khi cài đặt xong, khởi chạy redis bằng dòng lệnh sau:
$ cd redis-2.8.18/src $ ./redis-server
Để đo hiệu năng, chúng ta sử dụng thư viện ruby "rack-mini-profiler". Thư viện này giups chúng ta kiểm tra hiệu năng ở ngoài view.
Chúng ta sẽ thực hiện bằng cách xây dụng một ứng dụng rails với 2 model Categories và Languages:
app/models/category.rb
class Category include Mongoid::Document include Mongoid::Timestamps include Mongoid::Paranoia include CommonMeta end
app/models/language.rb
class Language include Mongoid: :Document include Mongoid::Timestamps include Mongoid::Paranoia include CommonMeta end
app/models/concerns/common_meta.rb
module CommonMeta extend ActiveSupport::Concern included do field :name, :type => String field :desc, :type => String field :page_title, :type => String end end
cùng build dữ liệu với seed.rb file rake db:seed Thực hiện lấy toàn bộ danh sách Category trong action index như bên dưới:
app/controllers/category_controller.rb
class CategoryController < ApplicationController include CategoryHelper def index @categories = Category.all end end
app/helpers/category_helper.rb
module CategoryHelper def fetch_categories @categories = Category.all end end
app/views/category/index.html.haml
%h1 Category Listing %ul#categories - @categories.each do |cat| %li %h3 = cat.name %p = cat.desc
config.routes.rb
Rails.application.routes.draw do resources :languages resources :category end
Khi sử dụng trình duyệt và trỏ đến /category, bạn sẽ tìm thấy mini-profiler điểm chuẩn thời gian thực hiện của mỗi hành động thực hiện. Việc này sẽ cung cấp cho bạn một ý tưởng để kiểm tra hiệu năng ứng dụng và làm thế nào để tối ưu hóa chúng. Trang này được thực hiện hai lệnh SQL và các truy vấn đã thực hiện khoảng 5ms để hoàn thành
2.1 Triển khai Redis
Có một Ruby client cho Redis giup chung ta kết nối với redis dễ dàng:
gem 'redis' gem 'redis-namespace' gem 'redis-rails' gem 'redis-rack-cache'
Sau khi cài đặt xong, thực hiện config redis như sau:
config/application.rb
#........... config.cache_store = :redis_store, 'redis://localhost:6379/0/cache', { expires_in: 90.minutes } #.........
config/initializers/redis.rb
$redis = Redis::Namespace.new("site_point", :redis => Redis.new)
Bây gio, redis function thì có thể được gọi từ bất cứ đâu thông qua bến toàn cục $redis
Thực hiện việc caching và lấy dữ liệu
app/helpers/category_helper.rb
module CategoryHelper def fetch_categories categories = $redis.get("categories") if categories.nil? categories = Category.all.to_json $redis.set("categories", categories) end @categories = JSON.load categories end end
Lần đầu tiên, chúng ta sẽ kiểm tra xem dữ liệu đã được caching trong memory hay chưa. Nếu chưa thì chúng ta thực hiện truy cập vào database, lấy ra dữ liệu sau đó lưu vào cache. Chú ý là cần chuyển về json rồi mới caching. Cuối cùng là dùng JSON.load để lấy dữ liệu từ cache ra.
Tuy nhiên, đồng nghĩa với việc này chúng ra cần gọi dữ liệu thông qua dạng json object ở ngoài view:
app/views/category/index.html.haml
%h1 Category Listing %ul#categories - @categories.each do |cat| %li %h3 = cat["name"] %p = cat["desc"]
Bây gio, quay lại brower và xem sự khác biệt so với trước đó. Sau lần đầu tiên, dữ liệu được gọi từ cache, việc này tiết kiệm nhiều tài nguyên cho chúng ta với một thay đổi đơn gian.
2.3 Quản lý cache
Gỉa sử như chúng ta nhận thấy có một sai xót trong qúa trình tạo category, sau đó chúng ta sửa lại như sau:
$ rails c c = Category.find_by :name => "Famly and Frends" c.name = "Family and Friends" c.save
Làm mới lại trình duyệt chúng ta sẽ thấy kết qủa:
Chuyện gì đang xảy ra vậy, thay đổi này không hiện thị lên view của chúng ta. Điều này là bởi vì chúng ta đang không truy xuất vào database, tất cả các dữ liệu trả về vẫn đang được lấy từ cache. Để xử lý việc này, chúng ta thêm điều kiện sau:
app/helpers/category_helper.rb
module CategoryHelper def fetch_categories categories = $redis.get("categories") if categories.nil? categories = Category.all.to_json $redis.set("categories", categories) # Expire the cache, every 3 hours $redis.expire("categories",3.hour.to_i) end @categories = JSON.load categories end end
Cache sẽ bị hết hạn sau 3 giờ. Tuy nhiên, việc này cũng chỉ hoạt đọng trong một số trường hợp. Và chúng ta cần xử lý triệt để hơn. Chúng ta sử dụng call_back trong model:
app/models/category.rb
class Category #........... after_save :clear_cache def clear_cache $redis.del "categories" end #........... end
Mỗi khi có thay đổi category object, thì Redis sẽ xóa toàn bộ cache. Điều này đảm bảo là cache luôn luôn update.
3. Tổng kết
Caching ở mức thấp thì đơn gianr và có thể làm được, nó thực sự có ích. Nó có thể giúp hệ thống của bạn tăng hiệu năng một cách đáng kể với một sự thay đổi nhỏ. Hi vọng bài viết sẽ giúp các bạn có cái nhìn về caching
Bài viết được dịch từ: http://www.sitepoint.com/rails-model-caching-redis/