[Rails]ビューキャッシュにTokyoTyrantを使ってHTMLを高速に表示する このエントリをはてなブックマークに登録

2010年08月16日

func09func09 / , ,

はじめに

今回はRailsをTokyoTyrantでビューキャッシュする方法についてです。

Railsのキャッシュ機能には、オンメモリ、ファイル、drb、Memcacheから好きなストレージを選んで利用することができます。高速で永続性可能なKeyValueストアのTokyoTyrantをプロジェクトで採用することも多いので、ビューキャッシュにTokyoTyrantを使えないか試してみました。

TokyoTyrant
http://fallabs.com/tokyotyrant/

TokyoTyrantのインストール

TokyoTyrantがまだインストールされていない場合は、各環境に合った方法でインストールしてください。

Macの場合

[cc lang='bash']
% sudo port install tokyotyrant
[/cc]

or ...

[cc lang='bash']
% brew install tokyotyrant
[/cc]

CentOSの場合

[cc lang='bash']
% sudo yum install tokyotyrant
[/cc]

TokyoTyrantサーバーを起動する

TokyoTyrantのサーバーを起動するには「ttserver」コマンドを使います。
[cc lang='bash']
% ttserver
2010-08-12T12:24:15+09:00 SYSTEM --------- logging started [25254] --------
2010-08-12T12:24:15+09:00 SYSTEM server configuration: host=(any) port=1978
2010-08-12T12:24:15+09:00 SYSTEM opening the database: *
2010-08-12T12:24:15+09:00 SYSTEM service started: 25254
2010-08-12T12:24:15+09:00 INFO timer thread 1 started
2010-08-12T12:24:15+09:00 INFO worker thread 1 started
2010-08-12T12:24:15+09:00 INFO worker thread 2 started
2010-08-12T12:24:15+09:00 INFO worker thread 3 started
2010-08-12T12:24:15+09:00 INFO worker thread 4 started
2010-08-12T12:24:15+09:00 INFO worker thread 5 started
2010-08-12T12:24:15+09:00 INFO worker thread 6 started
2010-08-12T12:24:15+09:00 INFO worker thread 7 started
2010-08-12T12:24:15+09:00 INFO worker thread 8 started
2010-08-12T12:24:15+09:00 SYSTEM listening started
[/cc]

Railsでのキャッシュ設定

RailsにキャッシュストレージとしてTokyoTyrant を設定します。

Railsはデフォルトの場合、production環境ではキャッシュが有効になっていますが、development環境では無効となっています。開発環境でもキャッシュを有効にしたい場合は、config/environments/development.rbで「config.action_controller.perform_caching = true」を設定しましょう。

[cc lang='ruby']
# config/environments/(development|production).rb

...(略)
# キャッシングを有効にする
config.action_controller.perform_caching = true
# キャッシュストレージとしてTokyoTyrantのポート1978を指定する。
ActionController::Base.cache_store = :mem_cache_store, “localhost:1978”
...(略)
[/cc]

おもしろいのは、TokyoTyrantがMemcachedとインターフェイスの互換性を持っているため、キャッシュストレージを:mem_cache_storeのままで、ポートを1978にしてやれば、そのまま利用可能なんです。
しかしMemcachedのexpires(キャッシュしている期限)はTokyoTyrantには無いので、Memcacheで有効期限を使った感覚でコードが書けない点に注意する必要があります。

描画するHTMLの一部だけをキャッシュするには

フラグメントキャッシュというキャッシュ方法がRailsには用意されています。

これはHTMLの一部分だけキャッシュしたい場合に有効です。例えば・・

  • ログイン情報など、すべてのユーザーに対して共通でない部分がある。(この場合がほとんど)
  • ページの一部分だけ、DBの処理が重くて、ページを表示するのに時間がかかる。

という場合、フラグメントキャッシュがとても効果を発揮します。

Railsはフラグメントキャッシュが見つかれば、キャッシュを利用してHTMLを描画しますし、なければキャッシュを作成して、次回アクセス時の負荷が減ります。

さらにコントローラー側でキャッシュが見つからなかった時だけ、DB処理をかけたりできるので、MVCのルールから外れずに、効果的にレスポンス速度をあげることが可能です。

ビュー側のコード

[cc lang='ruby']
# app/views/pages/index.html.erb

<%- cache 'pages-index' do -%>
<%= Time.zone.now %>にキャッシュされたページです。
<%= @very_heavy_task_result %>
<%- end -%>
[/cc]

