Class: CachedModel

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
lib/cached_model.rb

Overview

An abstract ActiveRecord descendant that caches records in memcache and in local memory.

CachedModel can store into both a local in-memory cache and in memcached. By default memcached is enabled and the local cache is disabled.

Local cache use can be enabled or disabled with CachedModel::use_local_cache=. If you do enable the local cache be sure to add a before filter that calls CachedModel::cache_reset for every request.

memcached use can be enabled or disabled with CachedModel::use_memcache=.

You can adjust the memcached TTL with CachedModel::ttl=

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.cache_localObject (readonly)

The local process cache. You shouldn’t touch me.



33
34
35
# File 'lib/cached_model.rb', line 33

def cache_local
  @cache_local
end

.ttlObject

Memcache record time-to-live for stored records.



53
54
55
# File 'lib/cached_model.rb', line 53

def ttl
  @ttl
end

.use_local_cache=(value) ⇒ Object (writeonly)

Enables or disables use of the local cache.

NOTE if you enable this you must call #cache_reset or you will experience uncontrollable process growth!

Defaults to false.



43
44
45
# File 'lib/cached_model.rb', line 43

def use_local_cache=(value)
  @use_local_cache = value
end

.use_memcache=(value) ⇒ Object (writeonly)

Enables or disables the use of memcache.



48
49
50
# File 'lib/cached_model.rb', line 48

def use_memcache=(value)
  @use_memcache = value
end

Class Method Details

.cache_delete(klass, id) ⇒ Object

Invalidate the cache entry for a record. The update method will automatically invalidate the cache when updates are made through ActiveRecord model record. However, several methods update tables with direct sql queries for effeciency. These methods should call this method to invalidate the cache after making those changes.

NOTE - if a SQL query updates multiple rows with one query, there is currently no way to invalidate the affected entries unless the entire cache is dumped or until the TTL expires, so try not to do this.



100
101
102
103
104
# File 'lib/cached_model.rb', line 100

def self.cache_delete(klass, id)
  key = "#{klass}:#{id}"
  CachedModel.cache_local.delete key if CachedModel.use_local_cache?
  Cache.delete "active_record:#{key}" if CachedModel.use_memcache?
end

.cache_resetObject

Invalidate the local process cache. This should be called from a before filter at the beginning of each request.



110
111
112
# File 'lib/cached_model.rb', line 110

def self.cache_reset
  CachedModel.cache_local.clear if CachedModel.use_local_cache?
end

.find(*args) ⇒ Object

Override the find method to look for values in the cache before going to the database. – TODO Push a bunch of code down into find_by_sql where it really should belong.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/cached_model.rb', line 121

def self.find(*args)
  args[0] = args.first.to_i if args.first =~ /\A\d+\Z/
  # Only handle simple find requests.  If the request was more complicated,
  # let the base class handle it, but store the retrieved records in the
  # local cache in case we need them later.
  if args.length != 1 or not Fixnum === args.first then
    # Rails requires multiple levels of indirection to look up a record
    # First call super
    records = super
    # Then, if it was a :all, just return
    return records if args.first == :all
    return records if RAILS_ENV == 'test'
    case records
    when Array then
      records.each { |r| r.cache_store }
    end
    return records
  end

  return super
end

.find_by_sql(*args) ⇒ Object

Find by primary key from the cache.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/cached_model.rb', line 146

def self.find_by_sql(*args)
  return super unless args.first =~ /^SELECT \* FROM #{table_name} WHERE \(#{table_name}\.#{primary_key} = '?(\d+)'?\) +LIMIT 1/

  id = $1.to_i

  # Try to find the record in the local cache.
  cache_key_local = "#{name}:#{id}"
  if CachedModel.use_local_cache? then
    record = CachedModel.cache_local[cache_key_local]
    return [record] unless record.nil?
  end

  # Try to find the record in memcache and add it to the local cache
  if CachedModel.use_memcache? then
    record = Cache.get "active_record:#{cache_key_local}"
    unless record.nil? then
      if CachedModel.use_local_cache? then
        CachedModel.cache_local[cache_key_local] = record
      end
      return [record]
    end
  end

  # Fetch the record from the DB
  records = super
  records.first.cache_store # only one
  return records
end

.use_local_cache?Boolean

Returns true if use of the local cache is enabled.

Returns:

  • (Boolean)


58
59
60
# File 'lib/cached_model.rb', line 58

def use_local_cache?
  return @use_local_cache
end

.use_memcache?Boolean

Returns true if use of memcache is enabled.

Returns:

  • (Boolean)


65
66
67
# File 'lib/cached_model.rb', line 65

def use_memcache?
  return @use_memcache
end

Instance Method Details

#cache_deleteObject

Remove this record from the cache.



206
207
208
209
# File 'lib/cached_model.rb', line 206

def cache_delete
  cache_local.delete cache_key_local if CachedModel.use_local_cache?
  Cache.delete cache_key_memcache if CachedModel.use_memcache?
end

#cache_key_localObject

The local cache key for this record.



214
215
216
# File 'lib/cached_model.rb', line 214

def cache_key_local
  return "#{self.class}:#{id}"
end

#cache_key_memcacheObject

The memcache key for this record.



221
222
223
# File 'lib/cached_model.rb', line 221

def cache_key_memcache
  return "active_record:#{cache_key_local}"
end

#cache_localObject

The local object cache.



228
229
230
# File 'lib/cached_model.rb', line 228

def cache_local
  return CachedModel.cache_local
end

#cache_storeObject

Store this record in the cache without associations. Storing associations leads to wasted cache space and hard-to-debug problems.



236
237
238
239
240
241
242
243
244
245
# File 'lib/cached_model.rb', line 236

def cache_store
  obj = dup
  obj.send :instance_variable_set, :@attributes, attributes_before_type_cast
  if CachedModel.use_local_cache? then
    cache_local[cache_key_local] = obj
  end
  if CachedModel.use_memcache? then
    Cache.put cache_key_memcache, obj, CachedModel.ttl
  end
end

#destroyObject

Delete the entry from the cache now that it isn’t in the DB.



178
179
180
181
182
# File 'lib/cached_model.rb', line 178

def destroy
  return super
ensure
  cache_delete
end

#reloadObject

Invalidate the cache for this record before reloading from the DB.



187
188
189
190
191
192
# File 'lib/cached_model.rb', line 187

def reload
  cache_delete
  return super
ensure
  cache_store
end

#updateObject

Store a new copy of ourselves into the cache.



197
198
199
200
201
# File 'lib/cached_model.rb', line 197

def update
  return super
ensure
  cache_store
end