QueryCache

Hi,

is someone interested in using the QueryCache?
It seams to be an old part of rails. I enable
the querycache in the database-specification with
a:

query_cache: true

and get an exception from ActiveRecord … ConnectionSpecification
There is an if-clause, which did not work together with
query_cache (l. 247):

def self.connection=(spec) #:nodoc:
if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
active_connections[name] = spec
elsif spec.kind_of?(ConnectionSpecification)
config = spec.config.reverse_merge(:allow_concurrency =>
@@allow_concurrency)
self.connection = self.send(spec.adapter_method, config)
elsif spec.nil?
raise ConnectionNotEstablished
else
establish_connection spec
end
end

There is no clause for a QueryCache. But QueryCache acts like
a connection. So I change it to:

if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter) 

or
spec.kind_of?(ActiveRecord::QueryCache)

The next problem was the concurrency-model of rails. The QueryCache
deletes
all entries if an update, insert or delete-action was done. But most
performance-sensitive enviroments use a few
ruby/rails/mongrel-processes.
So the QueryCache-Module isn’t scaleable.

In our Webshop we have different kinds of data. Some data, which isn’t
changed at runtime (Categories, Products) and data, which will be
changed by user-actions.

I add a class-method to ActiveRecord::base, so that you can specify
your class as ‘cacheable’. Only this data will be cached. And I deleted
the old update/insert/delete-semantic. Now the cache caches only
‘cacheable’-classes. I add the rewrite of the query_cache-File at the
end of this mail.

The reason? Why? In the past I’m working with spring/hibernate-apps.
There ist the ehcache, which boosts the performance. If the application
don’t need to ask the database, it will be very fast. And because
the ehcache ist between application-logic and database, you don’t
had to think about caching. I had some experience with template-caching
and it’s more intrusive.

With my few changes on the QueryCache, the performance will speed up
from 4 Request per second to 6 requests per second. The database and
the rails-app and the ab (apachebench) are on the same box. I hope
the QueryCache will speed up more in an environment with the database
on another box.

The next work will be the change of the datastructure for the cache.
At the moment it’s a ruby-hash. You will get memory-problems, if there
are many cacheable-entities in the cache. An LRU or LFU-Module will
be better. It may be possible, to use a size-Parameter with the
cacheable-call. Than every class could have its own cache-size.

we will see.
jens

query_cache.rb

module ActiveRecord
class QueryCache #:nodoc:
def initialize(connection)
@connection = connection
@query_cache = {}
end

def clear_query_cache
  @query_cache = {}
end

def select_all(sql, name = nil)
  if ActiveRecord::Base.cacheable? extract_class(name)
    (@query_cache[sql] ||= @connection.select_all(sql, name)).dup
  else
    @connection.select_all(sql, name)
  end
end

def select_one(sql, name = nil)
  @query_cache[sql] ||= @connection.select_one(sql, name)
end

private
  def method_missing(method, *arguments, &proc)
    @connection.send(method, *arguments, &proc)
  end

  def extract_class name
    name ? name.split[0] : nil
  end

end

class Base
@@cacheable_classes = []
# Set the connection for the class with caching on
class << self
def cacheable
@@cacheable_classes << self.name
end

  def cacheable? name
    @@cacheable_classes.include? name
  end

  alias_method :connection_without_query_cache=, :connection=

  def connection=(spec)
    if spec.is_a?(ConnectionSpecification) and 

spec.config[:query_cache]
spec = QueryCache.new(self.send(spec.adapter_method,
spec.config))
end
self.connection_without_query_cache = spec
end
end
end

class AbstractAdapter #:nodoc:
# Stub method to be able to treat the connection the same whether
the query cache has been turned on or not
def clear_query_cache
end
end
end