顯示具有 cache 標籤的文章。 顯示所有文章
顯示具有 cache 標籤的文章。 顯示所有文章

4/28/2007

Rails Cache 效率上的問題

Fragement Cache 是大家常用的Rails 內建 Cache 機制,不過昨天看到石鍋拌飯先生寫的rails缓存机制的几个问题,裡面提到一些 Fragement Cache 的問題,這裡做一下筆記。




1. read_fragment 實做方式效率不佳

actionpack/lib/action_controller/caching.rb 裡面,read_fragment 假設是使用 file_store 的話,實做方式不佳
def read(name, options = nil) #:nodoc:
File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil
end
每次判斷 fragment 都得 open file 一次,的確會損耗效率。解決方式是用 File.exist? 來判斷即可。

2. expire_fragment 使用 regular expression 效率不佳

不算問題啦,假設 expire_fragment 使用 regular expression ,根據內建的實做方式,他會一個一個目錄下去用 regular expression ,然後 regular expression 通常執行速度不佳,所以就....

解決方式有兩個,第一個就是不要在 expire_fragment 使用 regular expression,另外就是使用 backend script 來 expire cache 即可。

3. expire_cache 的時間點

簡單講就是 controller read_fragment 的到 cache ,但是當 render 的時候卻剛剛好被 expire 掉了,就會出現 nil object error 。因為發生機率比較低,解決方式只能說視情況而定。

當然也可以照著原作者的講法,在 view 當中判斷 Model obj 是不是存在,不過這個作法就比較 dirty。

3/28/2007

Fragment Cache Part 2

上次介紹了如何使用最基本的 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

3/27/2007

Fragment Cache Part 1

之前介紹過 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 機制。

12/22/2006

Rails Cache 再探:Page Cache 使用心得

以前介紹過 Rails Cache 第一步 : Page Cache。自從摸透了 Page Cache 之後,本人實在愛不釋手,至今已經用 Page Cache 寫過 3 ~ 4 個敝公司的行銷專案。我發現到 Page Cache 最大的特色就是可以產生一個完全靜態的頁面,而且完全不經過 cgi 就直接給 Web Server讀取。所以當你使用 page cache ,這意味著
  1. 最快的速度(靜態頁面絕對最快)
  2. 最高的安全性(不跑 cgi 絕對最安全)
  3. 前端 Web Server 完全不需要安裝 Ruby on Rails
等絕對優勢。

當然你會開始發問啦,這個東西只有幾個靜態頁面,但是遇到需要動態更新的資料的頁面要怎麼辦?我的回答是『不怎麼辦呀!!!』,本來 Page Cache 就不是用在這方面的,他是用在特殊的用途的,而且這些特殊用途其實還蠻常見的。

適用範圍

有時候某些流量超級巨大的頁面,像是一個網站的首頁,通常流量都超大的。這個時候網站的首頁還要動態產生那就實在太苦了機器,還是讓 Page Cache 產生出來靜態頁面,然後一個小時更新一次就好。

或是我們寫到某些計算量太大的頁面,像是一些資料庫資料的分析,不管怎麼算都需要計算個 10分20分鐘的時候,既然無法每個 request 都動態產生頁面,還是用 cache 一天產生一次這樣的大量計算的頁面吧,而且通常這種計算量大的頁面都不會要求即時的。

另外一個使用範圍,就是一些小型行銷專案。有些案子是一些小活動的頁面,通常他們只需要一個到兩個小頁面即可,這個時候也可以使用 Page Cache。不過這個時候我用 Page Cache 的原因在於通常負責行銷活動的機器有時候也會派上其他用途,一家公司通常不會特別撥一個機器去做活動頁面,這樣太浪費機器的資源。為了擔心行銷活動跟其他任務的機器綁在一起,如果我的 cgi 哪裡出了問題,影響到公司其他 team 的任務就不好意思了。另外一個原因是其他 team 的機器如果跑的是其他的任務,通常 frontend web server 是經過某些用途特製化過了,網管不會希望這樣特製化的 frontend web server灌太多其他 cgi 程式影響原本的用途。這時候我也會使用 Page Cache 來寫 Ruby on Rails,這個方面是取其安全性跟
frontend web server 不需要安裝 cgi的特色。

使用技巧

至於要怎麼使用呢?我把我目前寫行銷專案的技巧講出來好了。

