rails3_libmemcached_store 0.4.0 → 0.5.0
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/BENCHMARKS +37 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +5 -1
- data/README.md +17 -5
- data/lib/action_dispatch/session/libmemcached_store.rb +7 -8
- data/lib/active_support/cache/libmemcached_store.rb +197 -49
- data/lib/libmemcached_store.rb +0 -1
- data/lib/memcached/get_with_flags.rb +42 -0
- data/lib/version.rb +1 -1
- data/test/active_support/libmemcached_store_test.rb +163 -185
- data/test/profile/benchmark.rb +94 -0
- metadata +8 -4
- data/lib/active_support/cache/compressed_libmemcached_store.rb +0 -15
data/BENCHMARKS
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
Testing with
|
2
|
+
ruby 1.9.3p125 (2012-02-16 revision 34643) [x86_64-darwin10.8.0]
|
3
|
+
Dalli 2.1.0
|
4
|
+
Libmemcached_store 0.5.0
|
5
|
+
user system total real
|
6
|
+
write:short:dalli 0.260000 0.060000 0.320000 ( 0.341703)
|
7
|
+
write:short:libm 0.040000 0.030000 0.070000 ( 0.176990)
|
8
|
+
write:long:dalli 0.240000 0.060000 0.300000 ( 0.327882)
|
9
|
+
write:long:libm 0.040000 0.030000 0.070000 ( 0.213028)
|
10
|
+
write:raw:dalli 0.210000 0.060000 0.270000 ( 0.271676)
|
11
|
+
write:raw:libm 0.040000 0.030000 0.070000 ( 0.149668)
|
12
|
+
|
13
|
+
read:miss:dalli 0.310000 0.080000 0.390000 ( 0.395997)
|
14
|
+
read:miss:libm 0.060000 0.040000 0.100000 ( 0.199834)
|
15
|
+
read:miss2:dalli 0.210000 0.060000 0.270000 ( 0.274769)
|
16
|
+
read:miss2:libm 0.060000 0.040000 0.100000 ( 0.219939)
|
17
|
+
read:exist:dalli 0.210000 0.060000 0.270000 ( 0.330582)
|
18
|
+
read:exist:libm 0.050000 0.050000 0.100000 ( 0.186952)
|
19
|
+
read:expired:dalli 0.240000 0.060000 0.300000 ( 0.306864)
|
20
|
+
read:expired:libm 0.060000 0.040000 0.100000 ( 0.212305)
|
21
|
+
read:raw:dalli 0.320000 0.070000 0.390000 ( 0.391410)
|
22
|
+
read:raw:libm 0.040000 0.050000 0.090000 ( 0.175050)
|
23
|
+
|
24
|
+
exist:miss:dalli 0.200000 0.060000 0.260000 ( 0.271294)
|
25
|
+
exist:miss:libm 0.060000 0.040000 0.100000 ( 0.211563)
|
26
|
+
exist:hit:dalli 0.200000 0.060000 0.260000 ( 0.324383)
|
27
|
+
exist:hit:libm 0.050000 0.050000 0.100000 ( 0.173287)
|
28
|
+
|
29
|
+
delete:miss:dalli 0.230000 0.050000 0.280000 ( 0.281045)
|
30
|
+
delete:miss:libm 0.050000 0.030000 0.080000 ( 0.213801)
|
31
|
+
delete:hit:dalli 0.290000 0.060000 0.350000 ( 0.354938)
|
32
|
+
delete:hit:libm 0.060000 0.030000 0.090000 ( 0.212315)
|
33
|
+
|
34
|
+
increment:dalli 0.210000 0.050000 0.260000 ( 0.271489)
|
35
|
+
increment:libm 0.040000 0.040000 0.080000 ( 0.153906)
|
36
|
+
decrement:dalli 0.220000 0.050000 0.270000 ( 0.292662)
|
37
|
+
decrement:libm 0.040000 0.040000 0.080000 ( 0.147424)
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## 0.5.0
|
4
|
+
* Use Memcached#exist if available (performance improvement ~25%)
|
5
|
+
* Correctly escape bad characters and too long keys
|
6
|
+
* Add benchmarks
|
7
|
+
* Remove the use of ActiveSupport::Entry which was a performance bottleneck #3
|
8
|
+
|
9
|
+
## 0.4.0
|
10
|
+
* Optimize read_multi to only make one call to memecached server
|
11
|
+
* Update test suite to reflect Rails' one
|
12
|
+
* Add session store tests
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -8,11 +8,11 @@ This cache is designed for Rails 3+ applications.
|
|
8
8
|
|
9
9
|
You'll need the memcached gem installed:
|
10
10
|
|
11
|
-
```ruby
|
11
|
+
```ruby
|
12
12
|
gem install memcached
|
13
13
|
```
|
14
14
|
|
15
|
-
or in your Gemfile
|
15
|
+
or in your Gemfile
|
16
16
|
|
17
17
|
```ruby
|
18
18
|
gem 'memcached'
|
@@ -48,18 +48,30 @@ designation. If no port is given, 11211 is assumed:
|
|
48
48
|
config.cache_store = :libmemcached_store, %w(cache-01 cache-02 127.0.0.1:11212)
|
49
49
|
```
|
50
50
|
|
51
|
-
|
52
|
-
|
51
|
+
Standard Rails cache store options can be used
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
config.cache_store = :libmemcached_store, '127.0.0.1:11211', :compress => true, :expires_in => 3600
|
55
|
+
```
|
56
|
+
|
57
|
+
More advanced options can be passed directly to the client
|
58
|
+
|
53
59
|
```ruby
|
54
|
-
config.cache_store = :libmemcached_store, '127.0.0.1:11211', :
|
60
|
+
config.cache_store = :libmemcached_store, '127.0.0.1:11211', :client => { :binary_protocol => true, :no_block => true }
|
55
61
|
```
|
56
62
|
|
57
63
|
You can also use `:libmemcached_store` to store your application sessions
|
58
64
|
|
59
65
|
```ruby
|
66
|
+
require 'action_dispatch/session/libmemcached_store'
|
60
67
|
config.session_store = :libmemcached_store, :namespace => '_session', :expire_after => 1800
|
61
68
|
```
|
62
69
|
|
70
|
+
## Performance
|
71
|
+
|
72
|
+
Used with Rails, __libmemcached_store__ is at least 1.5x faster than __dalli__. See [BENCHMARKS](https://github.com/ccocchi/libmemcached_store/blob/master/BENCHMARKS)
|
73
|
+
for details
|
74
|
+
|
63
75
|
## Props
|
64
76
|
|
65
77
|
Thanks to Brian Aker ([http://tangent.org](http://tangent.org)) for creating libmemcached, and Evan
|
@@ -1,17 +1,17 @@
|
|
1
1
|
require 'memcached'
|
2
|
-
require '
|
2
|
+
require 'action_dispatch/middleware/session/abstract_store'
|
3
3
|
|
4
4
|
module ActionDispatch
|
5
5
|
module Session
|
6
6
|
class LibmemcachedStore < AbstractStore
|
7
7
|
|
8
|
-
DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS.merge(:prefix_key => 'rack:session', :memcache_server => 'localhost:11211')
|
9
|
-
|
10
8
|
def initialize(app, options = {})
|
11
9
|
options[:expire_after] ||= options[:expires]
|
12
10
|
super
|
11
|
+
client_options = { default_ttl: options.fetch(:expire_after, 0) }
|
12
|
+
client_options[:namespace] = options[:namespace] || 'rack:session'
|
13
13
|
@mutex = Mutex.new
|
14
|
-
@pool = options[:cache] || Memcached.new(@default_options[:memcache_server],
|
14
|
+
@pool = options[:cache] || Memcached.new(@default_options[:memcache_server], client_options)
|
15
15
|
end
|
16
16
|
|
17
17
|
private
|
@@ -20,7 +20,7 @@ module ActionDispatch
|
|
20
20
|
loop do
|
21
21
|
sid = super
|
22
22
|
begin
|
23
|
-
@pool.
|
23
|
+
@pool.exist(sid)
|
24
24
|
rescue Memcached::NotFound
|
25
25
|
break sid
|
26
26
|
end
|
@@ -40,8 +40,7 @@ module ActionDispatch
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def set_session(env, session_id, new_session, options = {})
|
43
|
-
expiry
|
44
|
-
expiry = expiry.nil? ? 0 : expiry + 1
|
43
|
+
expiry = options[:expire_after].to_i
|
45
44
|
|
46
45
|
with_lock(env, false) do
|
47
46
|
@pool.set(session_id, new_session, expiry)
|
@@ -64,7 +63,7 @@ module ActionDispatch
|
|
64
63
|
with_lock(env, false) do
|
65
64
|
@pool.delete(sid)
|
66
65
|
end
|
67
|
-
end
|
66
|
+
end
|
68
67
|
end
|
69
68
|
|
70
69
|
def with_lock(env, default)
|
@@ -1,67 +1,169 @@
|
|
1
1
|
require 'memcached'
|
2
|
+
require 'memcached/get_with_flags'
|
2
3
|
|
3
|
-
|
4
|
-
# In 3.0 all values returned from Rails.cache.read are frozen.
|
5
|
-
# This makes sense for an in-memory store storing object references,
|
6
|
-
# but for a marshalled store we should be able to modify things.
|
7
|
-
# Starting with 3.2, values are not frozen anymore.
|
8
|
-
def value_with_dup
|
9
|
-
result = value_without_dup
|
10
|
-
result.frozen? && result.duplicable? ? result.dup : result
|
11
|
-
end
|
12
|
-
alias_method_chain :value, :dup
|
13
|
-
end
|
4
|
+
require 'digest/md5'
|
14
5
|
|
15
6
|
module ActiveSupport
|
16
7
|
module Cache
|
17
|
-
|
8
|
+
|
9
|
+
#
|
10
|
+
# Store using memcached gem as client
|
11
|
+
#
|
12
|
+
# Global options can be passed to be applied to each method by default.
|
13
|
+
# Supported options are
|
14
|
+
# * <tt>:compress</tt> : if set to true, data will be compress before stored
|
15
|
+
# * <tt>:compress_threshold</tt> : specify the threshold at which to compress
|
16
|
+
# value, default is 4K
|
17
|
+
# * <tt>:namespace</tt> : prepend each key with this value for simple namespacing
|
18
|
+
# * <tt>:expires_in</tt> : default TTL in seconds for each. Default value is 0, i.e. forever
|
19
|
+
# Specific value can be passed per key with write and fetch command.
|
20
|
+
#
|
21
|
+
# Options can also be passed direclty to the memcache client, via the <tt>:client</tt>
|
22
|
+
# option. For example, if you want to use pipelining, you can use
|
23
|
+
# :client => { :no_block => true }
|
24
|
+
#
|
25
|
+
class LibmemcachedStore
|
18
26
|
attr_reader :addresses
|
19
27
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
28
|
+
DEFAULT_CLIENT_OPTIONS = { distribution: :consistent_ketama, binary_protocol: true, default_ttl: 0 }
|
29
|
+
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
|
30
|
+
DEFAULT_COMPRESS_THRESHOLD = 4096
|
31
|
+
FLAG_COMPRESSED = 0x2
|
32
|
+
|
33
|
+
attr_reader :silence, :options
|
34
|
+
alias_method :silence?, :silence
|
35
|
+
|
36
|
+
# Silence the logger.
|
37
|
+
def silence!
|
38
|
+
@silence = true
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
# Silence the logger within a block.
|
43
|
+
def mute
|
44
|
+
previous_silence, @silence = defined?(@silence) && @silence, true
|
45
|
+
yield
|
46
|
+
ensure
|
47
|
+
@silence = previous_silence
|
48
|
+
end
|
24
49
|
|
25
50
|
def initialize(*addresses)
|
26
51
|
addresses.flatten!
|
27
|
-
|
52
|
+
options = addresses.extract_options!
|
53
|
+
client_options = options.delete(:client) || {}
|
54
|
+
if options[:namespace]
|
55
|
+
client_options[:prefix_key] = options.delete(:namespace)
|
56
|
+
client_options[:prefix_delimiter] = ':'
|
57
|
+
@namespace_length = client_options[:prefix_key].length + 1
|
58
|
+
else
|
59
|
+
@namespace_length = 0
|
60
|
+
end
|
61
|
+
client_options[:default_ttl] = options.delete(:expires_in).to_i if options[:expires_in]
|
62
|
+
|
63
|
+
@options = options.reverse_merge(compress_threshold: DEFAULT_COMPRESS_THRESHOLD)
|
28
64
|
@addresses = addresses
|
29
|
-
@cache = Memcached.new(@addresses,
|
65
|
+
@cache = Memcached.new(@addresses, client_options.reverse_merge(DEFAULT_CLIENT_OPTIONS))
|
66
|
+
@cache.instance_eval { send(:extend, GetWithFlags) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def fetch(key, options = nil)
|
70
|
+
if block_given?
|
71
|
+
key = expanded_key(key)
|
72
|
+
unless options && options[:force]
|
73
|
+
entry = instrument(:read, key, options) do |payload|
|
74
|
+
payload[:super_operation] = :fetch if payload
|
75
|
+
read_entry(key, options)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
if entry.nil?
|
80
|
+
result = instrument(:generate, key, options) do |payload|
|
81
|
+
yield
|
82
|
+
end
|
83
|
+
write_entry(key, result, options)
|
84
|
+
result
|
85
|
+
else
|
86
|
+
instrument(:fetch_hit, key, options) { |payload| }
|
87
|
+
entry
|
88
|
+
end
|
89
|
+
else
|
90
|
+
read(key, options)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def read(key, options = nil)
|
95
|
+
key = expanded_key(key)
|
96
|
+
instrument(:read, key, options) do |payload|
|
97
|
+
entry = read_entry(key, options)
|
98
|
+
payload[:hit] = !!entry if payload
|
99
|
+
entry
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def write(key, value, options = nil)
|
104
|
+
key = expanded_key(key)
|
105
|
+
instrument(:write, key, options) do |payload|
|
106
|
+
write_entry(key, value, options)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def delete(key, options = nil)
|
111
|
+
key = expanded_key(key)
|
112
|
+
instrument(:delete, key) do |payload|
|
113
|
+
delete_entry(key, options)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def exist?(key, options = nil)
|
118
|
+
key = expanded_key(key)
|
119
|
+
instrument(:exist?, key) do |payload|
|
120
|
+
if @cache.respond_to?(:exist)
|
121
|
+
@cache.exist(escape_and_normalize(key))
|
122
|
+
true
|
123
|
+
else
|
124
|
+
read_entry(key, options) != nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
rescue Memcached::NotFound
|
128
|
+
false
|
30
129
|
end
|
31
130
|
|
32
131
|
def increment(key, amount = 1, options = nil)
|
132
|
+
key = expanded_key(key)
|
33
133
|
instrument(:increment, key, amount: amount) do
|
34
|
-
@cache.incr(key, amount)
|
134
|
+
@cache.incr(escape_and_normalize(key), amount)
|
35
135
|
end
|
36
|
-
rescue Memcached::
|
136
|
+
rescue Memcached::NotFound
|
137
|
+
nil
|
138
|
+
rescue Memcached::Error => e
|
139
|
+
log_error(e)
|
37
140
|
nil
|
38
141
|
end
|
39
142
|
|
40
143
|
def decrement(key, amount = 1, options = nil)
|
144
|
+
key = expanded_key(key)
|
41
145
|
instrument(:decrement, key, amount: amount) do
|
42
|
-
@cache.decr(key, amount)
|
146
|
+
@cache.decr(escape_and_normalize(key), amount)
|
43
147
|
end
|
44
|
-
rescue Memcached::
|
148
|
+
rescue Memcached::NotFound
|
149
|
+
nil
|
150
|
+
rescue Memcached::Error => e
|
151
|
+
log_error(e)
|
45
152
|
nil
|
46
153
|
end
|
47
154
|
|
48
|
-
#
|
49
|
-
# Optimize read_multi to only make one call to memcached
|
50
|
-
# server.
|
51
|
-
#
|
52
155
|
def read_multi(*names)
|
156
|
+
names.flatten!
|
53
157
|
options = names.extract_options!
|
54
|
-
options = merged_options(options)
|
55
158
|
|
56
159
|
return {} if names.empty?
|
57
160
|
|
58
|
-
|
59
|
-
raw_values = @cache.get(
|
161
|
+
mapping = Hash[names.map {|name| [escape_and_normalize(expanded_key(name)), name] }]
|
162
|
+
raw_values, flags = @cache.get(mapping.keys, false, true)
|
60
163
|
|
61
164
|
values = {}
|
62
165
|
raw_values.each do |key, value|
|
63
|
-
|
64
|
-
values[keys_to_names[key]] = entry.value unless entry.expired?
|
166
|
+
values[mapping[key]] = deserialize(value, options[:raw], flags[key])
|
65
167
|
end
|
66
168
|
values
|
67
169
|
end
|
@@ -77,7 +179,9 @@ module ActiveSupport
|
|
77
179
|
protected
|
78
180
|
|
79
181
|
def read_entry(key, options = nil)
|
80
|
-
|
182
|
+
options ||= {}
|
183
|
+
raw_value, flags = @cache.get(escape_and_normalize(key), false, true)
|
184
|
+
deserialize(raw_value, options[:raw], flags)
|
81
185
|
rescue Memcached::NotFound
|
82
186
|
nil
|
83
187
|
rescue Memcached::Error => e
|
@@ -85,13 +189,18 @@ module ActiveSupport
|
|
85
189
|
nil
|
86
190
|
end
|
87
191
|
|
88
|
-
# Set the key to the given value. Pass :unless_exist => true if you want to
|
89
|
-
# skip setting a key that already exists.
|
90
192
|
def write_entry(key, entry, options = nil)
|
91
|
-
|
92
|
-
|
193
|
+
options = options ? @options.merge(options) : @options
|
194
|
+
method = options[:unless_exist] ? :add : :set
|
195
|
+
entry = options[:raw] ? entry.to_s : Marshal.dump(entry)
|
196
|
+
flags = 0
|
93
197
|
|
94
|
-
|
198
|
+
if options[:compress] && entry.bytesize >= options[:compress_threshold]
|
199
|
+
entry = Zlib::Deflate.deflate(entry)
|
200
|
+
flags |= FLAG_COMPRESSED
|
201
|
+
end
|
202
|
+
|
203
|
+
@cache.send(method, escape_and_normalize(key), entry, options[:expires_in].to_i, false, flags)
|
95
204
|
true
|
96
205
|
rescue Memcached::Error => e
|
97
206
|
log_error(e)
|
@@ -99,7 +208,7 @@ module ActiveSupport
|
|
99
208
|
end
|
100
209
|
|
101
210
|
def delete_entry(key, options = nil)
|
102
|
-
@cache.delete(key)
|
211
|
+
@cache.delete(escape_and_normalize(key))
|
103
212
|
true
|
104
213
|
rescue Memcached::NotFound
|
105
214
|
false
|
@@ -109,27 +218,66 @@ module ActiveSupport
|
|
109
218
|
end
|
110
219
|
|
111
220
|
private
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
221
|
+
|
222
|
+
def deserialize(value, raw = false, flags = 0)
|
223
|
+
value = Zlib::Inflate.inflate(value) if (flags & FLAG_COMPRESSED) != 0
|
224
|
+
raw ? value : Marshal.load(value)
|
225
|
+
rescue TypeError, ArgumentError
|
226
|
+
value
|
227
|
+
end
|
228
|
+
|
229
|
+
def escape_and_normalize(key)
|
230
|
+
key = key.to_s.force_encoding("BINARY").gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
|
231
|
+
key_length = key.length
|
232
|
+
|
233
|
+
return key if @namespace_length + key_length <= 250
|
234
|
+
|
235
|
+
max_key_length = 213 - @namespace_length
|
236
|
+
"#{key[0, max_key_length]}:md5:#{Digest::MD5.hexdigest(key)}"
|
237
|
+
end
|
238
|
+
|
239
|
+
def expanded_key(key) # :nodoc:
|
240
|
+
return key.cache_key.to_s if key.respond_to?(:cache_key)
|
241
|
+
|
242
|
+
case key
|
243
|
+
when Array
|
244
|
+
if key.size > 1
|
245
|
+
key = key.collect { |element| expanded_key(element) }
|
246
|
+
else
|
247
|
+
key = key.first
|
248
|
+
end
|
249
|
+
when Hash
|
250
|
+
key = key.sort_by { |k,_| k.to_s }.collect { |k, v| "#{k}=#{v}" }
|
118
251
|
end
|
252
|
+
|
253
|
+
key.to_param
|
119
254
|
end
|
120
255
|
|
121
|
-
def
|
122
|
-
(
|
256
|
+
def instrument(operation, key, options=nil)
|
257
|
+
log(operation, key, options)
|
258
|
+
|
259
|
+
if ActiveSupport::Cache::Store.instrument
|
260
|
+
payload = { :key => key }
|
261
|
+
payload.merge!(options) if options.is_a?(Hash)
|
262
|
+
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
|
263
|
+
else
|
264
|
+
yield(nil)
|
265
|
+
end
|
123
266
|
end
|
124
267
|
|
125
|
-
def
|
126
|
-
!
|
268
|
+
def log(operation, key, options=nil)
|
269
|
+
return unless !silence? && logger && logger.debug?
|
270
|
+
logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}")
|
127
271
|
end
|
128
272
|
|
129
273
|
def log_error(exception)
|
130
|
-
return unless logger && logger.error?
|
274
|
+
return unless !silence? && logger && logger.error?
|
131
275
|
logger.error "MemcachedError (#{exception.inspect}): #{exception.message}"
|
132
276
|
end
|
277
|
+
|
278
|
+
def logger
|
279
|
+
Rails.logger
|
280
|
+
end
|
133
281
|
end
|
134
282
|
end
|
135
283
|
end
|
data/lib/libmemcached_store.rb
CHANGED
@@ -0,0 +1,42 @@
|
|
1
|
+
#
|
2
|
+
# Allow get method to returns value + entry's flags
|
3
|
+
# This is useful to set compression flag.
|
4
|
+
#
|
5
|
+
module GetWithFlags
|
6
|
+
def get(keys, marshal=true, with_flags=false)
|
7
|
+
if keys.is_a? Array
|
8
|
+
# Multi get
|
9
|
+
ret = Memcached::Lib.memcached_mget(@struct, keys);
|
10
|
+
check_return_code(ret, keys)
|
11
|
+
|
12
|
+
hash, flags_hash = {}, {}
|
13
|
+
value, key, flags, ret = Memcached::Lib.memcached_fetch_rvalue(@struct)
|
14
|
+
while ret != 21 do # Lib::MEMCACHED_END
|
15
|
+
if ret == 0 # Lib::MEMCACHED_SUCCESS
|
16
|
+
hash[key] = value
|
17
|
+
flags_hash[key] = flags if with_flags
|
18
|
+
elsif ret != 16 # Lib::MEMCACHED_NOTFOUND
|
19
|
+
check_return_code(ret, key)
|
20
|
+
end
|
21
|
+
value, key, flags, ret = Memcached::Lib.memcached_fetch_rvalue(@struct)
|
22
|
+
end
|
23
|
+
if marshal
|
24
|
+
hash.each do |key, value|
|
25
|
+
hash[key] = Marshal.load(value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
with_flags ? [hash, flags_hash] : hash
|
29
|
+
else
|
30
|
+
# Single get
|
31
|
+
value, flags, ret = Memcached::Lib.memcached_get_rvalue(@struct, keys)
|
32
|
+
check_return_code(ret, keys)
|
33
|
+
value = Marshal.load(value) if marshal
|
34
|
+
with_flags ? [value, flags] : value
|
35
|
+
end
|
36
|
+
rescue => e
|
37
|
+
tries ||= 0
|
38
|
+
raise unless tries < options[:exception_retry_limit] && should_retry(e)
|
39
|
+
tries += 1
|
40
|
+
retry
|
41
|
+
end
|
42
|
+
end
|
data/lib/version.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
require 'memcached'
|
3
5
|
require 'active_support'
|
@@ -7,67 +9,101 @@ require 'active_support/cache/libmemcached_store'
|
|
7
9
|
|
8
10
|
# Make it easier to get at the underlying cache options during testing.
|
9
11
|
class ActiveSupport::Cache::LibmemcachedStore
|
10
|
-
|
12
|
+
def client_options
|
13
|
+
@cache.options
|
14
|
+
end
|
11
15
|
end
|
12
16
|
|
13
|
-
|
14
|
-
def
|
15
|
-
|
16
|
-
assert_equal 'bar', @cache.read('foo')
|
17
|
-
end
|
18
|
-
|
19
|
-
def test_should_overwrite
|
20
|
-
@cache.write('foo', 'bar')
|
21
|
-
@cache.write('foo', 'baz')
|
22
|
-
assert_equal 'baz', @cache.read('foo')
|
17
|
+
class MockUser
|
18
|
+
def cache_key
|
19
|
+
'foo'
|
23
20
|
end
|
21
|
+
end
|
24
22
|
|
23
|
+
module CacheStoreBehavior
|
25
24
|
def test_fetch_without_cache_miss
|
26
25
|
@cache.write('foo', 'bar')
|
27
|
-
@cache.expects(:
|
26
|
+
@cache.expects(:write_entry).never
|
28
27
|
assert_equal 'bar', @cache.fetch('foo') { 'baz' }
|
29
28
|
end
|
30
29
|
|
31
30
|
def test_fetch_with_cache_miss
|
32
|
-
@cache.expects(:
|
31
|
+
@cache.expects(:write_entry).with('foo', 'baz', nil)
|
33
32
|
assert_equal 'baz', @cache.fetch('foo') { 'baz' }
|
34
33
|
end
|
35
34
|
|
36
35
|
def test_fetch_with_forced_cache_miss
|
37
36
|
@cache.write('foo', 'bar')
|
38
|
-
@cache.expects(:
|
39
|
-
@cache.expects(:
|
40
|
-
@cache.fetch('foo', :
|
37
|
+
@cache.expects(:read_entry).never
|
38
|
+
@cache.expects(:write_entry).with('foo', 'baz', force: true)
|
39
|
+
assert_equal 'baz', @cache.fetch('foo', force: true) { 'baz' }
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_fetch_with_cached_false
|
43
|
+
@cache.write('foo', false)
|
44
|
+
refute @cache.fetch('foo') { raise }
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_fetch_with_raw_object
|
48
|
+
o = Object.new
|
49
|
+
o.instance_variable_set :@foo, 'bar'
|
50
|
+
assert_equal o, @cache.fetch('foo', raw: true) { o }
|
41
51
|
end
|
42
52
|
|
43
|
-
def
|
44
|
-
|
45
|
-
@cache.
|
46
|
-
|
53
|
+
def test_fetch_with_cache_key
|
54
|
+
u = MockUser.new
|
55
|
+
@cache.write(u.cache_key, 'bar')
|
56
|
+
assert_equal 'bar', @cache.fetch(u) { raise }
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_should_read_and_write_strings
|
60
|
+
assert @cache.write('foo', 'bar')
|
61
|
+
assert_equal 'bar', @cache.read('foo')
|
47
62
|
end
|
48
63
|
|
49
64
|
def test_should_read_and_write_hash
|
50
|
-
|
51
|
-
assert_equal({:
|
65
|
+
assert @cache.write('foo', { a: 'b' })
|
66
|
+
assert_equal({ a: 'b' }, @cache.read('foo'))
|
52
67
|
end
|
53
68
|
|
54
69
|
def test_should_read_and_write_integer
|
55
|
-
|
70
|
+
assert @cache.write('foo', 1)
|
56
71
|
assert_equal 1, @cache.read('foo')
|
57
72
|
end
|
58
73
|
|
59
74
|
def test_should_read_and_write_nil
|
60
|
-
|
75
|
+
assert @cache.write('foo', nil)
|
61
76
|
assert_equal nil, @cache.read('foo')
|
62
77
|
end
|
63
78
|
|
64
79
|
def test_should_read_and_write_false
|
65
80
|
assert @cache.write('foo', false)
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
81
|
+
assert_equal false, @cache.read('foo')
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_read_and_write_compressed_data
|
85
|
+
@cache.write('foo', 'bar', :compress => true, :compress_threshold => 1)
|
86
|
+
assert_equal 'bar', @cache.read('foo')
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_write_should_overwrite
|
90
|
+
@cache.write('foo', 'bar')
|
91
|
+
@cache.write('foo', 'baz')
|
92
|
+
assert_equal 'baz', @cache.read('foo')
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_write_compressed_data
|
96
|
+
@cache.write('foo', 'bar', :compress => true, :compress_threshold => 1, :raw => true)
|
97
|
+
assert_equal Zlib::Deflate.deflate('bar'), @cache.instance_variable_get(:@cache).get('foo', false)
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_read_miss
|
101
|
+
assert_nil @cache.read('foo')
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_read_should_return_a_different_object_id_each_time_it_is_called
|
105
|
+
@cache.write('foo', 'bar')
|
106
|
+
refute_equal @cache.read('foo').object_id, @cache.read('foo').object_id
|
71
107
|
end
|
72
108
|
|
73
109
|
def test_read_multi
|
@@ -77,94 +113,75 @@ module CacheStoreBehavior
|
|
77
113
|
assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
|
78
114
|
end
|
79
115
|
|
80
|
-
def
|
81
|
-
@cache.write('foo', 'bar'
|
116
|
+
def test_read_multi_with_array
|
117
|
+
@cache.write('foo', 'bar')
|
82
118
|
@cache.write('fu', 'baz')
|
83
|
-
@cache.
|
84
|
-
sleep(0.002)
|
85
|
-
assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu'))
|
86
|
-
end
|
87
|
-
|
88
|
-
def test_read_and_write_compressed_small_data
|
89
|
-
@cache.write('foo', 'bar', :compress => true)
|
90
|
-
raw_value = @cache.send(:read_entry, 'foo', {}).raw_value
|
91
|
-
assert_equal 'bar', @cache.read('foo')
|
92
|
-
value = Marshal.load(raw_value) rescue raw_value
|
93
|
-
assert_equal 'bar', value
|
119
|
+
assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi(['foo', 'fu']))
|
94
120
|
end
|
95
121
|
|
96
|
-
def
|
97
|
-
@cache.write('foo', 'bar', :
|
98
|
-
|
99
|
-
assert_equal
|
100
|
-
assert_equal 'bar', Marshal.load(Zlib::Inflate.inflate(raw_value))
|
122
|
+
def test_read_multi_with_raw
|
123
|
+
@cache.write('foo', 'bar', :raw => true)
|
124
|
+
@cache.write('fu', 'baz', :raw => true)
|
125
|
+
assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
|
101
126
|
end
|
102
127
|
|
103
|
-
def
|
104
|
-
@cache.write('foo',
|
105
|
-
|
128
|
+
def test_read_multi_with_compress
|
129
|
+
@cache.write('foo', 'bar', :compress => true, :compress_threshold => 1)
|
130
|
+
@cache.write('fu', 'baz', :compress => true, :compress_threshold => 1)
|
131
|
+
assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
|
106
132
|
end
|
107
133
|
|
108
134
|
def test_cache_key
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
end
|
113
|
-
@cache.write(obj, "bar")
|
114
|
-
assert_equal "bar", @cache.read("foo")
|
135
|
+
o = MockUser.new
|
136
|
+
@cache.write(o, 'bar')
|
137
|
+
assert_equal 'bar', @cache.read('foo')
|
115
138
|
end
|
116
139
|
|
117
140
|
def test_param_as_cache_key
|
118
141
|
obj = Object.new
|
119
142
|
def obj.to_param
|
120
|
-
|
143
|
+
'foo'
|
121
144
|
end
|
122
|
-
@cache.write(obj,
|
123
|
-
assert_equal
|
145
|
+
@cache.write(obj, 'bar')
|
146
|
+
assert_equal 'bar', @cache.read('foo')
|
124
147
|
end
|
125
148
|
|
126
149
|
def test_array_as_cache_key
|
127
|
-
@cache.write([:fu,
|
128
|
-
assert_equal
|
150
|
+
@cache.write([:fu, 'foo'], 'bar')
|
151
|
+
assert_equal 'bar', @cache.read('fu/foo')
|
129
152
|
end
|
130
153
|
|
131
154
|
def test_hash_as_cache_key
|
132
|
-
@cache.write({:foo => 1, :fu => 2},
|
133
|
-
assert_equal
|
155
|
+
@cache.write({:foo => 1, :fu => 2}, 'bar')
|
156
|
+
assert_equal 'bar', @cache.read('foo=1/fu=2')
|
134
157
|
end
|
135
158
|
|
136
159
|
def test_keys_are_case_sensitive
|
137
|
-
@cache.write(
|
138
|
-
assert_nil @cache.read(
|
160
|
+
@cache.write('foo', 'bar')
|
161
|
+
assert_nil @cache.read('FOO')
|
139
162
|
end
|
140
163
|
|
141
|
-
def
|
142
|
-
@cache.
|
143
|
-
assert_equal true, @cache.exist?('foo')
|
144
|
-
assert_equal false, @cache.exist?('bar')
|
164
|
+
def test_keys_with_spaces
|
165
|
+
assert_equal 'baz', @cache.fetch('foo bar') { 'baz' }
|
145
166
|
end
|
146
167
|
|
147
|
-
def
|
148
|
-
@cache.write('foo',
|
149
|
-
|
168
|
+
def test_exist
|
169
|
+
@cache.write('foo', 'bar')
|
170
|
+
assert @cache.exist?('foo')
|
171
|
+
refute @cache.exist?('bar')
|
150
172
|
end
|
151
173
|
|
152
174
|
def test_delete
|
153
175
|
@cache.write('foo', 'bar')
|
154
176
|
assert @cache.exist?('foo')
|
155
|
-
|
156
|
-
|
177
|
+
assert @cache.delete('foo')
|
178
|
+
refute @cache.exist?('foo')
|
157
179
|
end
|
158
180
|
|
159
181
|
def test_delete_with_unexistent_key
|
160
182
|
@cache.expects(:log_error).never
|
161
|
-
|
162
|
-
|
163
|
-
end
|
164
|
-
|
165
|
-
def test_read_should_return_a_different_object_id_each_time_it_is_called
|
166
|
-
@cache.write('foo', 'bar')
|
167
|
-
refute_equal @cache.read('foo').object_id, @cache.read('foo').object_id
|
183
|
+
refute @cache.exist?('foo')
|
184
|
+
refute @cache.delete('foo')
|
168
185
|
end
|
169
186
|
|
170
187
|
def test_store_objects_should_be_immutable
|
@@ -179,112 +196,42 @@ module CacheStoreBehavior
|
|
179
196
|
assert_equal 'baz', bar.gsub!(/r/, 'z')
|
180
197
|
end
|
181
198
|
|
182
|
-
def test_expires_in
|
183
|
-
time = Time.local(2008, 4, 24)
|
184
|
-
Time.stubs(:now).returns(time)
|
185
|
-
|
186
|
-
@cache.write('foo', 'bar', :expires_in => 45)
|
187
|
-
assert_equal 'bar', @cache.read('foo')
|
188
|
-
|
189
|
-
Time.stubs(:now).returns(time + 30)
|
190
|
-
assert_equal 'bar', @cache.read('foo')
|
191
|
-
|
192
|
-
Time.stubs(:now).returns(time + 61)
|
193
|
-
assert_nil @cache.read('foo')
|
194
|
-
end
|
195
|
-
|
196
|
-
def test_expires_in_as_activesupport_duration
|
197
|
-
time = Time.local(2012, 02, 03)
|
198
|
-
Time.stubs(:now).returns(time)
|
199
|
-
|
200
|
-
@cache.write('foo', 'bar', :expires_in => 1.minute)
|
201
|
-
assert_equal 'bar', @cache.read('foo')
|
202
|
-
|
203
|
-
Time.stubs(:now).returns(time + 30)
|
204
|
-
assert_equal 'bar', @cache.read('foo')
|
205
|
-
|
206
|
-
Time.stubs(:now).returns(time + 61)
|
207
|
-
assert_nil @cache.read('foo')
|
208
|
-
end
|
209
|
-
|
210
|
-
def test_expires_in_as_float
|
211
|
-
time = Time.local(2012, 02, 03)
|
212
|
-
Time.stubs(:now).returns(time)
|
213
|
-
|
214
|
-
@cache.write('foo', 'bar', :expires_in => 60.0)
|
215
|
-
assert_equal 'bar', @cache.read('foo')
|
216
|
-
|
217
|
-
Time.stubs(:now).returns(time + 30)
|
218
|
-
assert_equal 'bar', @cache.read('foo')
|
219
|
-
|
220
|
-
Time.stubs(:now).returns(time + 61)
|
221
|
-
assert_nil @cache.read('foo')
|
222
|
-
end
|
223
|
-
|
224
|
-
def test_race_condition_protection
|
225
|
-
time = Time.now
|
226
|
-
@cache.write('foo', 'bar', :expires_in => 60)
|
227
|
-
Time.stubs(:now).returns(time + 61)
|
228
|
-
result = @cache.fetch('foo', :race_condition_ttl => 10) do
|
229
|
-
assert_equal 'bar', @cache.read('foo')
|
230
|
-
"baz"
|
231
|
-
end
|
232
|
-
assert_equal "baz", result
|
233
|
-
end
|
234
|
-
|
235
|
-
def test_race_condition_protection_is_limited
|
236
|
-
time = Time.now
|
237
|
-
@cache.write('foo', 'bar', :expires_in => 60)
|
238
|
-
Time.stubs(:now).returns(time + 71)
|
239
|
-
result = @cache.fetch('foo', :race_condition_ttl => 10) do
|
240
|
-
assert_equal nil, @cache.read('foo')
|
241
|
-
"baz"
|
242
|
-
end
|
243
|
-
assert_equal "baz", result
|
244
|
-
end
|
245
|
-
|
246
|
-
def test_race_condition_protection_is_safe
|
247
|
-
time = Time.now
|
248
|
-
@cache.write('foo', 'bar', :expires_in => 60)
|
249
|
-
Time.stubs(:now).returns(time + 61)
|
250
|
-
begin
|
251
|
-
@cache.fetch('foo', :race_condition_ttl => 10) do
|
252
|
-
assert_equal 'bar', @cache.read('foo')
|
253
|
-
raise ArgumentError.new
|
254
|
-
end
|
255
|
-
rescue ArgumentError
|
256
|
-
end
|
257
|
-
assert_equal "bar", @cache.read('foo')
|
258
|
-
Time.stubs(:now).returns(time + 71)
|
259
|
-
assert_nil @cache.read('foo')
|
260
|
-
end
|
261
|
-
|
262
199
|
def test_crazy_key_characters
|
263
200
|
crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-"
|
264
|
-
|
201
|
+
assert @cache.write(crazy_key, "1", :raw => true)
|
265
202
|
assert_equal "1", @cache.read(crazy_key)
|
266
203
|
assert_equal "1", @cache.fetch(crazy_key)
|
267
|
-
|
204
|
+
assert @cache.delete(crazy_key)
|
205
|
+
refute @cache.exist?(crazy_key)
|
268
206
|
assert_equal "2", @cache.fetch(crazy_key, :raw => true) { "2" }
|
269
207
|
assert_equal 3, @cache.increment(crazy_key)
|
270
208
|
assert_equal 2, @cache.decrement(crazy_key)
|
271
209
|
end
|
272
210
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
211
|
+
def test_really_long_keys
|
212
|
+
key = "a" * 251
|
213
|
+
assert @cache.write(key, "bar")
|
214
|
+
assert_equal "bar", @cache.read(key)
|
215
|
+
assert_equal "bar", @cache.fetch(key)
|
216
|
+
assert_nil @cache.read("#{key}x")
|
217
|
+
assert_equal({key => "bar"}, @cache.read_multi(key))
|
218
|
+
assert @cache.delete(key)
|
219
|
+
refute @cache.exist?(key)
|
220
|
+
assert @cache.write(key, '2', :raw => true)
|
221
|
+
assert_equal 3, @cache.increment(key)
|
222
|
+
assert_equal 2, @cache.decrement(key)
|
223
|
+
end
|
224
|
+
|
225
|
+
def test_really_long_keys_with_namespace
|
226
|
+
@cache = ActiveSupport::Cache.lookup_store(:libmemcached_store, :expires_in => 60, :namespace => 'namespace')
|
227
|
+
@cache.silence!
|
228
|
+
test_really_long_keys
|
229
|
+
end
|
283
230
|
end
|
284
231
|
|
285
232
|
module CacheIncrementDecrementBehavior
|
286
233
|
def test_increment
|
287
|
-
@cache.write('foo', 1, :raw => true)
|
234
|
+
@cache.write('foo', '1', :raw => true)
|
288
235
|
assert_equal 1, @cache.read('foo').to_i
|
289
236
|
assert_equal 2, @cache.increment('foo')
|
290
237
|
assert_equal 2, @cache.read('foo').to_i
|
@@ -293,13 +240,36 @@ module CacheIncrementDecrementBehavior
|
|
293
240
|
end
|
294
241
|
|
295
242
|
def test_decrement
|
296
|
-
@cache.write('foo', 3, :raw => true)
|
243
|
+
@cache.write('foo', '3', :raw => true)
|
297
244
|
assert_equal 3, @cache.read('foo').to_i
|
298
245
|
assert_equal 2, @cache.decrement('foo')
|
299
246
|
assert_equal 2, @cache.read('foo').to_i
|
300
247
|
assert_equal 1, @cache.decrement('foo')
|
301
248
|
assert_equal 1, @cache.read('foo').to_i
|
302
249
|
end
|
250
|
+
|
251
|
+
def test_increment_decrement_non_existing_keys
|
252
|
+
@cache.expects(:log_error).never
|
253
|
+
assert_nil @cache.increment('foo')
|
254
|
+
assert_nil @cache.decrement('bar')
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
module CacheCompressBehavior
|
259
|
+
def test_read_and_write_compressed_small_data
|
260
|
+
@cache.write('foo', 'bar', :compress => true)
|
261
|
+
raw_value = @cache.send(:read_entry, 'foo', {}).raw_value
|
262
|
+
assert_equal 'bar', @cache.read('foo')
|
263
|
+
value = Marshal.load(raw_value) rescue raw_value
|
264
|
+
assert_equal 'bar', value
|
265
|
+
end
|
266
|
+
|
267
|
+
def test_read_and_write_compressed_large_data
|
268
|
+
@cache.write('foo', 'bar', :compress => true, :compress_threshold => 2)
|
269
|
+
raw_value = @cache.send(:read_entry, 'foo', {}).raw_value
|
270
|
+
assert_equal 'bar', @cache.read('foo')
|
271
|
+
assert_equal 'bar', Marshal.load(Zlib::Inflate.inflate(raw_value))
|
272
|
+
end
|
303
273
|
end
|
304
274
|
|
305
275
|
class LibmemcachedStoreTest < MiniTest::Unit::TestCase
|
@@ -307,7 +277,7 @@ class LibmemcachedStoreTest < MiniTest::Unit::TestCase
|
|
307
277
|
include CacheIncrementDecrementBehavior
|
308
278
|
|
309
279
|
def setup
|
310
|
-
@cache = ActiveSupport::Cache.lookup_store(:libmemcached_store, :
|
280
|
+
@cache = ActiveSupport::Cache.lookup_store(:libmemcached_store, expires_in: 60)
|
311
281
|
@cache.clear
|
312
282
|
@cache.silence!
|
313
283
|
end
|
@@ -326,26 +296,34 @@ class LibmemcachedStoreTest < MiniTest::Unit::TestCase
|
|
326
296
|
end
|
327
297
|
|
328
298
|
def test_should_enable_consistent_ketema_hashing_by_default
|
329
|
-
assert_equal :consistent_ketama, @cache.
|
299
|
+
assert_equal :consistent_ketama, @cache.client_options[:distribution]
|
330
300
|
end
|
331
301
|
|
332
302
|
def test_should_not_enable_non_blocking_io_by_default
|
333
|
-
assert_equal false, @cache.
|
303
|
+
assert_equal false, @cache.client_options[:no_block]
|
334
304
|
end
|
335
305
|
|
336
306
|
def test_should_not_enable_server_failover_by_default
|
337
|
-
assert_nil @cache.
|
307
|
+
assert_nil @cache.client_options[:failover]
|
338
308
|
end
|
339
309
|
|
340
310
|
def test_should_allow_configuration_of_custom_options
|
341
|
-
options = {
|
342
|
-
:tcp_nodelay => true,
|
343
|
-
:distribution => :modula
|
344
|
-
}
|
311
|
+
options = { client: { tcp_nodelay: true, distribution: :modula } }
|
345
312
|
|
346
313
|
store = ActiveSupport::Cache.lookup_store :libmemcached_store, 'localhost', options
|
347
314
|
|
348
|
-
assert_equal :modula, store.
|
349
|
-
assert_equal true, store.
|
315
|
+
assert_equal :modula, store.client_options[:distribution]
|
316
|
+
assert_equal true, store.client_options[:tcp_nodelay]
|
317
|
+
end
|
318
|
+
|
319
|
+
def test_should_allow_mute_and_silence
|
320
|
+
cache = ActiveSupport::Cache.lookup_store :libmemcached_store, 'localhost'
|
321
|
+
cache.mute do
|
322
|
+
assert cache.write('foo', 'bar')
|
323
|
+
assert_equal 'bar', cache.read('foo')
|
324
|
+
end
|
325
|
+
refute cache.silence?
|
326
|
+
cache.silence!
|
327
|
+
assert cache.silence?
|
350
328
|
end
|
351
329
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'active_support'
|
3
|
+
|
4
|
+
require 'libmemcached_store'
|
5
|
+
require 'active_support/cache/libmemcached_store'
|
6
|
+
|
7
|
+
require 'dalli'
|
8
|
+
require 'active_support/cache/dalli_store'
|
9
|
+
|
10
|
+
puts "Testing with"
|
11
|
+
puts RUBY_DESCRIPTION
|
12
|
+
puts "Dalli #{Dalli::VERSION}"
|
13
|
+
puts "Libmemcached_store #{LibmemcachedStore::VERSION}"
|
14
|
+
|
15
|
+
# We'll use a simple @value to try to avoid spending time in Marshal,
|
16
|
+
# which is a constant penalty that both clients have to pay
|
17
|
+
@value = []
|
18
|
+
@marshalled = Marshal.dump(@value)
|
19
|
+
|
20
|
+
@servers = ['127.0.0.1:11211']
|
21
|
+
@key1 = "Short"
|
22
|
+
@key2 = "Sym1-2-3::45"*4
|
23
|
+
@key3 = "Long"*40
|
24
|
+
@key4 = "Medium"*8
|
25
|
+
|
26
|
+
N = 2_500
|
27
|
+
|
28
|
+
@dalli = ActiveSupport::Cache::DalliStore.new(@servers).silence!
|
29
|
+
@libm = ActiveSupport::Cache::LibmemcachedStore.new(@servers).silence!
|
30
|
+
|
31
|
+
def clear
|
32
|
+
@dalli.clear
|
33
|
+
@libm.clear
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_method(title, method_name, key, *arguments)
|
37
|
+
{ dalli: @dalli, libm: @libm }.each do |name, store|
|
38
|
+
@job.report("#{title}:#{name}") { N.times { store.send(method_name, key, *arguments) } }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def run_method(method_name, key, *arguments)
|
43
|
+
[@dalli, @libm].each do |store|
|
44
|
+
store.send(method_name, key, *arguments)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
Benchmark.bm(31) do |x|
|
49
|
+
@job = x
|
50
|
+
|
51
|
+
test_method('write:short', :write, @key1, @value)
|
52
|
+
test_method('write:long', :write, @key3, @value)
|
53
|
+
test_method('write:raw', :write, @key4, @value, raw: true)
|
54
|
+
|
55
|
+
puts
|
56
|
+
clear
|
57
|
+
|
58
|
+
test_method('read:miss', :read, @key1)
|
59
|
+
test_method('read:miss2', :read, @key1)
|
60
|
+
|
61
|
+
run_method(:write, @key4, @value)
|
62
|
+
test_method('read:exist', :read, @key4)
|
63
|
+
|
64
|
+
run_method(:write, @key4, @value, expires_in: 1)
|
65
|
+
sleep(1)
|
66
|
+
test_method('read:expired', :read, @key2)
|
67
|
+
|
68
|
+
run_method(:write, @key3, @value, raw: true)
|
69
|
+
test_method('read:raw', :read, @key3, raw: true)
|
70
|
+
|
71
|
+
puts
|
72
|
+
clear
|
73
|
+
|
74
|
+
test_method('exist:miss', :exist?, @key4)
|
75
|
+
|
76
|
+
run_method(:write, @key4, @value)
|
77
|
+
test_method('exist:hit', :exist?, @key4)
|
78
|
+
|
79
|
+
puts
|
80
|
+
clear
|
81
|
+
|
82
|
+
test_method('delete:miss', :delete, @key4)
|
83
|
+
|
84
|
+
run_method(:write, @key1, @value)
|
85
|
+
test_method('delete:hit', :delete, @key1)
|
86
|
+
|
87
|
+
puts
|
88
|
+
clear
|
89
|
+
|
90
|
+
run_method(:write, @key4, 0, raw: true)
|
91
|
+
|
92
|
+
test_method('increment', :increment, @key4)
|
93
|
+
test_method('decrement', :decrement, @key4)
|
94
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails3_libmemcached_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2012-
|
14
|
+
date: 2012-09-11 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: memcached
|
@@ -120,6 +120,8 @@ extra_rdoc_files: []
|
|
120
120
|
files:
|
121
121
|
- .gitignore
|
122
122
|
- .travis.yml
|
123
|
+
- BENCHMARKS
|
124
|
+
- CHANGELOG.md
|
123
125
|
- Gemfile
|
124
126
|
- MIT-LICENSE
|
125
127
|
- README.md
|
@@ -128,15 +130,16 @@ files:
|
|
128
130
|
- gemfiles/rails31.gemfile
|
129
131
|
- gemfiles/rails32.gemfile
|
130
132
|
- lib/action_dispatch/session/libmemcached_store.rb
|
131
|
-
- lib/active_support/cache/compressed_libmemcached_store.rb
|
132
133
|
- lib/active_support/cache/libmemcached_store.rb
|
133
134
|
- lib/libmemcached_store.rb
|
135
|
+
- lib/memcached/get_with_flags.rb
|
134
136
|
- lib/version.rb
|
135
137
|
- libmemcached_store.gemspec
|
136
138
|
- test/action_dispatch/abstract_unit.rb
|
137
139
|
- test/action_dispatch/libmemcached_store_test.rb
|
138
140
|
- test/active_support/libmemcached_store_test.rb
|
139
141
|
- test/fixtures/session_autoload_test.rb
|
142
|
+
- test/profile/benchmark.rb
|
140
143
|
- test/test_helper.rb
|
141
144
|
homepage: http://github.com/ccocchi/libmemcached_store
|
142
145
|
licenses: []
|
@@ -158,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
161
|
version: '0'
|
159
162
|
requirements: []
|
160
163
|
rubyforge_project:
|
161
|
-
rubygems_version: 1.8.
|
164
|
+
rubygems_version: 1.8.21
|
162
165
|
signing_key:
|
163
166
|
specification_version: 3
|
164
167
|
summary: ActiveSupport 3+ cache store for the C-based libmemcached client
|
@@ -167,4 +170,5 @@ test_files:
|
|
167
170
|
- test/action_dispatch/libmemcached_store_test.rb
|
168
171
|
- test/active_support/libmemcached_store_test.rb
|
169
172
|
- test/fixtures/session_autoload_test.rb
|
173
|
+
- test/profile/benchmark.rb
|
170
174
|
- test/test_helper.rb
|
@@ -1,15 +0,0 @@
|
|
1
|
-
module ActiveSupport
|
2
|
-
module Cache
|
3
|
-
class CompressedLibmemcachedStore < LibmemcachedStore
|
4
|
-
def read(name, options = {})
|
5
|
-
if value = super(name, (options || {}).merge(:raw => true))
|
6
|
-
Marshal.load(ActiveSupport::Gzip.decompress(value))
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
def write(name, value, options = {})
|
11
|
-
super(name, ActiveSupport::Gzip.compress(Marshal.dump(value)), (options || {}).merge(:raw => true))
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|