rails3_libmemcached_store 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|