segmented-memcache 1.2.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/.gitignore +1 -0
- data/LICENSE +20 -0
- data/README.rdoc +215 -0
- data/Rakefile +56 -0
- data/VERSION.yml +5 -0
- data/lib/memcache/local_server.rb +107 -0
- data/lib/memcache/migration.rb +23 -0
- data/lib/memcache/null_server.rb +30 -0
- data/lib/memcache/pg_server.rb +163 -0
- data/lib/memcache/segmented_server.rb +116 -0
- data/lib/memcache/server.rb +265 -0
- data/lib/memcache.rb +409 -0
- data/segmented-memcache.gemspec +68 -0
- data/test/memcache_local_server_test.rb +11 -0
- data/test/memcache_null_server_test.rb +65 -0
- data/test/memcache_pg_server_test.rb +28 -0
- data/test/memcache_segmented_server_test.rb +21 -0
- data/test/memcache_server_test.rb +35 -0
- data/test/memcache_server_test_helper.rb +159 -0
- data/test/memcache_test.rb +233 -0
- data/test/test_helper.rb +26 -0
- metadata +83 -0
data/lib/memcache.rb
ADDED
@@ -0,0 +1,409 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
$:.unshift(File.dirname(__FILE__))
|
4
|
+
require 'memcache/server'
|
5
|
+
require 'memcache/local_server'
|
6
|
+
require 'memcache/segmented_server'
|
7
|
+
|
8
|
+
class Memcache
|
9
|
+
DEFAULT_EXPIRY = 0
|
10
|
+
LOCK_TIMEOUT = 5
|
11
|
+
WRITE_LOCK_WAIT = 1
|
12
|
+
|
13
|
+
attr_reader :default_expiry, :default_namespace, :servers
|
14
|
+
|
15
|
+
def initialize(opts)
|
16
|
+
@default_expiry = opts[:default_expiry] || DEFAULT_EXPIRY
|
17
|
+
@default_namespace = opts[:namespace]
|
18
|
+
default_server = opts[:segment_large_values] ? SegmentedServer : Server
|
19
|
+
|
20
|
+
@servers = (opts[:servers] || [ opts[:server] ]).collect do |server|
|
21
|
+
case server
|
22
|
+
when Hash
|
23
|
+
server = default_server.new(opts.merge(server))
|
24
|
+
when String
|
25
|
+
host, port = server.split(':')
|
26
|
+
server = default_server.new(opts.merge(:host => host, :port => port))
|
27
|
+
when Class
|
28
|
+
server = server.new
|
29
|
+
when :local
|
30
|
+
server = Memcache::LocalServer.new
|
31
|
+
end
|
32
|
+
server
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def clone
|
37
|
+
self.class.new(
|
38
|
+
:default_expiry => default_expiry,
|
39
|
+
:default_namespace => default_namespace,
|
40
|
+
:servers => servers.collect {|s| s.clone}
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
"<Memcache: %d servers, ns: %p>" % [@servers.length, namespace]
|
46
|
+
end
|
47
|
+
|
48
|
+
def namespace
|
49
|
+
@namespace || default_namespace
|
50
|
+
end
|
51
|
+
|
52
|
+
def namespace=(namespace)
|
53
|
+
if default_namespace == namespace
|
54
|
+
@namespace = nil
|
55
|
+
else
|
56
|
+
@namespace = namespace
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def in_namespace(namespace)
|
61
|
+
# Temporarily change the namespace for convenience.
|
62
|
+
begin
|
63
|
+
old_namespace = self.namespace
|
64
|
+
self.namespace = "#{old_namespace}#{namespace}"
|
65
|
+
yield
|
66
|
+
ensure
|
67
|
+
self.namespace = old_namespace
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def get(keys, opts = {})
|
72
|
+
raise 'opts must be hash' unless opts.kind_of?(Hash)
|
73
|
+
|
74
|
+
if keys.kind_of?(Array)
|
75
|
+
multi_get(keys, opts)
|
76
|
+
else
|
77
|
+
key = cache_key(keys)
|
78
|
+
|
79
|
+
if opts[:expiry]
|
80
|
+
value = server(key).gets(key)
|
81
|
+
server(key).cas(key, value, value.memcache_cas, opts[:expiry]) if value
|
82
|
+
else
|
83
|
+
value = server(key).get(key, opts[:cas])
|
84
|
+
end
|
85
|
+
opts[:raw] ? value : unmarshal(value, key)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def read(keys, opts = {})
|
90
|
+
get(keys, opts.merge(:raw => true))
|
91
|
+
end
|
92
|
+
|
93
|
+
def set(key, value, opts = {})
|
94
|
+
opts = compatible_opts(opts)
|
95
|
+
|
96
|
+
expiry = opts[:expiry] || default_expiry
|
97
|
+
flags = opts[:flags] || 0
|
98
|
+
key = cache_key(key)
|
99
|
+
data = marshal(value, opts)
|
100
|
+
server(key).set(key, data, expiry, flags)
|
101
|
+
value
|
102
|
+
end
|
103
|
+
|
104
|
+
def write(key, value, opts = {})
|
105
|
+
set(key, value, opts.merge(:raw => true))
|
106
|
+
end
|
107
|
+
|
108
|
+
def add(key, value, opts = {})
|
109
|
+
opts = compatible_opts(opts)
|
110
|
+
|
111
|
+
expiry = opts[:expiry] || default_expiry
|
112
|
+
flags = opts[:flags] || 0
|
113
|
+
key = cache_key(key)
|
114
|
+
data = marshal(value, opts)
|
115
|
+
server(key).add(key, data, expiry, flags) && value
|
116
|
+
end
|
117
|
+
|
118
|
+
def replace(key, value, opts = {})
|
119
|
+
opts = compatible_opts(opts)
|
120
|
+
|
121
|
+
expiry = opts[:expiry] || default_expiry
|
122
|
+
flags = opts[:flags] || 0
|
123
|
+
key = cache_key(key)
|
124
|
+
data = marshal(value, opts)
|
125
|
+
server(key).replace(key, data, expiry, flags) && value
|
126
|
+
end
|
127
|
+
|
128
|
+
def cas(key, value, opts = {})
|
129
|
+
raise 'opts must be hash' unless opts.kind_of?(Hash)
|
130
|
+
|
131
|
+
expiry = opts[:expiry] || default_expiry
|
132
|
+
flags = opts[:flags] || 0
|
133
|
+
key = cache_key(key)
|
134
|
+
data = marshal(value, opts)
|
135
|
+
server(key).cas(key, data, opts[:cas], expiry, flags) && value
|
136
|
+
end
|
137
|
+
|
138
|
+
def append(key, value)
|
139
|
+
key = cache_key(key)
|
140
|
+
server(key).append(key, value)
|
141
|
+
end
|
142
|
+
|
143
|
+
def prepend(key, value)
|
144
|
+
key = cache_key(key)
|
145
|
+
server(key).prepend(key, value)
|
146
|
+
end
|
147
|
+
|
148
|
+
def count(key)
|
149
|
+
key = cache_key(key)
|
150
|
+
server(key).get(key).to_i
|
151
|
+
end
|
152
|
+
|
153
|
+
def incr(key, amount = 1)
|
154
|
+
key = cache_key(key)
|
155
|
+
server(key).incr(key, amount)
|
156
|
+
end
|
157
|
+
|
158
|
+
def decr(key, amount = 1)
|
159
|
+
key = cache_key(key)
|
160
|
+
server(key).decr(key, amount)
|
161
|
+
end
|
162
|
+
|
163
|
+
def update(key, opts = {})
|
164
|
+
value = get(key, :cas => true)
|
165
|
+
if value
|
166
|
+
cas(key, yield(value), opts.merge!(:cas => value.memcache_cas))
|
167
|
+
else
|
168
|
+
add(key, yield(value), opts)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def get_or_add(key, *args)
|
173
|
+
# Pseudo-atomic get and update.
|
174
|
+
if block_given?
|
175
|
+
opts = args[0] || {}
|
176
|
+
get(key) || add(key, yield, opts) || get(key)
|
177
|
+
else
|
178
|
+
opts = args[1] || {}
|
179
|
+
get(key) || add(key, args[0], opts) || get(key)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def get_or_set(key, *args)
|
184
|
+
if block_given?
|
185
|
+
opts = args[0] || {}
|
186
|
+
get(key) || set(key, yield, opts)
|
187
|
+
else
|
188
|
+
opts = args[1] || {}
|
189
|
+
get(key) || set(key, args[0], opts)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def get_some(keys, opts = {})
|
194
|
+
keys = keys.collect {|key| key.to_s}
|
195
|
+
|
196
|
+
records = opts[:disable] ? {} : self.get(keys, opts)
|
197
|
+
if opts[:validation]
|
198
|
+
records.delete_if do |key, value|
|
199
|
+
not opts[:validation].call(key, value)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
keys_to_fetch = keys - records.keys
|
204
|
+
method = opts[:overwrite] ? :set : :add
|
205
|
+
if keys_to_fetch.any?
|
206
|
+
yield(keys_to_fetch).each do |key, value|
|
207
|
+
self.send(method, key, value, opts) unless opts[:disable] or opts[:disable_write]
|
208
|
+
records[key] = value
|
209
|
+
end
|
210
|
+
end
|
211
|
+
records
|
212
|
+
end
|
213
|
+
|
214
|
+
def lock(key, opts = {})
|
215
|
+
# Returns false if the lock already exists.
|
216
|
+
expiry = opts[:expiry] || LOCK_TIMEOUT
|
217
|
+
add(lock_key(key), Socket.gethostname, :expiry => expiry, :raw => true)
|
218
|
+
end
|
219
|
+
|
220
|
+
def unlock(key)
|
221
|
+
delete(lock_key(key))
|
222
|
+
end
|
223
|
+
|
224
|
+
def with_lock(key, opts = {})
|
225
|
+
until lock(key) do
|
226
|
+
return if opts[:ignore]
|
227
|
+
sleep(WRITE_LOCK_WAIT) # just wait
|
228
|
+
end
|
229
|
+
yield
|
230
|
+
unlock(key) unless opts[:keep]
|
231
|
+
end
|
232
|
+
|
233
|
+
def lock_key(key)
|
234
|
+
"lock:#{key}"
|
235
|
+
end
|
236
|
+
|
237
|
+
def locked?(key)
|
238
|
+
get(lock_key(key), :raw => true)
|
239
|
+
end
|
240
|
+
|
241
|
+
def delete(key)
|
242
|
+
key = cache_key(key)
|
243
|
+
server(key).delete(key)
|
244
|
+
end
|
245
|
+
|
246
|
+
def flush_all(opts = {})
|
247
|
+
delay = opts[:delay].to_i
|
248
|
+
interval = opts[:interval].to_i
|
249
|
+
|
250
|
+
servers.each do |server|
|
251
|
+
server.flush_all(delay)
|
252
|
+
delay += interval
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def reset
|
257
|
+
servers.each {|server| server.close if server.respond_to?(:close)}
|
258
|
+
end
|
259
|
+
|
260
|
+
def stats(field = nil)
|
261
|
+
if field
|
262
|
+
servers.collect do |server|
|
263
|
+
server.stats[field]
|
264
|
+
end
|
265
|
+
else
|
266
|
+
stats = {}
|
267
|
+
servers.each do |server|
|
268
|
+
stats[server.name] = server.stats
|
269
|
+
end
|
270
|
+
stats
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
alias clear flush_all
|
275
|
+
|
276
|
+
def [](key)
|
277
|
+
get(key)
|
278
|
+
end
|
279
|
+
|
280
|
+
def []=(key, value)
|
281
|
+
set(key, value)
|
282
|
+
end
|
283
|
+
|
284
|
+
def self.init(yaml_file = nil)
|
285
|
+
yaml_file = File.join(Rails.root, 'config', 'memcached.yml')
|
286
|
+
|
287
|
+
if File.exists?(yaml_file)
|
288
|
+
yaml = YAML.load_file(yaml_file)
|
289
|
+
defaults = (yaml.delete('defaults') || {}).symbolize_keys
|
290
|
+
config = (yaml[Rails.env] || {}).symbolize_keys
|
291
|
+
|
292
|
+
if not config.empty? and not config[:disabled]
|
293
|
+
if config[:servers]
|
294
|
+
opts = defaults.merge(config.symbolize_keys)
|
295
|
+
Object.const_set('CACHE', Memcache.new(opts))
|
296
|
+
else
|
297
|
+
config.each do |connection, opts|
|
298
|
+
opts = defaults.merge(opts.symbolize_keys)
|
299
|
+
Memcache.pool[connection] = Memcache.new(opts)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
protected
|
307
|
+
|
308
|
+
def compatible_opts(opts)
|
309
|
+
# Support passing expiry instead of opts. This may be deprecated in the future.
|
310
|
+
opts.kind_of?(Hash) ? opts : {:expiry => opts}
|
311
|
+
end
|
312
|
+
|
313
|
+
def multi_get(keys, opts = {})
|
314
|
+
return {} if keys.empty?
|
315
|
+
|
316
|
+
key_to_input_key = {}
|
317
|
+
keys_by_server = Hash.new { |h,k| h[k] = [] }
|
318
|
+
|
319
|
+
# Store keys by servers. Also store a mapping from cache key to input key.
|
320
|
+
keys.each do |input_key|
|
321
|
+
key = cache_key(input_key)
|
322
|
+
server = server(key)
|
323
|
+
key_to_input_key[key] = input_key.to_s
|
324
|
+
keys_by_server[server] << key
|
325
|
+
end
|
326
|
+
|
327
|
+
# Fetch and combine the results. Also, map the cache keys back to the input keys.
|
328
|
+
results = {}
|
329
|
+
keys_by_server.each do |server, keys|
|
330
|
+
server.get(keys, opts[:cas]).each do |key, value|
|
331
|
+
input_key = key_to_input_key[key]
|
332
|
+
results[input_key] = opts[:raw] ? value : unmarshal(value, key)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
results
|
336
|
+
end
|
337
|
+
|
338
|
+
def cache_key(key)
|
339
|
+
safe_key = key ? key.to_s.gsub(/%/, '%%').gsub(/ /, '%s') : key
|
340
|
+
if namespace.nil? then
|
341
|
+
safe_key
|
342
|
+
else
|
343
|
+
"#{namespace}:#{safe_key}"
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def marshal(value, opts = {})
|
348
|
+
opts[:raw] ? value : Marshal.dump(value)
|
349
|
+
end
|
350
|
+
|
351
|
+
def unmarshal(value, key = nil)
|
352
|
+
return value if value.nil?
|
353
|
+
|
354
|
+
object = Marshal.load(value)
|
355
|
+
object.memcache_flags = value.memcache_flags
|
356
|
+
object.memcache_cas = value.memcache_cas
|
357
|
+
object
|
358
|
+
rescue Exception => e
|
359
|
+
puts "Memcache read error: #{e.class} #{e.to_s} on key '#{key}' while unmarshalling value: #{value}"
|
360
|
+
nil
|
361
|
+
end
|
362
|
+
|
363
|
+
def server(key)
|
364
|
+
raise ArgumentError, "key too long #{key.inspect}" if key.length > 250
|
365
|
+
return servers.first if servers.length == 1
|
366
|
+
|
367
|
+
hash = (Zlib.crc32(key) >> 16) & 0x7fff
|
368
|
+
servers[hash % servers.length]
|
369
|
+
end
|
370
|
+
|
371
|
+
class Pool
|
372
|
+
attr_reader :fallback
|
373
|
+
|
374
|
+
def initialize
|
375
|
+
@cache_by_scope = {}
|
376
|
+
@cache_by_scope[:default] = Memcache.new(:server => Memcache::LocalServer)
|
377
|
+
@fallback = :default
|
378
|
+
end
|
379
|
+
|
380
|
+
def include?(scope)
|
381
|
+
@cache_by_scope.include?(scope.to_sym)
|
382
|
+
end
|
383
|
+
|
384
|
+
def fallback=(scope)
|
385
|
+
@fallback = scope.to_sym
|
386
|
+
end
|
387
|
+
|
388
|
+
def [](scope)
|
389
|
+
@cache_by_scope[scope.to_sym] || @cache_by_scope[fallback]
|
390
|
+
end
|
391
|
+
|
392
|
+
def []=(scope, cache)
|
393
|
+
@cache_by_scope[scope.to_sym] = cache
|
394
|
+
end
|
395
|
+
|
396
|
+
def reset
|
397
|
+
@cache_by_scope.values.each {|c| c.reset}
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
def self.pool
|
402
|
+
@@cache_pool ||= Pool.new
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
# Add flags and cas
|
407
|
+
class Object
|
408
|
+
attr_accessor :memcache_flags, :memcache_cas
|
409
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{segmented-memcache}
|
8
|
+
s.version = "1.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Justin Balthrop, Alessandro Dal Grande, Fabio Segalla"]
|
12
|
+
s.date = %q{2009-12-17}
|
13
|
+
s.description = %q{Ruby client for memcached supporting advanced protocol features and pluggable architecture.}
|
14
|
+
s.email = %q{lab@develon.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION.yml",
|
25
|
+
"lib/memcache.rb",
|
26
|
+
"lib/memcache/local_server.rb",
|
27
|
+
"lib/memcache/migration.rb",
|
28
|
+
"lib/memcache/null_server.rb",
|
29
|
+
"lib/memcache/pg_server.rb",
|
30
|
+
"lib/memcache/segmented_server.rb",
|
31
|
+
"lib/memcache/server.rb",
|
32
|
+
"segmented-memcache.gemspec",
|
33
|
+
"test/memcache_local_server_test.rb",
|
34
|
+
"test/memcache_null_server_test.rb",
|
35
|
+
"test/memcache_pg_server_test.rb",
|
36
|
+
"test/memcache_segmented_server_test.rb",
|
37
|
+
"test/memcache_server_test.rb",
|
38
|
+
"test/memcache_server_test_helper.rb",
|
39
|
+
"test/memcache_test.rb",
|
40
|
+
"test/test_helper.rb"
|
41
|
+
]
|
42
|
+
s.homepage = %q{http://github.com/develon/memcache}
|
43
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
44
|
+
s.require_paths = ["lib"]
|
45
|
+
s.rubygems_version = %q{1.3.5}
|
46
|
+
s.summary = %q{Advanced ruby memcache client}
|
47
|
+
s.test_files = [
|
48
|
+
"test/memcache_local_server_test.rb",
|
49
|
+
"test/memcache_null_server_test.rb",
|
50
|
+
"test/memcache_pg_server_test.rb",
|
51
|
+
"test/memcache_segmented_server_test.rb",
|
52
|
+
"test/memcache_server_test.rb",
|
53
|
+
"test/memcache_server_test_helper.rb",
|
54
|
+
"test/memcache_test.rb",
|
55
|
+
"test/test_helper.rb"
|
56
|
+
]
|
57
|
+
|
58
|
+
if s.respond_to? :specification_version then
|
59
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
60
|
+
s.specification_version = 3
|
61
|
+
|
62
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
63
|
+
else
|
64
|
+
end
|
65
|
+
else
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|