Make your Rails app faster with memcached. Part 1

Posted on December 13, 2007
1
2
3
4
5
6
7


port install memcached
apt-get install memcached 
emerge memcached
yum install memcached

or get the source and compile it.

Setup cache_fu

  • Run memcached

  • configure config/memcached.yml

Parts to cache in rails:

  1. Cache User Authentication restfull_authentication: User.current_user logged_in?

Those issue a DB query:

1
2
3
4


SELECT * FROM users WHERE (users.'id' = 1) LIMIT 1

edit user model (app/models/user.rb)

1
2
3
4
5
6
7


class User < ActiveRecord::Base 
  acts_as_cached 
  after_save :expire_cache 
end

  • note: only if you are not using cookie session store from rails2.0

change the method loginfromsession in lib/authenticated_system.rb

1
2
3
4
5
6
7
8
9


# Called from #current_user. First attempt to login by the user id stored in the session. 
# Changed to read from Memcached 
def login_from_session 
  #self.current_user = User.find_by_id(session[:user]) if session[:user] 
  self.current_user = User.get_cache(session[:user]) if session[:user] 
end 

Settings plugin I change the method self.object(var_name) in the file: vendor/plugins/settings/lib/settings.rb

1
2
3
4
5
6
7
8
9
10
11
12
13


acts_as_cached 
#retrieve the actual Setting record 
def self.object(var_name) 
  #Settings.find_by_var(var_name.to_s) 
  if Settings.cached?(var_name.to_s) 
    return Settings.get_cache(var_name.to_s) 
  else 
    setting = Settings.find_by_var(var_name.to_s)
    Settings.set_cache(var_name.to_s,setting) 
  end
end

then, I added a settings model: app/models/setting.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


class Setting < ActiveRecord::Base 
  validates_uniqueness_of :var 
  acts_as_cached 
  after_save :expire_cache 
  after_save :expire_cached_var 
  
  # Clear Cache by var 
  def expire_cached_var 
    Settings.all.each do |t| 
      Settings.clear_cache(t[0].to_s) 
    end
  end
end

Basic model caching

An example of article.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


class Article < ActiveRecord::Base 
  
  validates_uniqueness_of :permalink
  
  def self.find_permalink(permalink) 
    if self.cached?(permalink) 
      return self.get_cache(permalink) 
    else 
      article = self.find_by_permalink(permalink) 
      self.set_cache(permalink,article) 
    end
  end
end

THE BEST: partial caching:

here you can cache partials for example in the view code:

app/views/properties.html.erb

1
2
3
4
5
6
7
8


  <% @articles.each do |article| %>  
    <% cache article.permalink do %>  
      <%= render :partial => "each_article", :locals=> {:article => article} %> 
    <% end %>
  <% end %>

will create $RAILSROOT/tmp/caches/properties/propertypermalink.cache something like that, not sure what is the name

To sweep it after an update to the model:

property.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


class Property < ActiveRecord::Base

  acts_as_cached
  after_save :reset_cache, :sweep_memcached
  
  def sweep_partial_cache
    cache_dir = ActionController::Base.page_cache_directory+"/.."+"/tmp/cache"
    unless cache_dir == RAILS_ROOT+"/public"
      FileUtils.rm_r(Dir.glob(cache_dir+"/"+self.permalink.to_s+".cache")) rescue Errno::ENOENT
      RAILS_DEFAULT_LOGGER.info("Cache directory '#{cache_dir}' fully sweeped.")
    end
  end

auto_complete_for ajax using memcached

it improved from 10 req/sec to 350 req/sec to one of my apps.

comming soon…

in part 2 of this article