+5

Tổng quan về Caching trong Ruby On Rails

Bài dưới bài giới thiệu tổng quan về Caching trong rails dùng để tăng tốc độ trang web. Bài viết gồm có 3 phần: Caching cơ bản, các cách lưu trữ cache, kết luận.

I. Caching cơ bản

Caching nghĩa là lưu trữ nội dung được sinh ra trong chu trình request-response và tái nội dung khi có một request tương tự trước đó. Nó giúp nâng cao hiệu suất của ứng dụng và có thể phục vụ được ngàn người dùng đồng thời.

Có 6 kĩ thuật caching được sử dụng chủ yếu trong Ruby On Rails (ROR): Page Caching, Action Caching, Fragment Caching, Russian Doll Caching, Low-Level Caching, SQL Caching. Với một project khi tạo ra thì ROR luôn mặc định cung chấp cho cũng ta Fragment Caching. Để có thể sử dụng được các kĩ thuật khác ví dụ như page và action caching chúng ta cần thêm gem actionpack-page_cachingactionpack-action_caching vào Gemile. Hơn nữa caching chỉ mặc định trong trong môi trường production nên để có thể cho caching hoạt động ở môi trường khác chúng ta phải thêm vào trong file 'config/enviroments/*.rb' dòng:

config.action_controller.perform_caching = true

1. Page Caching

Page caching là một cơ chế của Rails mà cho phép request cho một trang được sinh ra được thi hành bới webserver(như Apache hay NGINX) mà không cần phải đi qua ngăn xếp của Rails. Mặc dù nó rất nhanh những không thể áp dụng cho tất cả(Ví dụ như các trang cần chứng thực người dùng). Bên cạnh đó webserver xử lý trực tiếp 1 file từ hệ thống file nên bạn cần cài đặt thời hạn cho cache. Tuy nhiên kĩ thuật này đã không dùng được từ Rails 4.

2. Action Caching

Page Caching không thể được sử dụng cho các actions mà có before_action. Ví dụ như, các trang cần chứng thực người dùng. Action Caching thi hành giống như Page Caching ngoại trừ request kế tiếp vào ngăn xếp của Rails cho nên filters có thể chạy trên nó trước khi cach được thực hiện. Điều này cho phép sự chứng thực người dùng và những hạn chế khác được chạy trong khi vẫn thực ra kết quả từ một bản sảo lưu trữ. Tuy nhiên kĩ thuật này đã không dùng được từ Rails 4.

3. Fragment Caching

Các ứng dụng web động đa số xây từ các trang với rất nhiều thành phần mà không phải tất cả trong số chúng có chùng chung các đặc điểm caching. Khi các phần khác nhau của 1 trang cần được cache thì bạn có thể sử dụng Fragme4nt Caching.

Fragment Caching cho phép 1 fragment được bọc trong khối cache và phục vụ ra khỏi cache khi có yêu cầu tiếp theo đến.

Để hiểu rõ hơn bạn xem ví dụ dưới đây. Nếu bạn muốn cache mỗi product trên mỗi trang bạn có thể làm như sau:

    <% @products.each do |product| %>
      <% cache product do %>
        <%= render product %>
      <% end %>
    <% end %>

Khi ứng dụng của bạn nhận được lần đầu request đến trang thì Rails sẽ viết một cache entry mới với key duy nhất như ví dụ dưới đây:

 views/products/1-201505056193031061005000/bea67108094918eeba42cd4a6e786901

Số ở giữa là product_id và sau nó là giá trị timestamp trong thuộc tính updated_at của bản bản ghi product. Rails sử dngj giá trị timestamp để đảm bảo rằng nó không phải là dữ liệu cũ. Nếu giá trị của updated_at thay đổi thì sẽ có một key mới sẽ đc sinh ra. Sau đó sẽ ghi một cache mới vào key đó. và cache cũ đc ghi đè để key cũ sẽ không được sử dụng. Đây được gọi là key-based expiration.

Các Cache fragment cũng sẽ đc đặt thời gian hết hạn khi fragment view tay đổi (ví dụ như HTML trong view thay đổi). Xâu các kí tự ở cuối của key là một số cây mẫu. Nó là một mảng băm md5 được tính dựa trên nội dung của view fragment mà bạn đang caching. Nếu bạn thây đổi view fragment thì mà băm md5 cũng sẽ thay đổi theo.

Nếu bạn muốn cache một fragment dưới các điều kiện cụ thể nào thì bạn có thể sử dụng cache_if hoặc cache_unless.

    <% cache_if admin?, product do %>
      <%= render product %>
    <% end %>