首先你需要安裝一個 Mongrel ,然後跑在你的 Ruby on Rails程式在 80 以外的 port,這裡用 4000 來說明好了。再來用 Firewall 去擋住 4000 port,只允許 localhost 可以 access 4000 port,這樣就相當安全了。

再來請在每一個 cache page 的 action 以外,另外寫一個 action 專門 expire 這個 page cache。這裡假設 action 名字叫做 expire。

最後用 crontab 在本機端上定期 trigger一下剛剛寫好的那個 expire action,記得要去 trigger Mongrel 開的 port 4000,而非 80 port。這裡就可以做到定期rotate refresh cache 的功能,並且剛剛不是有說到 firewall 僅僅允許 localhost 去 access ,就是為了讓本機端去做到定期 rotate 的功能。

結論

到目前為止,雖然這個方式算是 special case 專用,但是這個方式,我覺得相當的好用而且簡單易懂,兼顧 Ruby on Rails 的方便性,並且擁有優異的速度跟安全性,程式碼僅僅多加個四五行 add / expire cache 的程式碼。如果有人想將 Ruby on Rails 導入公司,但是 CTO 因為延展性,速度,安全性等等理由去打發你的話,可以先考慮使用這個方式來導入。通常一不導入則已,只要一導入 Ruby on Rails ,你的同事嘗了 Ruby on Rails 的甜頭,就很難回頭去寫原本的 PHP 或是 Java 了 XD


延伸閱讀


9/20/2006

Rails Cache 第一步 : Page Cache

Rails 分為 Page Cache ,Action Cache,Partial Cache,現在就先來講 Page Cache。

首先,將你想 cache 的 page 在 controller 裡面加入 caches_page :action_name
class AbcController < ApplicationController
caches_page :index
end
然後確定你的環境底下有 enable cache,Rails 裡面 development 環境下是 disable cache ,production 環境下是 enable cache。如果要在 development 下面 enable cache,可以在 config/environments/development.rb 下面,加入
ActionController::Base.perform_caching = true
即可。

如此以後你 click 這個頁面,他就會幫你 cache 住頁面。如果你仔細翻 log 的話,你會發現有這一段
Cached page: /abc.html (0.00062)
然後你發現到 public 底下有一個 abc.html,裡面就是你的 cache page。如果是 index action ,他就直接 cache 成 controller 的名字。如果是其他 action ,他就 cache 成 controller_name/action_name.html。
像是 ABC controller 的 index action 就 cache 成 public/abc.html
像是 ABC controller 的 go action 就 cache 成 public/abc/go.html

你可能會覺得奇怪,為何他的 cache page 跟其他的 System 的 cache 不太一樣。其他系統的 cache 檔名可能是 ajbieohhwio.86787e27.87887073 之類,為啥Rails 的這個 page cache 這個那麼的好讀?

仔細思考一下,當我們 request abc controller 的 index action 時候。大家都是打 http://www.example.com/abc 。那麼 lighttpd 接受到這個 request 的時候(這裡僅僅指 lighttpd ,其他 web server 不確定)。他會先去 document root 尋找 abc.html。結果他就發現有 abc.html ( cache 的 page ),就立刻 return回去。當中完全沒有進到 ruby cgi,完全沒有 cgi 快慢的問題,所以這是最快的 cache 方式

如果你某種情況要 expire cache。像是修改 db 之後,要將原先的 cache reload。你可以使用
     expire_page :action => 'index'
來手動 expire 掉。

但是要注意,這種 page cache 沒有設定 expire time 的方式。所以說,要嘛就是當經過某些特定的動作時 expire 掉 cache page,要嘛就是跑一個 backend process 去清掉 cache。這個網頁有批評這個 page cache 沒有 expire 機制根本就沒有用。

不過,對這個網頁的論點,我呈反對態度。

就舉個例子,樂多Blog的作法就很類似。裡面我們個人Blog每個頁面都是靜態 HTML 頁面,而非 PHP,每當我們做出一個改變的時候,管理頁面就會幫我們產生新的靜態頁面。這樣的作法當然在產生的時候比較慢,但是 show 頁面的時候可以達成比任何 CGI 還要快的效率。

結論

Cache 好不好用是看你怎麼用,以及什麼情況要用啥方式。而不是設計出一個每個情況都可以用的方式,那只會顯得臃腫沒意義。