dalli 0.11.0 → 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of dalli might be problematic. Click here for more details.
- data/History.md +6 -0
- data/Performance.md +34 -0
- data/README.md +5 -0
- data/dalli.gemspec +0 -1
- data/lib/dalli.rb +1 -0
- data/lib/dalli/client.rb +35 -20
- data/lib/dalli/server.rb +27 -48
- data/lib/dalli/socket.rb +80 -0
- data/lib/dalli/version.rb +1 -1
- data/test/helper.rb +1 -0
- data/test/test_active_support.rb +4 -8
- data/test/test_network.rb +2 -2
- metadata +4 -4
- data/TODO.md +0 -5
data/History.md
CHANGED
data/Performance.md
CHANGED
@@ -4,6 +4,10 @@ Performance
|
|
4
4
|
Caching is all about performance, so I carefully track Dalli performance to ensure no regressions.
|
5
5
|
Times are from a Unibody MBP 2.4Ghz Core 2 Duo running Snow Leopard.
|
6
6
|
|
7
|
+
You can optionally use kgio to give Dalli a small, 10-20% performance boost: gem install kgio.
|
8
|
+
|
9
|
+
*memcache-client*:
|
10
|
+
|
7
11
|
Testing 1.8.5 with ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
|
8
12
|
user system total real
|
9
13
|
set:plain:memcache-client 2.070000 0.590000 2.660000 ( 2.669744)
|
@@ -14,6 +18,8 @@ Times are from a Unibody MBP 2.4Ghz Core 2 Duo running Snow Leopard.
|
|
14
18
|
missing:ruby:memcache-client 1.900000 0.370000 2.270000 ( 2.282264)
|
15
19
|
mixed:ruby:memcache-client 4.430000 0.950000 5.380000 ( 5.420251)
|
16
20
|
|
21
|
+
*dalli*:
|
22
|
+
|
17
23
|
Testing 0.9.0 with ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
|
18
24
|
user system total real
|
19
25
|
set:plain:dalli 1.610000 0.360000 1.970000 ( 2.032947)
|
@@ -33,3 +39,31 @@ Times are from a Unibody MBP 2.4Ghz Core 2 Duo running Snow Leopard.
|
|
33
39
|
multiget:ruby:dalli 0.870000 0.300000 1.170000 ( 1.295592)
|
34
40
|
missing:ruby:dalli 1.420000 0.370000 1.790000 ( 1.925094)
|
35
41
|
mixed:ruby:dalli 2.800000 0.680000 3.480000 ( 3.820694)
|
42
|
+
|
43
|
+
Testing 0.11.1 with ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
|
44
|
+
Using standard socket IO
|
45
|
+
user system total real
|
46
|
+
set:plain:dalli 1.570000 0.380000 1.950000 ( 1.990252)
|
47
|
+
setq:plain:dalli 0.460000 0.140000 0.600000 ( 0.600362)
|
48
|
+
set:ruby:dalli 1.630000 0.380000 2.010000 ( 2.050056)
|
49
|
+
get:plain:dalli 1.710000 0.410000 2.120000 ( 2.156428)
|
50
|
+
get:ruby:dalli 1.680000 0.410000 2.090000 ( 2.120228)
|
51
|
+
multiget:ruby:dalli 0.860000 0.310000 1.170000 ( 1.182857)
|
52
|
+
missing:ruby:dalli 1.540000 0.390000 1.930000 ( 1.976637)
|
53
|
+
mixed:ruby:dalli 3.300000 0.810000 4.110000 ( 4.166230)
|
54
|
+
mixedq:ruby:dalli 2.530000 0.640000 3.170000 ( 3.214916)
|
55
|
+
incr:ruby:dalli 0.540000 0.140000 0.680000 ( 0.691829)
|
56
|
+
|
57
|
+
Testing 0.11.1 with ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
|
58
|
+
Using kgio socket IO
|
59
|
+
user system total real
|
60
|
+
set:plain:dalli 0.800000 0.370000 1.170000 ( 1.694842)
|
61
|
+
setq:plain:dalli 0.460000 0.150000 0.610000 ( 0.618146)
|
62
|
+
set:ruby:dalli 0.860000 0.370000 1.230000 ( 1.760995)
|
63
|
+
get:plain:dalli 0.910000 0.390000 1.300000 ( 1.860499)
|
64
|
+
get:ruby:dalli 0.900000 0.390000 1.290000 ( 1.809426)
|
65
|
+
multiget:ruby:dalli 0.720000 0.300000 1.020000 ( 1.044887)
|
66
|
+
missing:ruby:dalli 0.770000 0.400000 1.170000 ( 1.649516)
|
67
|
+
mixed:ruby:dalli 1.750000 0.760000 2.510000 ( 3.563845)
|
68
|
+
mixedq:ruby:dalli 1.730000 0.650000 2.380000 ( 2.883827)
|
69
|
+
incr:ruby:dalli 0.280000 0.130000 0.410000 ( 0.603845)
|
data/README.md
CHANGED
@@ -44,6 +44,9 @@ Remember, Dalli **requires** memcached 1.4+. You can check the version with `me
|
|
44
44
|
|
45
45
|
The test suite requires memcached 1.4.3+ with SASL enabled (./configure --enable-sasl). Currently only supports the PLAIN mechanism.
|
46
46
|
|
47
|
+
Dalli has no runtime dependencies and never will. You can optionally install the 'kgio' gem to
|
48
|
+
give Dalli a 10-20% performance boost.
|
49
|
+
|
47
50
|
|
48
51
|
Usage with Rails 3.0
|
49
52
|
---------------------------
|
@@ -127,6 +130,8 @@ If you have a fix you wish to provide, please fork the code, fix in your local p
|
|
127
130
|
Thanks
|
128
131
|
------------
|
129
132
|
|
133
|
+
Eric Wong - for help using his [kgio](http://unicorn.bogomips.org/kgio/index.html) library.
|
134
|
+
|
130
135
|
Brian Mitchell - for his remix-stash project which was helpful when implementing and testing the binary protocol support.
|
131
136
|
|
132
137
|
[NorthScale](http://northscale.com) - for their project sponsorship
|
data/dalli.gemspec
CHANGED
data/lib/dalli.rb
CHANGED
data/lib/dalli/client.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: ascii
|
1
2
|
module Dalli
|
2
3
|
class Client
|
3
4
|
|
@@ -43,9 +44,12 @@ module Dalli
|
|
43
44
|
|
44
45
|
def get(key, options=nil)
|
45
46
|
resp = perform(:get, key)
|
46
|
-
(!resp || resp == 'Not found') ? nil :
|
47
|
+
(!resp || resp == 'Not found') ? nil : resp
|
47
48
|
end
|
48
49
|
|
50
|
+
##
|
51
|
+
# Fetch multiple keys efficiently.
|
52
|
+
# Returns a hash of { 'key' => 'value', 'key2' => 'value1' }
|
49
53
|
def get_multi(*keys)
|
50
54
|
return {} if keys.empty?
|
51
55
|
options = nil
|
@@ -55,7 +59,7 @@ module Dalli
|
|
55
59
|
perform(:getkq, key)
|
56
60
|
end
|
57
61
|
values = ring.servers.inject({}) { |hash, s| hash.merge!(s.request(:noop)); hash }
|
58
|
-
values.inject(values) { |memo, (k,v)| memo[k] =
|
62
|
+
values.inject(values) { |memo, (k,v)| memo[k] = v; memo }
|
59
63
|
end
|
60
64
|
end
|
61
65
|
|
@@ -69,29 +73,46 @@ module Dalli
|
|
69
73
|
val
|
70
74
|
end
|
71
75
|
|
76
|
+
##
|
77
|
+
# compare and swap values using optimistic locking.
|
78
|
+
# Fetch the existing value for key.
|
79
|
+
# If it exists, yield the value to the block.
|
80
|
+
# Add the block's return value as the new value for the key.
|
81
|
+
# Add will fail if someone else changed the value.
|
82
|
+
#
|
83
|
+
# Returns:
|
84
|
+
# - nil if the key did not exist.
|
85
|
+
# - false if the value was changed by someone else.
|
86
|
+
# - true if the value was successfully updated.
|
72
87
|
def cas(key, ttl=nil, options=nil, &block)
|
73
88
|
ttl ||= @options[:expires_in]
|
74
89
|
(value, cas) = perform(:cas, key)
|
75
|
-
value = (!value || value == 'Not found') ? nil :
|
90
|
+
value = (!value || value == 'Not found') ? nil : value
|
76
91
|
if value
|
77
92
|
newvalue = block.call(value)
|
78
|
-
perform(:add, key,
|
93
|
+
perform(:add, key, newvalue, ttl, cas, options)
|
79
94
|
end
|
80
95
|
end
|
81
96
|
|
82
97
|
def set(key, value, ttl=nil, options=nil)
|
83
98
|
ttl ||= @options[:expires_in]
|
84
|
-
perform(:set, key,
|
99
|
+
perform(:set, key, value, ttl, options)
|
85
100
|
end
|
86
|
-
|
101
|
+
|
102
|
+
##
|
103
|
+
# Conditionally add a key/value pair, if the key does not already exist
|
104
|
+
# on the server. Returns true if the operation succeeded.
|
87
105
|
def add(key, value, ttl=nil, options=nil)
|
88
106
|
ttl ||= @options[:expires_in]
|
89
|
-
perform(:add, key,
|
107
|
+
perform(:add, key, value, ttl, 0, options)
|
90
108
|
end
|
91
109
|
|
110
|
+
##
|
111
|
+
# Conditionally add a key/value pair, only if the key already exists
|
112
|
+
# on the server. Returns true if the operation succeeded.
|
92
113
|
def replace(key, value, ttl=nil, options=nil)
|
93
114
|
ttl ||= @options[:expires_in]
|
94
|
-
perform(:replace, key,
|
115
|
+
perform(:replace, key, value, ttl, options)
|
95
116
|
end
|
96
117
|
|
97
118
|
def delete(key)
|
@@ -148,10 +169,16 @@ module Dalli
|
|
148
169
|
perform(:decr, key, amt, ttl, default)
|
149
170
|
end
|
150
171
|
|
172
|
+
##
|
173
|
+
# Collect the stats for each server.
|
174
|
+
# Returns a hash like { 'hostname:port' => { 'stat1' => 'value1', ... }, 'hostname2:port' => { ... } }
|
151
175
|
def stats
|
152
176
|
ring.servers.inject({}) { |memo, s| memo["#{s.hostname}:#{s.port}"] = s.request(:stats); memo }
|
153
177
|
end
|
154
178
|
|
179
|
+
##
|
180
|
+
# Close our connection to each server.
|
181
|
+
# If you perform another operation after this, the connections will be re-established.
|
155
182
|
def close
|
156
183
|
if @ring
|
157
184
|
@ring.servers.map { |s| s.close }
|
@@ -170,18 +197,6 @@ module Dalli
|
|
170
197
|
)
|
171
198
|
end
|
172
199
|
|
173
|
-
def serialize(value, options)
|
174
|
-
value
|
175
|
-
# options && options[:raw] ? value.to_s : ::Marshal.dump(value)
|
176
|
-
end
|
177
|
-
|
178
|
-
def deserialize(value, options)
|
179
|
-
value
|
180
|
-
# options && options[:raw] ? value : ::Marshal.load(value)
|
181
|
-
# rescue TypeError
|
182
|
-
# raise Dalli::DalliError, "Invalid marshalled data in memcached: #{value}"
|
183
|
-
end
|
184
|
-
|
185
200
|
def env_servers
|
186
201
|
ENV['MEMCACHE_SERVERS'] ? ENV['MEMCACHE_SERVERS'].split(',') : nil
|
187
202
|
end
|
data/lib/dalli/server.rb
CHANGED
@@ -91,14 +91,22 @@ module Dalli
|
|
91
91
|
def down!(toss_exception=false)
|
92
92
|
close
|
93
93
|
@down_at = Time.now.to_i
|
94
|
-
@
|
95
|
-
|
94
|
+
@error = $! && $!.class.name
|
95
|
+
@msg = @msg || ($! && $!.message && !$!.message.empty? && $!.message)
|
96
|
+
@trace = @trace || $!.backtrace
|
97
|
+
|
98
|
+
if toss_exception
|
99
|
+
x = Dalli::NetworkError.new("#{self.hostname}:#{self.port} is currently down: #{@error} #{@msg}")
|
100
|
+
x.set_backtrace @trace
|
101
|
+
raise x
|
102
|
+
end
|
96
103
|
nil
|
97
104
|
end
|
98
105
|
|
99
106
|
def up!
|
100
107
|
@down_at = nil
|
101
108
|
@msg = nil
|
109
|
+
@trace = nil
|
102
110
|
end
|
103
111
|
|
104
112
|
def multi?
|
@@ -325,7 +333,21 @@ module Dalli
|
|
325
333
|
end
|
326
334
|
end
|
327
335
|
|
328
|
-
|
336
|
+
def write(bytes, socket=connection)
|
337
|
+
begin
|
338
|
+
socket.write(bytes)
|
339
|
+
rescue SystemCallError
|
340
|
+
down!(true)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def read(count, socket=connection)
|
345
|
+
begin
|
346
|
+
socket.readfull(count)
|
347
|
+
rescue SystemCallError, Timeout::Error, EOFError
|
348
|
+
down!(true)
|
349
|
+
end
|
350
|
+
end
|
329
351
|
|
330
352
|
def connection
|
331
353
|
@sock ||= begin
|
@@ -333,60 +355,17 @@ module Dalli
|
|
333
355
|
raise Dalli::NetworkError, "#{self.hostname}:#{self.port} is currently down: #{@msg}"
|
334
356
|
end
|
335
357
|
|
336
|
-
# All this ugly code to ensure proper Socket connect timeout
|
337
358
|
begin
|
338
|
-
|
339
|
-
sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
340
|
-
begin
|
341
|
-
sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
|
342
|
-
rescue Errno::EINPROGRESS
|
343
|
-
resp = IO.select(nil, [sock], nil, TIMEOUT)
|
344
|
-
begin
|
345
|
-
sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
|
346
|
-
rescue Errno::EISCONN
|
347
|
-
end
|
348
|
-
end
|
359
|
+
sock = KSocket.open(hostname, port)
|
349
360
|
rescue
|
350
361
|
down!(true)
|
351
362
|
end
|
352
|
-
# end ugly code
|
353
|
-
|
354
|
-
sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
355
363
|
sasl_authentication(sock) if Dalli::Server.need_auth?
|
356
364
|
up!
|
357
365
|
sock
|
358
366
|
end
|
359
367
|
end
|
360
368
|
|
361
|
-
def write(bytes)
|
362
|
-
begin
|
363
|
-
connection.write(bytes)
|
364
|
-
rescue SystemCallError
|
365
|
-
down!(true)
|
366
|
-
end
|
367
|
-
end
|
368
|
-
|
369
|
-
def read(count, socket=connection)
|
370
|
-
begin
|
371
|
-
value = ''
|
372
|
-
begin
|
373
|
-
loop do
|
374
|
-
value << socket.sysread(count - value.bytesize)
|
375
|
-
break if value.bytesize == count
|
376
|
-
end
|
377
|
-
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
378
|
-
if IO.select([socket], nil, nil, TIMEOUT)
|
379
|
-
retry
|
380
|
-
else
|
381
|
-
raise Timeout::Error, "IO timeout"
|
382
|
-
end
|
383
|
-
end
|
384
|
-
value
|
385
|
-
rescue SystemCallError, Timeout::Error, EOFError
|
386
|
-
down!(true)
|
387
|
-
end
|
388
|
-
end
|
389
|
-
|
390
369
|
def split(n)
|
391
370
|
[n >> 32, 0xFFFFFFFF & n]
|
392
371
|
end
|
@@ -487,7 +466,7 @@ module Dalli
|
|
487
466
|
|
488
467
|
# negotiate
|
489
468
|
req = [REQUEST, OPCODES[:auth_negotiation], 0, 0, 0, 0, 0, 0, 0].pack(FORMAT[:noop])
|
490
|
-
|
469
|
+
write(req, socket)
|
491
470
|
header = read(24, socket)
|
492
471
|
raise Dalli::NetworkError, 'No response' if !header
|
493
472
|
(extras, type, status, count) = header.unpack(NORMAL_HEADER)
|
data/lib/dalli/socket.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
begin
|
2
|
+
require 'kgio'
|
3
|
+
puts "Using kgio socket IO" if $TESTING
|
4
|
+
|
5
|
+
class Dalli::Server::KSocket < Kgio::Socket
|
6
|
+
TIMEOUT = 0.5
|
7
|
+
|
8
|
+
def wait_readable
|
9
|
+
IO.select([self], nil, nil, TIMEOUT) || raise(Timeout::Error, "IO timeout")
|
10
|
+
end
|
11
|
+
|
12
|
+
def wait_writable
|
13
|
+
IO.select(nil, [self], nil, TIMEOUT) || raise(Timeout::Error, "IO timeout")
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.open(host, port)
|
17
|
+
addr = Socket.pack_sockaddr_in(port, host)
|
18
|
+
sock = start(addr)
|
19
|
+
sock.wait_writable
|
20
|
+
sock
|
21
|
+
end
|
22
|
+
|
23
|
+
alias :write :kgio_write
|
24
|
+
|
25
|
+
def readfull(count)
|
26
|
+
value = ''
|
27
|
+
loop do
|
28
|
+
value << kgio_read!(count - value.bytesize)
|
29
|
+
break if value.bytesize == count
|
30
|
+
end
|
31
|
+
value
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
::Kgio.wait_readable = :wait_readable
|
37
|
+
::Kgio.wait_writable = :wait_writable
|
38
|
+
|
39
|
+
rescue LoadError
|
40
|
+
puts "Using standard socket IO" if $TESTING
|
41
|
+
|
42
|
+
class Dalli::Server::KSocket < Socket
|
43
|
+
TIMEOUT = 0.5
|
44
|
+
|
45
|
+
def self.open(host, port)
|
46
|
+
# All this ugly code to ensure proper Socket connect timeout
|
47
|
+
addr = Socket.getaddrinfo(host, nil)
|
48
|
+
sock = new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
49
|
+
begin
|
50
|
+
sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
|
51
|
+
rescue Errno::EINPROGRESS
|
52
|
+
resp = IO.select(nil, [sock], nil, TIMEOUT)
|
53
|
+
begin
|
54
|
+
sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
|
55
|
+
rescue Errno::EISCONN
|
56
|
+
end
|
57
|
+
end
|
58
|
+
sock
|
59
|
+
end
|
60
|
+
|
61
|
+
def readfull(count)
|
62
|
+
value = ''
|
63
|
+
begin
|
64
|
+
loop do
|
65
|
+
value << sysread(count - value.bytesize)
|
66
|
+
break if value.bytesize == count
|
67
|
+
end
|
68
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
69
|
+
if IO.select([self], nil, nil, TIMEOUT)
|
70
|
+
retry
|
71
|
+
else
|
72
|
+
raise Timeout::Error, "IO timeout"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
value
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
data/lib/dalli/version.rb
CHANGED
data/test/helper.rb
CHANGED
data/test/test_active_support.rb
CHANGED
@@ -72,7 +72,7 @@ class TestActiveSupport < Test::Unit::TestCase
|
|
72
72
|
connect
|
73
73
|
@mc.write("abc", 5, :raw => true)
|
74
74
|
@mc.write("cba", 5, :raw => true)
|
75
|
-
if RAILS_VERSION
|
75
|
+
if RAILS_VERSION =~ /2\.3/
|
76
76
|
assert_raise ArgumentError do
|
77
77
|
@mc.read_multi("abc", "cba")
|
78
78
|
end
|
@@ -82,13 +82,9 @@ class TestActiveSupport < Test::Unit::TestCase
|
|
82
82
|
|
83
83
|
@dalli.write("abc", 5, :raw => true)
|
84
84
|
@dalli.write("cba", 5, :raw => true)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
89
|
-
else
|
90
|
-
assert_equal({'abc' => '5', 'cba' => '5' }, @dalli.read_multi("abc", "cba"))
|
91
|
-
end
|
85
|
+
# XXX: API difference between m-c and dalli. Dalli is smarter about
|
86
|
+
# what it needs to unmarshal.
|
87
|
+
assert_equal({'abc' => '5', 'cba' => '5' }, @dalli.read_multi("abc", "cba"))
|
92
88
|
end
|
93
89
|
end
|
94
90
|
end
|
data/test/test_network.rb
CHANGED
@@ -18,7 +18,7 @@ class TestNetwork < Test::Unit::TestCase
|
|
18
18
|
|
19
19
|
should 'handle connection reset' do
|
20
20
|
memcached_mock(lambda {|sock| sock.close }) do
|
21
|
-
assert_error Dalli::NetworkError, /Connection reset|
|
21
|
+
assert_error Dalli::NetworkError, /Connection reset|EOFError/ do
|
22
22
|
dc = Dalli::Client.new('localhost:19123')
|
23
23
|
dc.get('abc')
|
24
24
|
end
|
@@ -27,7 +27,7 @@ class TestNetwork < Test::Unit::TestCase
|
|
27
27
|
|
28
28
|
should 'handle malformed response' do
|
29
29
|
memcached_mock(lambda {|sock| sock.write('123') }) do
|
30
|
-
assert_error Dalli::NetworkError, /
|
30
|
+
assert_error Dalli::NetworkError, /EOFError/ do
|
31
31
|
dc = Dalli::Client.new('localhost:19123')
|
32
32
|
dc.get('abc')
|
33
33
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 11
|
8
|
-
-
|
9
|
-
version: 0.11.
|
8
|
+
- 1
|
9
|
+
version: 0.11.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Mike Perham
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-11-
|
17
|
+
date: 2010-11-14 00:00:00 -08:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -93,12 +93,12 @@ files:
|
|
93
93
|
- lib/dalli/sasl/base.rb
|
94
94
|
- lib/dalli/sasl/plain.rb
|
95
95
|
- lib/dalli/server.rb
|
96
|
+
- lib/dalli/socket.rb
|
96
97
|
- lib/dalli/version.rb
|
97
98
|
- lib/dalli.rb
|
98
99
|
- LICENSE
|
99
100
|
- README.md
|
100
101
|
- History.md
|
101
|
-
- TODO.md
|
102
102
|
- Rakefile
|
103
103
|
- Gemfile
|
104
104
|
- dalli.gemspec
|