3.1. Collection Caching

Render helper cũng có thể cache các templates riêng biệt mà được render cho một tập hợp. Nó có thể thậm chí thay thế ví dụ trước với mỗi cái nó đọc tất cả các cache templates một lần thay vì đọc từ cái một. Đoạn code dưới sẽ mô tả rõ hơn.

    <%= render partial: 'products/product', collection: @products, cached: true %>

4. Russian Doll Caching

Bạn có lẽ muốn rằng các fragments cached lồng bên trong các fragment cached khác. Điều đó được gọi là Russian doll caching.

Ưu điểm của Russian doll caching là rằng nếu một sản phẩm được update thì tất cả các fragment khác có thể được tái sử dụng khi sinh lại fragment.

Như được giải thích ở phần trước thì một file đươc cache sẽ có thời gian hết hạn nếu giá trị của updated_at thay đổi trong bản ghi mà file cache phụ thuộc một cách trực tiếp. Tuy nhiên, Nó sẽ không expire bất kì cache fragment nào mà bị lồng nhau.

Ví dụ:

    <% cache product do %>
      <%= render product.games %>
    <% end %>

Mà viết cách khác:

    <% cache game do %>
      <%= render game %>
    <% end %>

Nếu bất kì thuộc tính nào của game bị thay đổi, thì giá trị updated_at sẽ được đặt lại là thời gian hiện tại. Tuy nhiên bởi vì updated_at sẽ không được thay đổi cho đối tượng product nên cache đó sẽ không được expired và ứng dụng của bạn sẽ đáp ứng dữ liệu cũ. Để fix điều này, thì chúng ta sẽ thắt chặt các model với nhau với phương thức touch.

    class Product < ApplicationRecord
      has_many :games
    end

    class Game < ApplicationRecord
      belongs_to :product, touch: true
    end

Với touch được đặt là true thì bất kì action nào mà thay đổi updated_at cho một bản ghi game cũng sẽ thay đổi theo nó.

5. Low-Level Caching

Thi thoảng bạn cần cache một giá trị cụ thể hoặc kết quả của một câu lênh query thay vì caching cả view fragments. Cơ chế caching của rails thực hiện rất tốt cho việc lưu trữ bất kì các kiểu dữ liệu nào.

Các hiệu quả nhất để thực hiện low-level caching là việc sử dụng phương thức Rails.cache.fetch. Phương thức này làm cả việc đọc và ghi cache. Khi chỉ được gửi một đối số duy nhất thì key được lấy về và giá trị từ từ cache được trả lại. Nếu một block được gửi thì kết của block sẽ đc cached để key được cho và kết đc trả về.

Với ví dụ dưới thì sẽ rõ hơn. Một ứng dụng có model Product với instant method mà tra cứu giá sản phẩm trên một website cạnh tranh. Dữ liệu được trả về bới phương thức này sẽ là hoàn hảo cho low-level caching.


    class Product < ApplicationRecord
      def competing_price
        Rails.cache.fetch("#{cache_key}/competing_price", expires_in: 12.hours) do
          Competitor::API.find_price(id)
        end
      end
    end

6. SQL Caching

Query caching là một đặc điểm của Rails mà cache tập hợp kết quả bởi mỗi câu lệnh query. Nếu Rails gặp lại cùng câu lệnh query lần nữa cho request, thì nó sẽ sử dụng kết quả được cache mà không phải vào database lấy dữ liệu lại lần nữa.

Ví dụ:


class ProductsController < ApplicationController

  def index
    # Run a find query
    @products = Product.all

    ...

    # Run the same query again
    @products = Product.all
  end

end

Lần thứ 2 cùng 1 câu query chạy với database nhưng nó không thực sự vào database. Lần thứ nhất kết quả đươc trả lại và được lư trong query cache (trong bộ nhớ) và lần thứ 2 kết quả đc lấy từ trong bộ nhớ đó.

Tuy nhiên, nó quan trong để nói rằng cache được tại thời điểm bắt đầu của một action và bị xóa ở thời điểm cuối cùng của action do đó nó tồn tại duy nhất trong suốt thời gian hành động. Nếu bạn muốn lưu trữ các kết quả của câu query trong thời gian dài. Bạn thế dử dụng bới low level caching.

II. Nơi lưu trữ cache

Rails cung cấp các nơi lưu trữ cache khác nhau cho dữ liệu được cache.

1. Cách cài đặt

