Cache Money Talk: Practical Application

34
Wolfram Arnold Wolfram Arnold www.rubyfocus.biz www.rubyfocus.biz Cache Money Practical Application

description

Cache Money is a new model caching framework for Active Record that transparently stores, synchronizes and expires model instances in memcached, through Active Record callbacks. It's a framework that provides almost all the pieces of transparently turning an application without model caching into one with model caching at the flip of a switch. In this presentation I'll show what the framework delivers, its limitation and what problems to watch out for when putting it in as drop-in intermediary for your database connection.

Transcript of Cache Money Talk: Practical Application

Page 1: Cache Money Talk: Practical Application

Wolfram ArnoldWolfram Arnoldwww.rubyfocus.bizwww.rubyfocus.biz

Cache Money

Practical Application

Page 2: Cache Money Talk: Practical Application

[email protected]

Outline

● Caching in Rails● Model caching● Cache Money API● What's not covered?● How do I know it's cached?● Advice—best bang for the buck?

Page 3: Cache Money Talk: Practical Application

[email protected]

:cache => Rails

Page 4: Cache Money Talk: Practical Application

[email protected]

Page CachingPage Caching

class CorpController < ApplicationControllerclass CorpController < ApplicationController

caches_pages :about, :privacy, :toscaches_pages :about, :privacy, :tos

endend

● Bypasses application● Fast!● Static content only—rare● Requires local file system

Page 5: Cache Money Talk: Practical Application

[email protected]

:choose => Store

Page 6: Cache Money Talk: Practical Application

[email protected]

BackendsBackends

config.cache_store =config.cache_store =

:mem_cache_store:mem_cache_store

:memory_store:memory_store

:file_store, "/path/to/cache/directory":file_store, "/path/to/cache/directory"

:drb_store, "druby://localhost:9192":drb_store, "druby://localhost:9192"

Page 7: Cache Money Talk: Practical Application

[email protected]

Action CachingAction Caching

class UsersController < ApplicationControllerclass UsersController < ApplicationController

caches_action :showcaches_action :show

def updatedef update

expires_actionexpires_action

endend

endend

● Can run before filters● ~1 order of magnitude slower

Page 8: Cache Money Talk: Practical Application

[email protected]

Fragment CachingFragment Caching

<% cache do %><% cache do %>

Some complicated slow-to-renderSome complicated slow-to-render

content here...content here...

<% end %><% end %>

● Stores of rendered snippets keyed on action● Maintaining expiration code tricky

Page 9: Cache Money Talk: Practical Application

[email protected]

:sweep => Cache

Page 10: Cache Money Talk: Practical Application

[email protected]

SweepingSweeping

class ListSweeper < ActionController::Caching::Sweeperclass ListSweeper < ActionController::Caching::Sweeper

observe Listobserve List

def def after_saveafter_save(record)(record)

expire_page(:controller => "lists",expire_page(:controller => "lists",

:action => %w( show public feed ), :id => list.id):action => %w( show public feed ), :id => list.id)

expire_action(:controller => "lists", :action => "all")expire_action(:controller => "lists", :action => "all")

# every place where List instances are used# every place where List instances are used

endend

class ListsController < ApplicationControllerclass ListsController < ApplicationController

caches_action :index, :show, :public, :feedcaches_action :index, :show, :public, :feed

cache_sweeper :list_sweeper, :only => [ :edit, :destroy ]cache_sweeper :list_sweeper, :only => [ :edit, :destroy ]

endend

Page 11: Cache Money Talk: Practical Application

[email protected]

:cache => Query

Page 12: Cache Money Talk: Practical Application

[email protected]

Query CacheQuery Cache

User Load (0.1ms)User Load (0.1ms) SELECT * FROM `users` SELECT * FROM `users` WHERE (`users`.`id` = 1572302630)WHERE (`users`.`id` = 1572302630)

CACHE (0.0ms)CACHE (0.0ms) SELECT * FROM `users` SELECT * FROM `users` WHERE (`users`.`id` = 1572302630) WHERE (`users`.`id` = 1572302630)

● Exact same query● No updates in between

Page 13: Cache Money Talk: Practical Application

[email protected]

:cache => Models

Page 14: Cache Money Talk: Practical Application

[email protected]

Views Controllers Models

DB

memcached

Page 15: Cache Money Talk: Practical Application

[email protected]

:cache => MONEY

Page 16: Cache Money Talk: Practical Application

[email protected]

User.find(id)User.find(id)

Page 17: Cache Money Talk: Practical Application

[email protected]

memcached

Models

Page 18: Cache Money Talk: Practical Application

[email protected]

Cache Money

User.find(id)User.find(id)

alias_method_chain :find_every, :cachealias_method_chain :find_every, :cache

alias_method_chain :find_from_ids, :cachealias_method_chain :find_from_ids, :cache

alias_method_chain :calculate, :cachealias_method_chain :calculate, :cache

