Module: MemcacheCluster

Extended by:
MemcacheCluster
Included in:
MemcacheCluster
Defined in:
lib/droid/heroku/memcache_cluster.rb

Overview

Manages a pool of memcache servers. This class should not be called outside of the reactor - it does not account for asynchronous access to the server list.

Constant Summary collapse

HEROKU_NAMESPACE =

heroku’s internal memcache namespace

'0Xfa15837Z'

Instance Method Summary collapse

Instance Method Details

#add(ip, port) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
# File 'lib/droid/heroku/memcache_cluster.rb', line 79

def add(ip, port)
  host = [ip, port].join(':')
  return if servers.include?(host)

  log { "#{host} added" }
  @servers.push host
  @servers.sort!
  @caches = {}
  write_to_file
  @last_read = Time.now
end

#attach(droid, file = 'memcached.yml') ⇒ Object

Create listeners for standard memcache cluster related topics.



53
54
55
56
57
58
59
# File 'lib/droid/heroku/memcache_cluster.rb', line 53

def attach(droid, file='memcached.yml')
  load_from_file(file)

  droid.listen4('memcache.up', :queue => "memcache.up.#{LocalStats.this_instance_name}.#$$") { |msg| add(msg['address'], msg['port']) }
  droid.listen4('instance.down', :queue => "instance.down.#{LocalStats.this_instance_name}.#$$") { |msg| remove(msg['local_ip']) if msg['slot'] == 'memcache' }
  EM.add_timer(1) { droid.publish('memcache.needed', {}) }
end

#cache(prefix, options = {}) ⇒ Object Also known as: []

A MemCache object configured with the given prefix.



62
63
64
65
# File 'lib/droid/heroku/memcache_cluster.rb', line 62

def cache(prefix, options={})
  caches[prefix] ||=
    MemCache.new(servers, options.merge(:namespace => prefix))
end

#cache_retry(prefix, opts = {}) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/droid/heroku/memcache_cluster.rb', line 18

def cache_retry(prefix, opts={})
  opts[:retries] ||= 5
  opts[:delay] ||= 0.5

  retried = 0
  begin
    c = cache(prefix)
    yield c if block_given?
  rescue MemCache::MemCacheError => e
    Log.error "#{e.class} -> #{e.message}", :exception => e
    raise if retried > opts[:retries]
    retried += 1
    sleep opts[:delay]
    @caches = { }
    retry
  end
end

#cachesObject



69
70
71
72
# File 'lib/droid/heroku/memcache_cluster.rb', line 69

def caches
  reload_if_stale
  @caches ||= {}
end

#get(prefix, *args) ⇒ Object



44
45
46
47
48
49
50
# File 'lib/droid/heroku/memcache_cluster.rb', line 44

def get(prefix, *args)
  res = nil
  cache_retry(prefix) do |c|
    res = c.get(*args)
  end
  res
end

#herokuObject

A MemCache object configured with heroku’s internal memcache namespace.



14
15
16
# File 'lib/droid/heroku/memcache_cluster.rb', line 14

def heroku
  cache(HEROKU_NAMESPACE)
end

#load_from_file(file) ⇒ Object



110
111
112
113
114
115
# File 'lib/droid/heroku/memcache_cluster.rb', line 110

def load_from_file(file)
  @file = file
  @last_read = Time.now
  @servers = YAML.load(File.read(file)) rescue []
  @caches = {}
end

#log(type = :debug) ⇒ Object



126
127
128
# File 'lib/droid/heroku/memcache_cluster.rb', line 126

def log(type=:debug)
  Log.send(type, "memcached: #{yield}")
end

#reload_if_staleObject



99
100
101
102
103
104
105
106
107
108
# File 'lib/droid/heroku/memcache_cluster.rb', line 99

def reload_if_stale
  if @last_read &&
    (Time.now - @last_read) > 5 &&
    File.mtime(@file) > @last_read
    log { "server list modified. reloading." }
    load_from_file(@file)
  end
rescue => boom
  # ignore errors accessing/reading file.
end

#remove(host) ⇒ Object



91
92
93
94
95
96
97
# File 'lib/droid/heroku/memcache_cluster.rb', line 91

def remove(host)
  if servers.reject!{ |s| s =~ /^#{host}/ }
    log { "#{host} removed" }
    caches.clear
    write_to_file
  end
end

#serversObject



74
75
76
77
# File 'lib/droid/heroku/memcache_cluster.rb', line 74

def servers
  reload_if_stale
  @servers ||= []
end

#set(prefix, *args) ⇒ Object



36
37
38
39
40
41
42
# File 'lib/droid/heroku/memcache_cluster.rb', line 36

def set(prefix, *args)
  res = nil
  cache_retry(prefix) do |c|
    res = c.set(*args)
  end
  res
end

#write_to_fileObject



117
118
119
120
121
122
123
124
# File 'lib/droid/heroku/memcache_cluster.rb', line 117

def write_to_file
  log { "writing server list: #{@file}" }
  File.open(@file, 'w') do |f|
    f.flock(File::LOCK_EX)
    f.write YAML.dump(@servers)
    f.flock(File::LOCK_UN)
  end
end