ngmoco-cache-money 0.2.23 → 0.2.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +30 -34
- data/README.markdown +30 -34
- data/config/environment.rb +0 -8
- data/config/memcached.yml +0 -2
- data/init.rb +1 -1
- data/lib/cache_money.rb +55 -36
- data/lib/cash/adapter/memcache_client.rb +36 -0
- data/lib/cash/adapter/memcached.rb +131 -0
- data/lib/cash/adapter/redis.rb +152 -0
- data/lib/cash/config.rb +7 -4
- data/lib/cash/finders.rb +15 -3
- data/lib/cash/index.rb +3 -6
- data/lib/cash/local.rb +1 -1
- data/lib/cash/lock.rb +2 -2
- data/lib/cash/mock.rb +16 -12
- data/lib/cash/query/abstract.rb +29 -3
- data/lib/cash/version.rb +3 -0
- data/lib/cash/write_through.rb +9 -5
- data/lib/mem_cached_support_store.rb +1 -1
- data/rails/init.rb +1 -40
- data/spec/cash/calculations_spec.rb +11 -0
- data/spec/cash/finders_spec.rb +4 -4
- data/spec/cash/lock_spec.rb +30 -24
- data/spec/cash/marshal_spec.rb +1 -1
- data/spec/cash/transactional_spec.rb +19 -17
- data/spec/cash/without_caching_spec.rb +32 -0
- data/spec/cash/write_through_spec.rb +7 -0
- data/spec/spec_helper.rb +32 -6
- metadata +136 -45
- data/lib/memcached_wrapper.rb +0 -263
- data/spec/memcached_wrapper_test.rb +0 -209
@@ -0,0 +1,152 @@
|
|
1
|
+
# Maps Redis methods and semantics to those of memcache-client
|
2
|
+
module Cash
|
3
|
+
module Adapter
|
4
|
+
class Redis
|
5
|
+
def initialize(repository, options = {})
|
6
|
+
@repository = repository
|
7
|
+
@logger = options[:logger]
|
8
|
+
@default_ttl = options[:default_ttl] || raise(":default_ttl is a required option")
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(key, value, ttl=nil, raw = false)
|
12
|
+
wrap(key, not_stored) do
|
13
|
+
logger.debug("Redis add: #{key.inspect}") if debug_logger?
|
14
|
+
value = dump(value) unless raw
|
15
|
+
# TODO: make transactional
|
16
|
+
result = @repository.setnx(key, value)
|
17
|
+
@repository.expires(key, ttl || @default_ttl) if 1 == result
|
18
|
+
logger.debug("Redis hit: #{key.inspect}") if debug_logger?
|
19
|
+
1 == result ? stored : not_stored
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(key, raw = false)
|
24
|
+
wrap(key) do
|
25
|
+
logger.debug("Redis get: #{key.inspect}") if debug_logger?
|
26
|
+
value = wrap(key) { @repository.get(key) }
|
27
|
+
if value
|
28
|
+
logger.debug("Redis hit: #{key.inspect}") if debug_logger?
|
29
|
+
value = load(value) unless raw
|
30
|
+
else
|
31
|
+
logger.debug("Redis miss: #{key.inspect}") if debug_logger?
|
32
|
+
end
|
33
|
+
value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_multi(*keys)
|
38
|
+
wrap(keys, {}) do
|
39
|
+
keys.flatten!
|
40
|
+
logger.debug("Redis get_multi: #{keys.inspect}") if debug_logger?
|
41
|
+
|
42
|
+
# Values are returned as an array. Convert them to a hash of matches, dropping anything
|
43
|
+
# that doesn't have a match.
|
44
|
+
values = @repository.mget(*keys)
|
45
|
+
result = {}
|
46
|
+
keys.each_with_index{ |key, i| result[key] = load(values[i]) if values[i] }
|
47
|
+
|
48
|
+
if result.any?
|
49
|
+
logger.debug("Redis hit: #{keys.inspect}") if debug_logger?
|
50
|
+
else
|
51
|
+
logger.debug("Redis miss: #{keys.inspect}") if debug_logger?
|
52
|
+
end
|
53
|
+
result
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def set(key, value, ttl=nil, raw = false)
|
58
|
+
wrap(key, not_stored) do
|
59
|
+
logger.debug("Redis set: #{key.inspect}") if debug_logger?
|
60
|
+
value = dump(value) unless raw
|
61
|
+
@repository.setex(key, ttl || @default_ttl, value)
|
62
|
+
logger.debug("Redis hit: #{key.inspect}") if debug_logger?
|
63
|
+
stored
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def delete(key)
|
68
|
+
wrap(key, not_found) do
|
69
|
+
logger.debug("Redis delete: #{key.inspect}") if debug_logger?
|
70
|
+
@repository.del(key)
|
71
|
+
logger.debug("Redis hit: #{key.inspect}") if debug_logger?
|
72
|
+
deleted
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_server_for_key(key)
|
77
|
+
wrap(key) do
|
78
|
+
# Redis::Distributed has a node_for method.
|
79
|
+
client = @repository.respond_to?(:node_for) ? @repository.node_for(key) : @repository.client
|
80
|
+
client.id
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def incr(key, value = 1)
|
85
|
+
# Redis always answeres positively to incr/decr but memcache does not and waits for the key
|
86
|
+
# to be added in a separate operation.
|
87
|
+
if wrap(nil) { @repository.exists(key) }
|
88
|
+
wrap(key) { @repository.incrby(key, value).to_i }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def decr(key, value = 1)
|
93
|
+
if wrap(nil) { @repository.exists(key) }
|
94
|
+
wrap(key) { @repository.decrby(key, value).to_i }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def flush_all
|
99
|
+
@repository.flushall
|
100
|
+
end
|
101
|
+
|
102
|
+
def exception_classes
|
103
|
+
[Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL]
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def logger
|
109
|
+
@logger
|
110
|
+
end
|
111
|
+
|
112
|
+
def debug_logger?
|
113
|
+
logger && logger.respond_to?(:debug?) && logger.debug?
|
114
|
+
end
|
115
|
+
|
116
|
+
def wrap(key, error_value = nil)
|
117
|
+
yield
|
118
|
+
rescue *exception_classes
|
119
|
+
log_error($!) if logger
|
120
|
+
error_value
|
121
|
+
end
|
122
|
+
|
123
|
+
def dump(value)
|
124
|
+
Marshal.dump(value)
|
125
|
+
end
|
126
|
+
|
127
|
+
def load(value)
|
128
|
+
Marshal.load(value)
|
129
|
+
end
|
130
|
+
|
131
|
+
def stored
|
132
|
+
"STORED\r\n"
|
133
|
+
end
|
134
|
+
|
135
|
+
def deleted
|
136
|
+
"DELETED\r\n"
|
137
|
+
end
|
138
|
+
|
139
|
+
def not_stored
|
140
|
+
"NOT_STORED\r\n"
|
141
|
+
end
|
142
|
+
|
143
|
+
def not_found
|
144
|
+
"NOT_FOUND\r\n"
|
145
|
+
end
|
146
|
+
|
147
|
+
def log_error(err)
|
148
|
+
logger.error("Redis ERROR, #{err.class}: #{err}") if logger
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/lib/cash/config.rb
CHANGED
@@ -16,7 +16,7 @@ module Cash
|
|
16
16
|
def self.extended(a_class)
|
17
17
|
class << a_class
|
18
18
|
def cache_config
|
19
|
-
@cache_config
|
19
|
+
@cache_config
|
20
20
|
end
|
21
21
|
|
22
22
|
delegate :repository, :indices, :to => :cache_config
|
@@ -26,7 +26,7 @@ module Cash
|
|
26
26
|
|
27
27
|
def inherited_with_cache_config(subclass)
|
28
28
|
inherited_without_cache_config(subclass)
|
29
|
-
@cache_config.inherit(subclass)
|
29
|
+
@cache_config.inherit(subclass) if @cache_config
|
30
30
|
end
|
31
31
|
|
32
32
|
def index(attributes, options = {})
|
@@ -41,6 +41,10 @@ module Cash
|
|
41
41
|
def cache_config=(config)
|
42
42
|
@cache_config = config
|
43
43
|
end
|
44
|
+
|
45
|
+
def cacheable?(*args)
|
46
|
+
Cash.enabled && cache_config
|
47
|
+
end
|
44
48
|
end
|
45
49
|
|
46
50
|
class Config
|
@@ -55,8 +59,7 @@ module Cash
|
|
55
59
|
end
|
56
60
|
|
57
61
|
def ttl
|
58
|
-
|
59
|
-
@ttl ||= @options[:ttl] || repository_ttl || 1.day
|
62
|
+
@ttl ||= (repository.respond_to?(:default_ttl) && repository.default_ttl) || @options[:ttl]
|
60
63
|
end
|
61
64
|
|
62
65
|
def version
|
data/lib/cash/finders.rb
CHANGED
@@ -21,17 +21,29 @@ module Cash
|
|
21
21
|
|
22
22
|
# User.find(:first, ...), User.find_by_foo(...), User.find(:all, ...), User.find_all_by_foo(...)
|
23
23
|
def find_every_with_cache(options)
|
24
|
-
|
24
|
+
if cacheable?
|
25
|
+
Query::Select.perform(self, options, scope(:find))
|
26
|
+
else
|
27
|
+
find_every_without_cache(options)
|
28
|
+
end
|
25
29
|
end
|
26
30
|
|
27
31
|
# User.find(1), User.find(1, 2, 3), User.find([1, 2, 3]), User.find([])
|
28
32
|
def find_from_ids_with_cache(ids, options)
|
29
|
-
|
33
|
+
if cacheable?
|
34
|
+
Query::PrimaryKey.perform(self, ids, options, scope(:find))
|
35
|
+
else
|
36
|
+
find_from_ids_without_cache(ids, options)
|
37
|
+
end
|
30
38
|
end
|
31
39
|
|
32
40
|
# User.count(:all), User.count, User.sum(...)
|
33
41
|
def calculate_with_cache(operation, column_name, options = {})
|
34
|
-
|
42
|
+
if cacheable?
|
43
|
+
Query::Calculation.perform(self, operation, column_name, options, scope(:find))
|
44
|
+
else
|
45
|
+
calculate_without_cache(operation, column_name, options)
|
46
|
+
end
|
35
47
|
end
|
36
48
|
end
|
37
49
|
end
|
data/lib/cash/index.rb
CHANGED
@@ -4,11 +4,8 @@ module Cash
|
|
4
4
|
delegate :each, :hash, :to => :@attributes
|
5
5
|
delegate :get, :set, :expire, :find_every_without_cache, :calculate_without_cache, :calculate_with_cache, :incr, :decr, :primary_key, :logger, :to => :@active_record
|
6
6
|
|
7
|
-
DEFAULT_OPTIONS = { :ttl => 1.day }
|
8
|
-
|
9
7
|
def initialize(config, active_record, attributes, options = {})
|
10
|
-
|
11
|
-
@config, @active_record, @attributes, @options = config, active_record, Array(attributes).collect(&:to_s).sort, DEFAULT_OPTIONS.merge(options)
|
8
|
+
@config, @active_record, @attributes, @options = config, active_record, Array(attributes).collect(&:to_s).sort, options
|
12
9
|
end
|
13
10
|
|
14
11
|
def ==(other)
|
@@ -47,7 +44,7 @@ module Cash
|
|
47
44
|
|
48
45
|
module Attributes
|
49
46
|
def ttl
|
50
|
-
@ttl ||= options[:ttl] || config.ttl
|
47
|
+
@ttl ||= options[:ttl] || @config.ttl
|
51
48
|
end
|
52
49
|
|
53
50
|
def order
|
@@ -129,7 +126,7 @@ module Cash
|
|
129
126
|
(a <=> b) * (order == :asc ? 1 : -1)
|
130
127
|
end.uniq
|
131
128
|
objects = truncate_if_necessary(objects)
|
132
|
-
set(key, objects, :ttl => ttl)
|
129
|
+
set(key, objects, :ttl => ttl)
|
133
130
|
incr("#{key}/count") { calculate_at_index(:count, attribute_value_pairs) }
|
134
131
|
end
|
135
132
|
end
|
data/lib/cash/local.rb
CHANGED
@@ -15,7 +15,7 @@ module Cash
|
|
15
15
|
|
16
16
|
def autoload_missing_constants
|
17
17
|
yield if block_given?
|
18
|
-
rescue ArgumentError,
|
18
|
+
rescue ArgumentError, *@remote_cache.exception_classes => error
|
19
19
|
lazy_load ||= Hash.new { |hash, hash_key| hash[hash_key] = true; false }
|
20
20
|
if error.to_s[/undefined class|referred/] && !lazy_load[error.to_s.split.last.constantize]
|
21
21
|
retry
|
data/lib/cash/lock.rb
CHANGED
@@ -33,7 +33,7 @@ module Cash
|
|
33
33
|
exponential_sleep(count, initial_wait) unless count == retries - 1
|
34
34
|
end
|
35
35
|
debug_lock(key)
|
36
|
-
raise Error, "Couldn't acquire memcache lock on
|
36
|
+
raise Error, "Couldn't acquire memcache lock on 'lock/#{key}'"
|
37
37
|
end
|
38
38
|
|
39
39
|
def release_lock(key)
|
@@ -41,7 +41,7 @@ module Cash
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def exponential_sleep(count, initial_wait)
|
44
|
-
sleep((2**count)
|
44
|
+
sleep((2**count) * initial_wait)
|
45
45
|
end
|
46
46
|
|
47
47
|
private
|
data/lib/cash/mock.rb
CHANGED
@@ -20,7 +20,7 @@ module Cash
|
|
20
20
|
@value = Marshal.dump(value)
|
21
21
|
end
|
22
22
|
|
23
|
-
if ttl.zero?
|
23
|
+
if ttl.nil? || ttl.zero?
|
24
24
|
@ttl = self.class.default_ttl
|
25
25
|
else
|
26
26
|
@ttl = ttl
|
@@ -68,18 +68,22 @@ module Cash
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def get(key, raw = false)
|
71
|
-
|
72
|
-
|
73
|
-
log('> END')
|
74
|
-
return nil
|
75
|
-
end
|
76
|
-
|
77
|
-
log("> sending key #{key}")
|
78
|
-
log('> END')
|
79
|
-
if raw
|
80
|
-
self[key].value
|
71
|
+
if key.is_a?(Array)
|
72
|
+
get_multi(*key)
|
81
73
|
else
|
82
|
-
|
74
|
+
log "< get #{key}"
|
75
|
+
unless self.has_unexpired_key?(key)
|
76
|
+
log('> END')
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
|
80
|
+
log("> sending key #{key}")
|
81
|
+
log('> END')
|
82
|
+
if raw
|
83
|
+
self[key].value
|
84
|
+
else
|
85
|
+
self[key].unmarshal
|
86
|
+
end
|
83
87
|
end
|
84
88
|
end
|
85
89
|
|
data/lib/cash/query/abstract.rb
CHANGED
@@ -61,8 +61,22 @@ module Cash
|
|
61
61
|
|
62
62
|
private
|
63
63
|
def cacheable?(*optionss)
|
64
|
-
|
65
|
-
|
64
|
+
if @active_record.respond_to?(:cacheable?) && ! @active_record.cacheable?(*optionss)
|
65
|
+
if logger
|
66
|
+
if @active_record.respond_to?(:cacheable?)
|
67
|
+
logger.debug(" \e[1;4;31mUNCACHEABLE CLASS\e[0m #{table_name}")
|
68
|
+
else
|
69
|
+
logger.debug(" \e[1;4;31mUNCACHEABLE INSTANCE\e[0m #{table_name} - #{optionss.inspect}")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
return false
|
73
|
+
end
|
74
|
+
optionss.each do |options|
|
75
|
+
unless safe_options_for_cache?(options)
|
76
|
+
logger.debug(" \e[1;4;31mUNCACHEABLE UNSAFE\e[0m #{table_name} - #{options.inspect}") if logger
|
77
|
+
return false
|
78
|
+
end
|
79
|
+
end
|
66
80
|
partial_indices = optionss.collect { |options| attribute_value_pairs_for_conditions(options[:conditions]) }
|
67
81
|
return if partial_indices.include?(nil)
|
68
82
|
attribute_value_pairs = partial_indices.sum.sort { |x, y| x[0] <=> y[0] }
|
@@ -74,7 +88,13 @@ module Cash
|
|
74
88
|
if index = indexed_on?(attribute_value_pairs.collect { |pair| pair[0] })
|
75
89
|
if index.matches?(self)
|
76
90
|
[attribute_value_pairs, index]
|
91
|
+
else
|
92
|
+
logger.debug(" \e[1;4;31mUNCACHEABLE NO MATCHING INDEX\e[0m #{table_name} - #{index.order_column.inspect} #{index.order.inspect} #{index.limit.inspect}") if logger
|
93
|
+
false
|
77
94
|
end
|
95
|
+
else
|
96
|
+
logger.debug(" \e[1;4;31mUNCACHEABLE NOT INDEXED\e[0m #{table_name} - #{attribute_value_pairs.collect { |pair| pair[0] }.inspect}") if logger
|
97
|
+
false
|
78
98
|
end
|
79
99
|
end
|
80
100
|
|
@@ -191,7 +211,13 @@ module Cash
|
|
191
211
|
order_sql = @options1[:order] || @options2[:order]
|
192
212
|
options[:order] = order_sql if order_sql
|
193
213
|
results = find_from_ids_without_cache(missing_ids, options)
|
194
|
-
|
214
|
+
if results
|
215
|
+
if results.is_a?(Array)
|
216
|
+
results.each {|o| @active_record.add_to_caches(o) }
|
217
|
+
else
|
218
|
+
@active_record.add_to_caches(results)
|
219
|
+
end
|
220
|
+
end
|
195
221
|
results
|
196
222
|
end
|
197
223
|
end
|
data/lib/cash/version.rb
ADDED
data/lib/cash/write_through.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Cash
|
2
2
|
module WriteThrough
|
3
|
-
DEFAULT_TTL = 12.hours
|
3
|
+
DEFAULT_TTL = 12.hours.to_i
|
4
4
|
|
5
5
|
def self.included(active_record_class)
|
6
6
|
active_record_class.class_eval do
|
@@ -50,19 +50,23 @@ module Cash
|
|
50
50
|
|
51
51
|
module ClassMethods
|
52
52
|
def add_to_caches(object)
|
53
|
-
indices.each { |index| index.add(object) } if
|
53
|
+
indices.each { |index| index.add(object) } if cacheable?
|
54
|
+
true
|
54
55
|
end
|
55
56
|
|
56
57
|
def update_caches(object)
|
57
|
-
indices.each { |index| index.update(object) } if
|
58
|
+
indices.each { |index| index.update(object) } if cacheable?
|
59
|
+
true
|
58
60
|
end
|
59
61
|
|
60
62
|
def remove_from_caches(object)
|
61
|
-
indices.each { |index| index.remove(object) } if
|
63
|
+
indices.each { |index| index.remove(object) } if cacheable?
|
64
|
+
true
|
62
65
|
end
|
63
66
|
|
64
67
|
def expire_caches(object)
|
65
|
-
indices.each { |index| index.delete(object) } if
|
68
|
+
indices.each { |index| index.delete(object) } if cacheable?
|
69
|
+
true
|
66
70
|
end
|
67
71
|
end
|
68
72
|
end
|
data/rails/init.rb
CHANGED
@@ -1,40 +1 @@
|
|
1
|
-
|
2
|
-
memcache_config = yml[RAILS_ENV]
|
3
|
-
memcache_config.symbolize_keys! if memcache_config.respond_to?(:symbolize_keys!)
|
4
|
-
|
5
|
-
if defined?(DISABLE_CACHE_MONEY) || ENV['DISABLE_CACHE_MONEY'] == 'true' || memcache_config.nil? || memcache_config[:cache_money] != true
|
6
|
-
Rails.logger.info 'cache-money disabled'
|
7
|
-
class ActiveRecord::Base
|
8
|
-
def self.index(*args)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
else
|
12
|
-
Rails.logger.info 'cache-money enabled'
|
13
|
-
require 'cache_money'
|
14
|
-
|
15
|
-
memcache_config[:logger] = Rails.logger
|
16
|
-
memcache_servers =
|
17
|
-
case memcache_config[:servers].class.to_s
|
18
|
-
when "String"; memcache_config[:servers].gsub(' ', '').split(',')
|
19
|
-
when "Array"; memcache_config[:servers]
|
20
|
-
end
|
21
|
-
$memcache = MemcachedWrapper.new(memcache_servers, memcache_config)
|
22
|
-
|
23
|
-
#ActionController::Base.cache_store = :cache_money_mem_cache_store
|
24
|
-
ActionController::Base.session_options[:cache] = $memcache if memcache_config[:sessions]
|
25
|
-
#silence_warnings {
|
26
|
-
# Object.const_set "RAILS_CACHE", ActiveSupport::Cache.lookup_store(:cache_money_mem_cache_store)
|
27
|
-
#}
|
28
|
-
|
29
|
-
$local = Cash::Local.new($memcache)
|
30
|
-
$lock = Cash::Lock.new($memcache)
|
31
|
-
$cache = Cash::Transactional.new($local, $lock)
|
32
|
-
|
33
|
-
# allow setting up caching on a per-model basis
|
34
|
-
unless memcache_config[:automatic_caching].to_s == 'false'
|
35
|
-
Rails.logger.info "cache-money: global model caching enabled"
|
36
|
-
class ActiveRecord::Base
|
37
|
-
is_cached(:repository => $cache)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
1
|
+
require 'cache_money'
|