cache(キャッシュ名) do ... end でブロック内をキャッシングします。cacheブロックで囲むだけでキャッシュするというのが直感的です。

コントローラー側のコード

[cc lang='ruby']
# app/controllers/pages_controller.rb

class PagesController < ApplicationController
def index
unless read_fragment 'pages-index'
# キャッシュが見つからなかった時の処理をここに書く
@very_heavy_task_result = very_heavy_task
end
end
private
def very_heavy_task
sleep 5
rand(100)
end
end
[/cc]
コントローラーのアクション内では read_fragment(キャッシュ名) でキャッシュが存在するかチェックできます。キャッシュが見つからなかった場合のみ、部分キャッシュで利用するデータを取得しています。

キャッシュに有効期限をつける

一度キャッシュしたHTMLを失効するには、明示的にexpire_fragment(キャッシュ名)メソッドを実行する必要があります。例えば10分間だけキャッシュさせたい場合、キャッシュストレージにMemcachedを利用していると、cache(キャッシュ名, :expires_in => 10.minutes) というような書き方が可能なので便利です。

TokyoTyrantを利用している場合(:file_storeでも)、キャッシュ名に有効期間のスタンプを付けておくことで、擬似的な有効期限を設けてみます。

タイムスタンプをキャッシュしたい秒数で割った数値をスタンプとしてみます。

[cc lang='ruby']
# app/controllers/application.rb
class ApplicationController < ActionController::Base
...(略)
def cache_key(name, options = {})
cache_key = nil
if options[:expires_in]
ts = Time.zone.now.to_id / options[:expires_in].to_i
cache_key = "#{name}+#{ts}"
else
cache_key = name
end
cache_key
end
...(略)
end
[/cc]

ApplicationControllerにcache_keyというメソッドを定義。引数に :expires_in => 秒数 を追加すると、キャッシュ名+スタンプ の形でキャッシュ名を作成します。

[cc lang='ruby']
# app/controllers/pages_controller.rb
class PagesController << ApplicationController
def index
# 10分間キャッシュする用のキャッシュ名を取得
@cache_key = cache_key('pages-index', :expires_in => 10.minutes )
unless read_fragment @cache_key
# SomeThing ...
end
end
end
[/cc]

おまけ:キャッシュの破棄

キャッシュ名にタイムスタンプをつける方法だと、大量のキャッシュがたまってしょうがないので、いらなくなったキャッシュを捨てる方法です。たまったままで良いならその限りではないです。
[cc lang='ruby']
class TokyoTyrantSweeper
def initialize(host = 'localhost', port = 1978)
@host, @port = host, port
end
# キーをリストアップ
def list
%x{tcrmgr list #{@host}:#{@port}}.split(/\n/)
end
# キーを検索
def find(keyword)
%x{tcrmgr list #{@host}:#{@port} | grep #{keyword}}.split(/\n/)
end
# キーを検索しつつ削除
def sweep(keyword)
find(keyword).each do |key|
%x{tcrmgr out #{@host}:#{@port} '#{key}'}
end
end
end
[/cc]

TokyoTyrantSweeperというお掃除用のクラスを作りました。rufus_tokyo を使えばよかったのかもしれませんが、ただ掃除したいだけだったので、tcrmgr コマンドを直接叩く簡単なものにしました。

こんな感じで使います。
[cc lang='ruby']
tt_sweeper = TokyoTyrantSweeper.new('localhost','1978')
# 'hoge' で検索して引っかかったキャッシュを全部捨てる
tt_sweeper.sweep('hoge')
[/cc]

最後に

Rails+TokyoTyrantでビューキャッシュする方法についていろいろと書いてみました。

Memcachedと違って有効期限が使えないことから、TokyoTyrantに依存するコードが入ってしまうのが反省すべき点となってしまいました。ActiveSupport::Cache::Store を継承した、ActiveSupport::Cache::TokyoTyrantStore を作るのが一番スマートかもしれません。

諸事情でMemcachedが使えないなんて時にどうぞ。

  1. メモからはじめる情報共有 DocBase 無料トライアルを開始
  2. DocBase 資料をダウンロード

「いいね!」で応援よろしくお願いします!

このエントリーに対するコメント


トラックバック
  1. TokyoTyrantは、キャッシュサーバとして使えるのか | Cloud Berry2012/01/11, 10:11 PM

    […] [Rails]ビューキャッシュにTokyoTyrantを使ってHTMLを高速に表示する Tokyo Tyrant + Lua Extensionで作るクエリキャッシュサーバ […]

we use!!Ruby on RailsAmazon Web Services

このページの先頭へ