oyaji's Blog

rails fragement cache

之前介紹過 Page Cache,那是一個將所有動態內容都變成靜態頁面,藉由不經過 CGI ,來達到最大覆載度的技巧。但是他的應用性不夠廣泛,僅限於下面的用途


  1. 流量超級大的頁面
    通常是 index page,一個網站可能有幾百個頁面,但是通常 index page 一個頁面就佔了三到五成的流量,而且 index page 通常每個網站都是一個小時,了不起 15 分鐘更新一次,這個時候用 index page 可以大幅度增加網站覆載度。
  2. 頁面修改的頻率遠遠小於讀取的頻率
    例如 Blog 系統,我可能一天寫一篇文章,但是一天讀取我的 Blog 的人可能有幾千個,這個時候為什麼每次讀取頁面的時候都得去 DB 撈資料,組合,Render 出來呢?還不如用靜態頁面儲存,然後每次修改頁面時都重新產生新的 HTML 即可

但是,如果要利用 Page Cache 做到某些部份的功能實在有點麻煩。像是在同一個頁面裡面,有些地方是很少機會修改,希望能夠 Cache 起來,但是有些地方修改頻率超頻繁,不能夠用 Page Cache 來做。這個時候,我們就可以使用 Partial Cache 的方式來加快速度,在Rails 裡面 Partial Cache 叫做 Fragment Cache。

使用方式如下

在 View 裡面

選定要 Cache 的部份,將他用 cache block 包起來

 

<% cache do %>
我們要 cache 的 content
<% end %>
如此,當我們用 production mode 的時候,你就會發現會出現 tmp/cache/你的controller/你的action.cache 這個檔案,也就是 cache 的內容。並且 reload 的時候,那個 block 的 content 都是不變的。

理論上 cache 是 work 的,但是如果你去翻 log 時,你會發現 db 一樣會去做相關的 operation。原因是因為我們並沒有告訴 Rails ,Controller 裡面某段 code 是已經 cache 好的東西,不需要去執行。我們都知道 db 通常是最大的 bottleneck ,如果 cache 沒辦法避免 db operation,那這樣的 cache 也僅僅省去了 render 的時間,並沒有太多幫助。

在 Controller 裡面

為了避免這樣,我們要在 controller 裡面指明,當 Fragment Cache work 時,某些 operation 是可以不用執行的。

 

 

unless read_fragment :action => '這個action的名字'
跟 cache 有關的 operation
只要有 cache ,就不需要去執行的 code.....
end

 

如此如果已經 cache 過了, Rails 就會避開這段 code 的執行。

要如何 Expire Cache 

Expire Cache 也是相當的方便,在 controller 裡面使用 expire_fragment 即可。

 

expire_fragment :action => '這個action的名字'

 

以上都是最最最基本的 Fragment Cache 的機制,但是如果只知道這些東西,根本做不了太多事情,我們下次來談談比較實用的 Fragment Cache 機制。

 
 

上次介紹了如何使用最基本的 Fragment Cache,現在來加上一些小小的小 trick。上次介紹在 View 裡面要這樣使用Fragment Cache

<% cache do %>
我們要 cache 的 content
<% end %>

 

Cache 會出現在 RailsRoot/tmp/cache/你的host/你的controller/你的action.cache 這個檔案裡面。假設上面的 action 是在 lala controller 的 haha action ,那們上面的寫法跟 下面的寫法效果是一樣的

 

<% cache(:controller => 'lala' , :action => 'haha' ) do %>
我們要 cache 的 content
<% end %>

 

簡單講,就是最上面的 cache do 寫法算是預設值,寫不寫好像沒差,當這個頁面只有這裡需要 cache 時,這樣寫就很方便。不過如果這個 haha action 會依照後面接的 id 的不同顯示不同的內容時,你可以很直覺的加上 :id 這個選項

 

<% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] ) do %>
我們要 cache 的 content
<% end %>

 

Cache 會出現在 RailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.cache 這個檔案裡面。簡單講,又多了一層以 action 為名的目錄,目錄裡面每個 id 都有自己的 cache file。這樣的作法可以簡單依照變數的不同區分 cache file 。

不過?

但是老實說,到目前為止,這個 fragment cache 用途還是不大。我們一個 Action 只能使用一個 Partial Cache,那如果同一個頁面有兩個以上的地方要 cache 呢?Partial Cache 的概念就是一個網站有很多個 block 可以被許多頁面共用。假設某個頁面有兩個部份要 cache ,一個是 RSS ,另外一個是 Info 這個地方。那我們依照 Rails 聖經本的方法,使用 part 變數供我們使用

 

<% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'rss' ) do %>
我們要 cache 的 rss content
<% end %>
<% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'info' ) do %>
我們要 cache 的 info content
<% end %>

 

如此,相關的 Cache 會產生在 RailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.part=rss.cache 還有RailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.part=info.cache 這兩個 file 裡面。仔細看就知道,他的命名機制就是 id. part=part_name.cache 這樣的寫法。很簡單吧。

到現在 Partial Cache 才從還好變成好用的階段。read_fragment 或是 exprie_fragment 都是使用

 

:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'info' 

這樣的命名機制來操作,不難吧。

還有嗎?

到現在為止,我還沒看過 :part 還不夠的情況,不過如果真的覺得用 controller , action , id , part 都不夠區分你的 cache ,還有最後一招

 

<% cache(:controller => 'lala' , :action => 'haha' , :id => params[:id] , :part => 'rss' , :part2 => 'rss'
, :part3 => 'rss' .... ) do %>
我們要 cache 的 rss content
<% end %>

簡單講,其實 :part 只是聖經本這樣寫,所以在 Rails Fragment Cache 機制裡,id 以後的 fragment 命名方式不限制,而且數量不限制,只要你不嫌打字太累,你大可以用幾百層去命名你的 cache 。出來的 cache file 命名規則是這樣 ailsRoot/tmp/cache/你的host/你的controller/你的action/這個頁面的id.part=rss&part2=rss&part3=rss.cache。總之隨便你怎麼命名 :p

 

后续看到的文章:
×××××××××××××××××

I am using Devise as authenticating solution in Rails and I have a cached fragment :recent_users.

I want this fragment to expire when a new user is registered, changed or removed, so I put in my(manually created) users_controller.rb

 

class UsersController < ApplicationController
    cache_sweeper :user_sweeper, :only => [:create, :update, :destroy]
...

But my fragment does not expire when new creates or changes.

My user_sweeper contains basic prescriptions

 

class UserSweeper < ActionController::Caching::Sweeper
observe User

def after_save(user)
   expire_cache(user)
end

def after_destroy(user)
  expire_cache(user)
end

private
  def expire_cache(user)
    expire_fragment :recent_users
  end
end

What am I doing wrong?

Problem solved!

I followed this steps and everything works:

 

$ mkdir app/controllers/users
$ touch app/controllers/users/registrations_controller.rb

In registrations_controller.rb

 

class Users::RegistrationsController < Devise::RegistrationsController
  cache_sweeper :user_sweeper, :only => [:create, :update, :destroy]    
end

Problem was that Registrations in Devise is a separate controller.




Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee