Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Caching for Fun and Profit

Caching for Fun and Profit

The slides from a presentation I gave at June's Indy.rb

Other Decks in Programming

Transcript

  1. NUMBERS EVERYONE SHOULD KNOW L1 cache reference 0.5 ns Branch

    mispredict 5 ns L2 cache reference 7 ns Mutex lock/unlock 100 ns (25) Main memory reference 100 ns Compress 1K bytes with Zippy 10,000 ns (3,000) Send 2K bytes over 1 Gbps network 20,000 ns Read 1 MB sequentially from memory 250,000 ns Round trip within same datacenter 500,000 ns Disk seek 10,000,000 ns Read 1 MB sequentially from network 10,000,000 ns Read 1 MB sequentially from disk 30,000,000 ns (20,000,000) Send packet CA->Netherlands->CA 150,000,000 ns 2 Wednesday, June 12, 13
  2. There are two hard problems in Computer Science: naming, caching,

    and off-by-one errors 4 Wednesday, June 12, 13
  3. WHAT’S A CACHE? get(:key) # => value It’s some form

    of: that is faster than other methods of getting that value and generally takes fewer resources 5 Wednesday, June 12, 13 fewer resources = less money, less time, less computing resources, less programming
  4. RAILS # configure Rails.application.config.cache_store = :mem_cache_store, "localhost:11211" # get/set @foo_bars

    = Rails.cache.fetch("#{user.id}:foo_bars") do user.foo_bars end # counters # in migration add_column :users, :foo_bars_count, :integer, :default => 0 # in model FooBar belongs_to :user, :counter_cache => true 6 Wednesday, June 12, 13
  5. WHERE CAN I CACHE? * in-process * redis * memcache

    * rdmbs * mongo * file system * wherever you want, really 7 Wednesday, June 12, 13
  6. IN-PROCESS # initialize the cache $cache = {} # look-up

    @foo_bars = $cache["#{user.id}:foo_bars"] ||= user.foo_bars # busting v1 user.foo_bars.create(options) $cache["#{user.id}:foo_bars"] = nil # busting v2 $bust_it_at = 30.seconds.from_now if Time.now > $bust_it_at $bust_it_at = 30.seconds.from_now $cache = nil end 8 Wednesday, June 12, 13 This is great for instances where being slightly out-of-date is okay. For example, configuration settings that don’t change often, but do change live. Time-based checks. db- backed classes. in-process is fast. really, really fast.
  7. REDIS # look-up @foo_bars = if tmp = $redis.hget("cache", "#{user.id}:foo_bars")

    tmp else tmp = user.foo_bars $redis.hset("cache", "#{user.id}:foo_bars", tmp) tmp end # busting user.foo_bars.create(options) $redis.hdel("cache", "#{user.id}:foo_bars") # Also $redis.get("cache:#{user.id}:foo_bars") 9 Wednesday, June 12, 13 This is totally fine if you don’t have memcache available and you do have redis
  8. REDIS (CONT’D) # ordered lists $redis.zadd("awesome_stuff", 1, "bacon") $redis.zadd("awesome_stuff", 2,

    "narwhals") $redis.zadd("awesome_stuff", 3, "cats") $redis.zrange(1, -1) # => ["narwhals", "cats"] 10 Wednesday, June 12, 13
  9. MEMCACHED # look-up @foo_bars = if tmp = $memcache.get("cache:#{user.id}:foo_bars") rescue

    nil tmp else tmp = user.foo_bars $memcache.set("cache:#{user.id}:foo_bars", tmp, TIMEOUT_SECONDS) tmp end # busting user.foo_bars.create(options) $memcache.delete("cache:#{user.id}:foo_bars") 11 Wednesday, June 12, 13
  10. RDBMS class KeyValue < ActiveRecord::Base def self.set(key,value) # serialize or

    whatever # crupdate(key, value) value end def self.get(key) self.find_by_key(key) end end # look-up @foo_bars = KeyValue.get("cache:#{user.id}:foo_bars") || KeyValue.set("cache:#{user.id}:foo_bars", user.foo_bars) # busting KeyValue.set("cache:#{user.id}:foo_bars", nil) # or KeyValue.find_by_key("cache:#{user.id}:foo_bars").destroy 12 Wednesday, June 12, 13
  11. CONCERNS * Is busting my cache going to make the

    load on your dbs Really High?™ * Is my cache sharded in such a way that busting it will spread out increased load instead of causing hotspots? * Does redis have enough RAM? * Is my cache in the cloud located in a place that is slower over the network than running the queries would be? * Namespace yo’ shit. 13 Wednesday, June 12, 13