ブログトップ 記事一覧 ログイン 無料ブログ開設

takihiroの日記 このページをアンテナに追加 RSSフィード

2009-01-29

[] 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_allselect_all_without_query_cache
select_all_with_query_cacheselect_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 の実現方法が見れてよかった。が、もっと知識が増えるのを期待していたので、少し残念。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/takihiro/20090129/1233226826