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_delay_commitObject

The transaction commit buffer. You shouldn’t touch me.



37
38
39
# File 'lib/cached_model.rb', line 37

def cache_delay_commit
  @cache_delay_commit
end

.cache_localObject (readonly)

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



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

def cache_local
  @cache_local
end

.cache_transaction_levelObject

The transaction nesting level. You shouldn’t touch me.



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

def cache_transaction_level
  @cache_transaction_level
end

.ttlObject

Memcache record time-to-live for stored records.



69
70
71
# File 'lib/cached_model.rb', line 69

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.



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

def use_local_cache=(value)
  @use_local_cache = value
end

.use_memcache=(value) ⇒ Object (writeonly)

Enables or disables the use of memcache.



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

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.



102
103
104
105
106
# File 'lib/cached_model.rb', line 102

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.



112
113
114
# File 'lib/cached_model.rb', line 112

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.



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

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.



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
174
175
# File 'lib/cached_model.rb', line 148

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 unless records.empty? # only one
  return records
end

.transaction(*args) ⇒ Object

Delay updating the cache while in a transaction.



180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/cached_model.rb', line 180

def self.transaction(*args)
  level = CachedModel.cache_transaction_level += 1
  CachedModel.cache_delay_commit[level] = []

  value = super

  waiting = CachedModel.cache_delay_commit.delete level
  waiting.each do |obj| obj.cache_store end
  
  return value
ensure
  CachedModel.cache_transaction_level -= 1
end

.use_local_cache?Boolean

Returns true if use of the local cache is enabled.

Returns:

  • (Boolean)


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

def self.use_local_cache?
  return @use_local_cache
end

.use_memcache?Boolean

Returns true if use of memcache is enabled.

Returns:

  • (Boolean)


204
205
206
# File 'lib/cached_model.rb', line 204

def self.use_memcache?
  return @use_memcache
end

Instance Method Details

#cache_deleteObject

Remove this record from the cache.



239
240
241
242
# File 'lib/cached_model.rb', line 239

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.



247
248
249
# File 'lib/cached_model.rb', line 247

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

#cache_key_memcacheObject

The memcache key for this record.



254
255
256
# File 'lib/cached_model.rb', line 254

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

#cache_localObject

The local object cache.



261
262
263
# File 'lib/cached_model.rb', line 261

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.



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/cached_model.rb', line 269

def cache_store
  obj = dup
  obj.send :instance_variable_set, :@attributes, attributes_before_type_cast
  if CachedModel.cache_delay_commit[CachedModel.cache_transaction_level].nil? then
    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
  else
    CachedModel.cache_delay_commit[CachedModel.cache_transaction_level] << obj
  end
  nil
end

#destroyObject

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



211
212
213
214
215
# File 'lib/cached_model.rb', line 211

def destroy
  return super
ensure
  cache_delete
end

#reloadObject

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



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

def reload
  cache_delete
  return super
ensure
  cache_store
end

#updateObject

Store a new copy of ourselves into the cache.



230
231
232
233
234
# File 'lib/cached_model.rb', line 230

def update
  return super
ensure
  cache_store
end