User.get(“User:1/#{id}”)User.get(“User:1/#{id}”)

Page 19: Cache Money Talk: Practical Application

[email protected]

Indexed

class User < ActiveRecord::Baseclass User < ActiveRecord::Base

index :nameindex :name, :order => :desc, :order => :desc

endend

User.all(:conditions => ['id = ?', ...])User.all(:conditions => ['id = ?', ...])

User.find_by_User.find_by_namename(:order => “DESC”)(:order => “DESC”)

User.all(:conditions => ['User.all(:conditions => ['namename = ?, ...], = ?, ...], :order => “DESC”):order => “DESC”)

User.countUser.count

User.count(User.count(:name:name => “Jones”) => “Jones”)

Page 20: Cache Money Talk: Practical Application

[email protected]

:expiry => how?

Page 21: Cache Money Talk: Practical Application

[email protected]

Expiry

after_create :add_to_cachesafter_create :add_to_caches

after_update :update_cachesafter_update :update_caches

after_destroy :remove_from_cachesafter_destroy :remove_from_caches

User.createUser.create

@user.attributes = { :name => ... }@user.attributes = { :name => ... }

@[email protected]

Page 22: Cache Money Talk: Practical Application

[email protected]

:what => else?

Page 23: Cache Money Talk: Practical Application

[email protected]

Associations

class Userclass User

has_many :videoshas_many :videos

endend

class Videoclass Video

index [id, user_id]index [id, user_id]

belongs_to :userbelongs_to :user

endend

@user.videos.find(id)@user.videos.find(id)

Page 24: Cache Money Talk: Practical Application

[email protected]

:must_have => Joins

Page 25: Cache Money Talk: Practical Application

[email protected]

Joins :throughclass Videoclass Video

has_many :taggings has_many :taggings

has_many :tags, :through => :taggingshas_many :tags, :through => :taggings

endend

@[email protected]

SELECT `tags`.* FROM `tags`SELECT `tags`.* FROM `tags`INNER JOININNER JOIN taggings taggings ONON tags.id = taggings.tag_id tags.id = taggings.tag_idWHERE ((`taggings`.taggable_id = 1912438135) AND WHERE ((`taggings`.taggable_id = 1912438135) AND (`taggings`.taggable_type = 'Video'))(`taggings`.taggable_type = 'Video'))

Page 26: Cache Money Talk: Practical Application

[email protected]

:include => Trouble

Page 27: Cache Money Talk: Practical Application

[email protected]

Joins :includeVideo.find(:all, Video.find(:all, :include:include => :taggings, => :taggings,

:conditions => “taggings.id IS NOT NULL”):conditions => “taggings.id IS NOT NULL”)

SELECT `videos`.`id` AS t0_r0, `videos`.`user_id` AS SELECT `videos`.`id` AS t0_r0, `videos`.`user_id` AS t0_r1, `taggings`.`id` AS t1_r0, t0_r1, `taggings`.`id` AS t1_r0, `taggings`.`tag_id` AS t1_r1, `taggings`.`tag_id` AS t1_r1, `taggings`.`taggable_id` AS t1_r2, `taggings`.`taggable_id` AS t1_r2, `taggings`.`taggable_type` AS t1_r3 FROM `videos``taggings`.`taggable_type` AS t1_r3 FROM `videos`LEFT OUTER JOINLEFT OUTER JOIN `taggings` `taggings` ONON `taggings`.taggable_id = `videos`.id AND `taggings`.taggable_id = `videos`.id AND `taggings`.taggable_type = 'Video' WHERE `taggings`.taggable_type = 'Video' WHERE (taggings.id IS NOT NULL)(taggings.id IS NOT NULL)

Page 28: Cache Money Talk: Practical Application

[email protected]

class Videoclass Video

has_many :taggingshas_many :taggings

endend

Video.find(:all, :include => :taggings)Video.find(:all, :include => :taggings)

→→ 1 SQL Query, not cacheable1 SQL Query, not cacheable

Video.find(:all).each do |v|Video.find(:all).each do |v|

v.taggingsv.taggings

endend

→ → N + 1 SQL Queries, cacheableN + 1 SQL Queries, cacheable

Page 29: Cache Money Talk: Practical Application

[email protected]

:options_for => Joins

Page 30: Cache Money Talk: Practical Application

[email protected]

Options for join queries

● Denormalize– Use after_save, after_destroy to keep in sync

● Maintain your own index– e.g. acts_as_most_popular

● Deconstruct into multiple queries– e.g. drop :include

Page 31: Cache Money Talk: Practical Application

[email protected]

:caching => Evolution

Page 32: Cache Money Talk: Practical Application

[email protected]

If it's supposed to help if must taste bitter.

Page 33: Cache Money Talk: Practical Application

[email protected]

Even if tastes bitter, it might still not help.

Page 34: Cache Money Talk: Practical Application

[email protected]

Thank You!

github.com/nkallen/cache-money/

github.com/wolframarnold/acts_as_most_popular

Wolfram ArnoldWolfram Arnoldwww.rubyfocus.bizwww.rubyfocus.biz