Bạn có thể cài đặt nơi lưu trữ cache mặc định của ứng dụng của bạn bằng việc cài đặt config.cache_store như bên dưới.


    config.cache_store = :memory_store, { size: 64.megabytes }

2. ActiveSupport::Cache::Store

Lớp này cung cấp xây dựng cho việc cache trong Rails. Đây là một lớp trừu tượng mà không thể thi hành nó 1 các trược tiếp. Thay vào đó phải sử dụng một lớp cụ thể gắn với công cụ lưu trữ.

Các phương thức chính được gọi là read, write, delete, exist?fetch. Phương thức fetch lấy một block và hoặc trả lại giá trị đã tồn tài trong cache hoặc là từ block lưu kết quả vào cache nếu trong cache không tồn tại giá trị.

Có một số option được sử dụng bởi mọi cài đặt bộ nhớ cache như bên dưới mô tả:

:namespace-Option này có thể được sử dụng để tạo một namespace bên trong nơi lưu trữ cahce. Nó đặc biệt hữu ích nếu ứng dụng dụng chung cache với ứng dụng khác.

:compress-Option này có thể được sử dụng để mô tả cách nén được sử dụng trong cache. Nó hữu ích trong việc truyền dữ liệu lớn qua những nơi mạng tốc độ chậm.

:compress_threshold-Option này có thể kết hợp với :compress để mô tả ngưỡng dưới mà cache không hể nén được. Mặc định là 16 kilobytes.

:expires_in-Option này cài đặt thời gian hết hạn cho cache. :race_condition_ttl-Option này có thể kết hợp với expires_in

3. ActiveSupport::Cache::MemoryStore

Nơi lưu trữ cache giữ các bản ghi trong bộ nhớ trong cùng tiến trình của Ruby. Nơi lưu chữ cache có một kích thước giới hạn được xác đinh bởi :size và mặc định định là 32Mb. Nếu cache vượt quá kích thước được phân thì các mục gần đây nhất được sử dụng sẽ bị gỡ bỏ.

    config.cache_store = :memory_store, { size: 64.megabytes }

Nếu chạy nhiều tiến trình thì các tiến trình không thể chia sẻ dữ liệu cache cho nhau. Cách lưu trữ này không thích hợp cho các ứng dụng lớn, nhưng nó làm việc rất hiệu quả cho ứng dụng nhỏ.

4. ActiveSupport::Cache::FileStore

Nơi lưu trữ cache này sử dụng hệ thống file để lưu trữ. Đường dẫn là nơi các file lưu trữ sẽ xác định khi khởi tạo cahce.

config.cache_store = :file_store, "/path/to/cache/directory"

Với nơi lưu trữ cache này, các đa tiến trình cùng một host có thể chia sẻ cùng 1 cache. Nơi lưu trữ cache này phù hợp với cho các trang web có lưu lượng trung bình thấp mà được phân phối ra làm 1 hoặc 2 host.

Khi cache sẽ phình ra cho đến khi đầy ổ, thì nó sẽ xóa các thực thể cũ nhất. Đó là cơ chế mặc định của nó.

5. ActiveSupport::Cache::MemCacheStore

Cách lưu trữ này sử dụng Danga's memcached server để cung cấp một bộ nhớ cache tập trung cho ứng dụng. Rails sử dụng gem dalli. Cách lưu trữ này giờ đây phổ biến ở đa số trong các trang web. Nó cung cấp cache đơn hay có thể chia sẻ cache với hiệu năng cao.

Khi mà khởi tạo cache thì cần xác định các địa chỉ cụ thể của tất cả các memcached server trong cụm của bạn. Nếu không có gì được xác định thì nó sẽ giả định memcached chạy trên localhost bởi cổng mặc định nhưng nó không phải ý tưởng hay cho các trang web lớn hơn.

Phương thức writefetch trên cache chấp nhận thêm 2 phương thức để tận dụng lợi thế của các tính năng cụ thể để được cache. :raw để gửi trực tiếp giá trị đến server mà không cần serialization và giá trị phải là String hoặc Number. :unless_exist nếu không muốn memcached cài đè các giá trị đã tồn tại.

    config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"

III. Kết luận

Qua đây, ta có cái nhìn tổng qua về cache trong Rails các dạng của nó, các nơi lưu trữ cache. Đây mà một kĩ thuật giúp nâng cao tốc độ của một trang web cũng như trải nghiệm người dùng đồng thời giảm tải được bên Server phải không hoạt động nhiều với cùng request giống nhau.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí