Bên cạnh Russian doll caching, còn nhiều techniques khác để speed up performance trong Rails apps. Lần này chúng ta sẽ xem built-in conditional GET support của Rails, cho phép bạn store rendered pages trong browser cache của user.
👋 Và nếu bạn thích đọc thêm về performance ngoài caching, chúng tôi viết nhiều hơn về Ruby (on Rails) performance, check out Ruby performance monitoring checklist.
Headers Etag Và Last-Modified
Khi browser execute HTTP GET request cho page trong Rails app, router sẽ link nó đến một controller action. Controller sẽ request required data từ database và render view. HTTP response (với 200 OK response code) sau đó sent back to browser với rendered HTML từ view trong response body để browser parse và display.
Khi resource được request again, chúng ta đi qua same pipeline. Trong some situations, điều này unnecessary vì page didn't change in the meantime. Cho đó, HTTP cung cấp ETag và Last-Modified headers. Dùng these, browser có thể store response body, và dùng headers để invalidate chúng khi stale.
Etags, hay Entity tags, dùng cho client-side cache validation, nên bạn có thể nghĩ chúng như cache keys cho HTTP responses. Chúng passed back to browser trong HTTP response header cho every request.
~ $ curl -I https://localhost:3000/products/1
HTTP/1.1 200 OK
...
ETag: W/"9462d76cc55aeb6249fa990e39231c7c"
Last-Modified: Wed, 25 Apr 2018 08:27:04 GMT
...
Nếu response repeated later, browser tìm existing response trong cache và dùng stored Etag từ last request làm If-None-Match header. Header này tell Rails app rằng chúng ta đã có version này trong cache.
Nếu Etag từ request match current one, Rails sẽ send 304 Not Modified response without response body. Điều này tell browser dùng version từ local cache thay thế.
~ $ curl -i -H 'If-None-Match: W/"9462d76cc55aeb6249fa990e39231c7c"' https://localhost:3000/products/1
HTTP/1.1 304 Not Modified
...
ETag: W/"9462d76cc55aeb6249fa990e39231c7c"
Last-Modified: Wed, 25 Apr 2018 08:27:04 GMT
...
Conditional GET Requests Trong Rails
Nếu request page từ local Rails application, ta thấy Rails add Etag cho each request automatically. Nếu request same page vài lần liên tiếp, ta thấy Etag change cho mỗi request.
Mặc dù Rails generate Etag cho each request by default, nó dùng digest của whole response body để generate. Nghĩa là <%= csrf_meta_tags %> trong layout throws Etag off, vì csrf-token meta tag change cho each request. Vì điều đó change body cho each request, Etag bị invalidate và local cache marked stale.
Bên cạnh đó, Rails sẽ never return 304 Not Modified by default, vì local cache never explicitly marked as fresh trong controller.
fresh_when Và stale?
Để dùng Etags từ request header cho conditional GETs, chúng ta cần explicitly mark object trong local cache là "fresh". Ví dụ, cho page show product, chúng ta keep cache fresh miễn là product và view templates không change. Để làm work, chúng ta làm 2 điều.
- Explicitly set values làm up Etag, vì dùng whole response body require render whole body để check cached response valid, negate speedup từ caching page locally.
- Compare Etag từ request header với predicted Etag trước render view, và omit rendering nếu match.
Rails có helpers làm everything cho chúng ta. Chúng ta explicitly base Etag và Last-Modified date trên product dùng fresh_when.
def show
@product = Product.find(params[:id])
fresh_when @product
end
Nếu bạn có explicit respond_to block, dùng stale? thay vì fresh_when.
def show
@product = Product.find(params[:id])
if stale?(@product)
respond_to do |format|
format.html
format.json { render json: @product }
end
end
end
Bây giờ, request product page sẽ cache response locally. Subsequent requests đến same page sẽ include Etag tell Rails chúng ta có cached response, so sánh với new Etag. Nếu match, Rails skip rendering page, và return 304 Not Modified immediately.

Lưu ý: Refresh page sẽ luôn request uncached version. Để test conditional GETs work, navigate away dùng link hoặc back button thay vì refresh.
Bạn thấy bài này và previous articles trong AppSignal Academy series như thế nào? Chúng tôi còn vài bài về caching in Rails lined up, nhưng đừng ngần ngại cho chúng tôi biết bạn muốn chúng tôi viết về gì (caching-related hoặc otherwise) tiếp theo!
