2009-01-29
■[Rails] Query Cache
ActiveRecord に Query Cache という機能があることを知りました。 Rails 2.0 で入った機能のようです(Riding Rails: Rails 2.0: It’s done!)。
使い方と実装を見てみます。Ruby は 1.8.7-p72、Rails は 2.2.2 です。
以下、下準備です。
% rails depot % cd depot % ruby script/generate scaffold product title:string price:integer % rake db:migrate
コンソールで動かしてみる
まずは通常通り find してみます。
% ruby script/console >> Product.all => [] >> Product.all => [] >>
ログには次のように 2 回 SQL が発行されたことが出力されています。
Product Load (0.4ms) SELECT * FROM "products" Product Load (0.2ms) SELECT * FROM "products"
次に、Query Cache を使って同じことをしてみます。
>> Product.cache do ?> Product.all >> Product.all >> end
ログを見ると、2 回目の find 時にキャッシュが使われたことが分かります。
Product Load (0.5ms) SELECT * FROM "products" CACHE (0.0ms) SELECT * FROM "products"
アクションの動作を見る
ここで、サーバを立ち上げてブラウザ経由でアクセスしてみます。Query Cache の機能を見るために、ProductsControllerのコードを少し変えました。
class ProductsController < ApplicationController def index @products = Product.find(:all) @products = Product.find(:all) # 2 回 select が実行されるように追加 end
これで、http://localhost:3000/products/にアクセスすると、次のようなログが出力されました。
Product Load (0.4ms) SELECT * FROM "products" CACHE (0.0ms) SELECT * FROM "products"
デフォルトで Query Cache の機能を使うようになっているようです。
実装を見る(モデル)
まずは、active_record/query_cache.rb を見ます。
module ActiveRecord module QueryCache def cache(&block) if ActiveRecord::Base.configurations.blank? yield else connection.cache(&block) # ○ end end
○の箇所が重要で、connection の cache メソッドに丸投げしています。
次に、active_record/connection_adapters/abstract/query_cache.rb を見てみます。
まずは、丸投げされたメソッド cache を見ます。
def cache old, @query_cache_enabled = @query_cache_enabled, true @query_cache ||= {} # (1) yield ensure clear_query_cache @query_cache_enabled = old end
@query_cache_enabled 変数を保存しつつ、ブロックを呼び出しています。
(1) より、キャッシュはハッシュで持つようになっているようです。
これだけだと、ブロックを呼び出しているだけで、どこでキャッシュの処理をしているか分かりませんが、実際には次のコードで実現していました。ちょっと長いです。※説明しやすいように切り貼りしています。
module ActiveRecord module ConnectionAdapters module QueryCache class << self def included(base) base.class_eval do alias_method_chain :select_all, :query_cache # (1) end end end def select_all_with_query_cache(*args) if @query_cache_enabled # (2) cache_sql(args.first) { # (3) select_all_without_query_cache(*args) } else select_all_without_query_cache(*args) end end def cache_sql(sql) result = if @query_cache.has_key?(sql) # (4) log_info(sql, "CACHE", 0.0) # (5) @query_cache[sql] else @query_cache[sql] = yield end end end
まず、(1) で select_all メソッドを select_all_with_query_cache メソッドで置き換えています。
alias_method_chain を上記のように使用すると、次のようなメソッド名の変化が起きます。
| 元のメソッド名 | - | alias_method_chain後のメソッド名 |
|---|---|---|
| select_all | → | select_all_without_query_cache |
| select_all_with_query_cache | → | select_all |
select_all メソッドは何かというと、find メソッドの中で使われている、検索系の SQL を実行するメソッドです。active_record/base.rb の find_by_sql メソッドを見ると分かりやすいです。
def find_by_sql(sql) connection.select_all(sanitize_sql(sql), "#{name} Load").collect! {|record| instantiate(record) } end
(2) で、@query_cache_enabled 変数を見て、Query Cache の機能を使うかどうか分岐しています。Query Cache する場合には、(3) のように cache_sql メソッドを呼びます。
cache_sql は @query_cache のハッシュにキャッシュがあるかどうか判断して、キャッシュがある場合にはそれを返し、ない場合には、select_without_query_cache を実行して、通常通り SQL を発行します。
実装をみる(コントローラ)
アクションでは、デフォルトで Query Cache を使うようになっていました。 action_controller/caching/sql_cache.rb に Query Cache を使っている場所がありました。
module ActionController #:nodoc: module Caching module SqlCache def self.included(base) #:nodoc: if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache) base.alias_method_chain :perform_action, :caching # (1) end end protected def perform_action_with_caching # (2) ActiveRecord::Base.cache do perform_action_without_caching end end end end end
ここでも、(1) のところで alias_method_chain を使って、perform_action を (2) の Query Cache でラップしたメソッドで置き換えています。
perform_action は アクションを処理するコントローラのメソッドです。base.rb を見ると、動作が分かります。
まとめ
Query Cache の実現方法が見れてよかった。が、もっと知識が増えるのを期待していたので、少し残念。
- 23 http://www.google.co.jp/search?sourceid=navclient&hl=ja&ie=UTF-8&rlz=1T4ADBF_jaJP289JP290&q=Flash 円グラフ
- 16 http://www.google.co.jp/search?hl=ja&client=firefox-a&rls=org.mozilla:ja:official&hs=NE0&q=Code128+バーコード 作成方法&btnG=検索&lr=lang_ja
- 10 http://reader.livedoor.com/reader/
- 8 http://ezsch.ezweb.ne.jp/search/ezGoogleMain.php?query=死刑執行&start-index=4&adpage=3&mode=02
- 7 http://www.google.co.jp/search?q=RSpec&lr=lang_ja&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:ja:official&client=firefox
- 6 http://www.google.co.jp/reader/view/
- 6 http://www.google.co.jp/search?sourceid=navclient&aq=t&hl=ja&ie=UTF-8&rls=GGLJ,GGLJ:2006-34,GGLJ:ja&q=添付ファイル+ファイル名+文字化け
- 5 http://www.google.com/search?hl=ja&lr=lang_ja&ie=UTF-8&oe=UTF-8&q=ruby+spec+mock+stub&num=50
- 4 http://ezsch.ezweb.ne.jp/search/ezGoogleMain.php?query=バーコード+作成&start-index=4&adpage=3&mode=02
- 4 http://www.google.co.jp/search?hl=ja&client=firefox-a&channel=s&rls=org.mozilla:ja:official&q=LPIC レベル2 勉強期間&btnG=検索&lr=