memcache-client 1.7.0 → 1.7.1
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/FAQ.rdoc +24 -0
- data/History.rdoc +14 -0
- data/README.rdoc +7 -2
- data/lib/continuum_native.rb +41 -0
- data/lib/memcache.rb +40 -69
- data/test/test_mem_cache.rb +4 -3
- metadata +4 -2
data/FAQ.rdoc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
= Memcache-client FAQ
|
2
|
+
|
3
|
+
== Does memcache-client work with Ruby 1.9?
|
4
|
+
|
5
|
+
Yes, Ruby 1.9 is supported. The test suite should pass completely on 1.8.6 and 1.9.1.
|
6
|
+
|
7
|
+
|
8
|
+
== I'm seeing "execution expired" or "time's up!" errors, what's that all about?
|
9
|
+
|
10
|
+
memcache-client 1.6.x+ now has socket operations timed out by default. This is to prevent
|
11
|
+
the Ruby process from hanging if memcached or starling get into a bad state, which has been
|
12
|
+
seen in production by both 37signals and FiveRuns. The default timeout is 0.5 seconds, which
|
13
|
+
should be more than enough time under normal circumstances. It's possible to hit a storm of
|
14
|
+
concurrent events which cause this timer to expire: a large Ruby VM can cause the GC to take
|
15
|
+
a while, while also storing a large (500k-1MB value), for example.
|
16
|
+
|
17
|
+
You can increase the timeout or disable them completely with the following configuration:
|
18
|
+
|
19
|
+
Rails:
|
20
|
+
config.cache_store = :mem_cache_store, 'server1', 'server2', { :timeout => nil } # no timeout
|
21
|
+
|
22
|
+
native:
|
23
|
+
MemCache.new ['server1', 'server2'], { :timeout => 1.0 } # 1 second timeout
|
24
|
+
|
data/History.rdoc
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
= 1.7.1
|
2
|
+
|
3
|
+
* Performance optimizations:
|
4
|
+
* Rely on higher performance operating system socket timeouts for low-level socket
|
5
|
+
read/writes where possible, instead of the (slower) SystemTimer or (slowest,
|
6
|
+
unreliable) Timeout libraries.
|
7
|
+
* the native binary search is back! The recent performance tuning made the binary search
|
8
|
+
a bottleneck again so it had to return. It uses RubyInline to compile the native extension and
|
9
|
+
silently falls back to pure Ruby if anything fails. Make sure you run:
|
10
|
+
`gem install RubyInline` if you want ultimate performance.
|
11
|
+
* the changes make memcache-client 100% faster than 1.7.0 in my performance test on Ruby 1.8.6:
|
12
|
+
15 sec -> 8 sec.
|
13
|
+
* Fix several logging issues.
|
14
|
+
|
1
15
|
= 1.7.0 (2009-03-08)
|
2
16
|
|
3
17
|
* Go through the memcached protocol document and implement any commands not already implemented:
|
data/README.rdoc
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
= memcache-client
|
2
2
|
|
3
|
-
A
|
3
|
+
A ruby library for accessing memcached.
|
4
4
|
|
5
5
|
Source:
|
6
6
|
|
@@ -26,7 +26,8 @@ Or with multiple servers:
|
|
26
26
|
== Tuning memcache-client
|
27
27
|
|
28
28
|
The MemCache.new method takes a number of options which can be useful at times. Please
|
29
|
-
read the source comments there for an overview.
|
29
|
+
read the source comments there for an overview. If you are using Ruby 1.8.x and using
|
30
|
+
multiple memcached servers, you should install the RubyInline gem for ultimate performance.
|
30
31
|
|
31
32
|
|
32
33
|
== Using memcache-client with Rails
|
@@ -43,3 +44,7 @@ Eric Hodel and the seattle.rb crew.
|
|
43
44
|
Email:: mailto:mperham@gmail.com
|
44
45
|
Twitter:: mperham[http://twitter.com/mperham]
|
45
46
|
WWW:: http://mikeperham.com
|
47
|
+
|
48
|
+
If my work on memcache-client is something you support, please take a moment to
|
49
|
+
recommend me at WWR[http://workingwithrails.com/person/10797-mike-perham]. I'm not
|
50
|
+
asking for money, just a electronic "thumbs up".
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Continuum
|
2
|
+
|
3
|
+
class << self
|
4
|
+
|
5
|
+
# Native extension to perform the binary search within the continuum
|
6
|
+
# space. There's a pure ruby version in memcache.rb so this is purely
|
7
|
+
# optional for performance and only necessary if you are using multiple
|
8
|
+
# memcached servers.
|
9
|
+
begin
|
10
|
+
require 'inline'
|
11
|
+
inline do |builder|
|
12
|
+
builder.c <<-EOM
|
13
|
+
int binary_search(VALUE ary, unsigned int r) {
|
14
|
+
int upper = RARRAY_LEN(ary) - 1;
|
15
|
+
int lower = 0;
|
16
|
+
int idx = 0;
|
17
|
+
ID value = rb_intern("value");
|
18
|
+
|
19
|
+
while (lower <= upper) {
|
20
|
+
idx = (lower + upper) / 2;
|
21
|
+
|
22
|
+
VALUE continuumValue = rb_funcall(RARRAY_PTR(ary)[idx], value, 0);
|
23
|
+
unsigned int l = NUM2UINT(continuumValue);
|
24
|
+
if (l == r) {
|
25
|
+
return idx;
|
26
|
+
}
|
27
|
+
else if (l > r) {
|
28
|
+
upper = idx - 1;
|
29
|
+
}
|
30
|
+
else {
|
31
|
+
lower = idx + 1;
|
32
|
+
}
|
33
|
+
}
|
34
|
+
return upper;
|
35
|
+
}
|
36
|
+
EOM
|
37
|
+
end
|
38
|
+
rescue Exception => e
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/memcache.rb
CHANGED
@@ -33,7 +33,7 @@ class MemCache
|
|
33
33
|
##
|
34
34
|
# The version of MemCache you are using.
|
35
35
|
|
36
|
-
VERSION = '1.7.
|
36
|
+
VERSION = '1.7.1'
|
37
37
|
|
38
38
|
##
|
39
39
|
# Default options for the cache object.
|
@@ -223,7 +223,7 @@ class MemCache
|
|
223
223
|
|
224
224
|
def get(key, raw = false)
|
225
225
|
with_server(key) do |server, cache_key|
|
226
|
-
logger.debug { "get #{key} from #{server.inspect}
|
226
|
+
logger.debug { "get #{key} from #{server.inspect}" } if logger
|
227
227
|
value = cache_get server, cache_key
|
228
228
|
return nil if value.nil?
|
229
229
|
value = Marshal.load value unless raw
|
@@ -333,12 +333,11 @@ class MemCache
|
|
333
333
|
with_server(key) do |server, cache_key|
|
334
334
|
|
335
335
|
value = Marshal.dump value unless raw
|
336
|
-
|
337
|
-
logger.debug { "set #{key} to #{server.inspect}: #{data.size}" } if logger
|
336
|
+
logger.debug { "set #{key} to #{server.inspect}: #{value.to_s.size}" } if logger
|
338
337
|
|
339
|
-
raise MemCacheError, "Value too large, memcached can only store 1MB of data per key" if
|
338
|
+
raise MemCacheError, "Value too large, memcached can only store 1MB of data per key" if value.to_s.size > ONE_MB
|
340
339
|
|
341
|
-
command = "set #{cache_key} 0 #{expiry} #{
|
340
|
+
command = "set #{cache_key} 0 #{expiry} #{value.to_s.size}#{noreply}\r\n#{value}\r\n"
|
342
341
|
|
343
342
|
with_socket_management(server) do |socket|
|
344
343
|
socket.write command
|
@@ -380,11 +379,10 @@ class MemCache
|
|
380
379
|
updated = yield value
|
381
380
|
|
382
381
|
with_server(key) do |server, cache_key|
|
383
|
-
logger.debug { "cas #{key} to #{server.inspect}: #{data.size}" } if logger
|
384
382
|
|
385
383
|
value = Marshal.dump updated unless raw
|
386
|
-
|
387
|
-
command = "cas #{cache_key} 0 #{expiry} #{value.size} #{token}#{noreply}\r\n#{value}\r\n"
|
384
|
+
logger.debug { "cas #{key} to #{server.inspect}: #{value.to_s.size}" } if logger
|
385
|
+
command = "cas #{cache_key} 0 #{expiry} #{value.to_s.size} #{token}#{noreply}\r\n#{value}\r\n"
|
388
386
|
|
389
387
|
with_socket_management(server) do |socket|
|
390
388
|
socket.write command
|
@@ -720,7 +718,7 @@ class MemCache
|
|
720
718
|
|
721
719
|
def gets(key, raw = false)
|
722
720
|
with_server(key) do |server, cache_key|
|
723
|
-
logger.debug { "gets #{key} from #{server.inspect}
|
721
|
+
logger.debug { "gets #{key} from #{server.inspect}" } if logger
|
724
722
|
result = with_socket_management(server) do |socket|
|
725
723
|
socket.write "gets #{cache_key}\r\n"
|
726
724
|
keyline = socket.gets # "VALUE <key> <flags> <bytes> <cas token>\r\n"
|
@@ -822,7 +820,7 @@ class MemCache
|
|
822
820
|
|
823
821
|
block.call(socket)
|
824
822
|
|
825
|
-
rescue SocketError, Timeout::Error => err
|
823
|
+
rescue SocketError, Errno::EAGAIN, Timeout::Error => err
|
826
824
|
logger.warn { "Socket failure: #{err.message}" } if logger
|
827
825
|
server.mark_dead(err)
|
828
826
|
handle_error(server, err)
|
@@ -921,13 +919,6 @@ class MemCache
|
|
921
919
|
|
922
920
|
class Server
|
923
921
|
|
924
|
-
##
|
925
|
-
# The amount of time to wait to establish a connection with a memcached
|
926
|
-
# server. If a connection cannot be established within this time limit,
|
927
|
-
# the server will be marked as down.
|
928
|
-
|
929
|
-
CONNECT_TIMEOUT = 0.25
|
930
|
-
|
931
922
|
##
|
932
923
|
# The amount of time to wait before attempting to re-establish a
|
933
924
|
# connection with a server that is marked dead.
|
@@ -1011,14 +1002,11 @@ class MemCache
|
|
1011
1002
|
|
1012
1003
|
# Attempt to connect if not already connected.
|
1013
1004
|
begin
|
1014
|
-
@sock =
|
1015
|
-
|
1016
|
-
if Socket.constants.include? 'TCP_NODELAY' then
|
1017
|
-
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
1018
|
-
end
|
1005
|
+
@sock = connect_to(@host, @port, @timeout)
|
1006
|
+
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
1019
1007
|
@retry = nil
|
1020
1008
|
@status = 'CONNECTED'
|
1021
|
-
rescue SocketError, SystemCallError, IOError
|
1009
|
+
rescue SocketError, SystemCallError, IOError => err
|
1022
1010
|
logger.warn { "Unable to open socket: #{err.class.name}, #{err.message}" } if logger
|
1023
1011
|
mark_dead err
|
1024
1012
|
end
|
@@ -1026,6 +1014,33 @@ class MemCache
|
|
1026
1014
|
return @sock
|
1027
1015
|
end
|
1028
1016
|
|
1017
|
+
def connect_to(host, port, timeout=nil)
|
1018
|
+
addr = Socket.getaddrinfo(host, nil)
|
1019
|
+
sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
1020
|
+
|
1021
|
+
if timeout
|
1022
|
+
secs = Integer(timeout)
|
1023
|
+
usecs = Integer((timeout - secs) * 1_000_000)
|
1024
|
+
optval = [secs, usecs].pack("l_2")
|
1025
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
1026
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
1027
|
+
|
1028
|
+
# Socket timeouts don't work for more complex IO operations
|
1029
|
+
# like gets which lay on top of read. We need to fall back to
|
1030
|
+
# the standard Timeout mechanism.
|
1031
|
+
sock.instance_eval <<-EOR
|
1032
|
+
alias :blocking_gets :gets
|
1033
|
+
def gets
|
1034
|
+
MemCacheTimer.timeout(#{timeout}) do
|
1035
|
+
self.blocking_gets
|
1036
|
+
end
|
1037
|
+
end
|
1038
|
+
EOR
|
1039
|
+
end
|
1040
|
+
sock.connect(Socket.pack_sockaddr_in(port, addr[0][3]))
|
1041
|
+
sock
|
1042
|
+
end
|
1043
|
+
|
1029
1044
|
##
|
1030
1045
|
# Close the connection to the memcached server targeted by this
|
1031
1046
|
# object. The server is not considered dead.
|
@@ -1059,51 +1074,6 @@ class MemCache
|
|
1059
1074
|
|
1060
1075
|
end
|
1061
1076
|
|
1062
|
-
# TCPSocket facade class which implements timeouts.
|
1063
|
-
class TCPTimeoutSocket
|
1064
|
-
|
1065
|
-
def initialize(host, port, timeout)
|
1066
|
-
MemCacheTimer.timeout(MemCache::Server::CONNECT_TIMEOUT) do
|
1067
|
-
@sock = TCPSocket.new(host, port)
|
1068
|
-
@len = timeout
|
1069
|
-
end
|
1070
|
-
end
|
1071
|
-
|
1072
|
-
def write(*args)
|
1073
|
-
MemCacheTimer.timeout(@len) do
|
1074
|
-
@sock.write(*args)
|
1075
|
-
end
|
1076
|
-
end
|
1077
|
-
|
1078
|
-
def gets(*args)
|
1079
|
-
MemCacheTimer.timeout(@len) do
|
1080
|
-
@sock.gets(*args)
|
1081
|
-
end
|
1082
|
-
end
|
1083
|
-
|
1084
|
-
def read(*args)
|
1085
|
-
MemCacheTimer.timeout(@len) do
|
1086
|
-
@sock.read(*args)
|
1087
|
-
end
|
1088
|
-
end
|
1089
|
-
|
1090
|
-
def _socket
|
1091
|
-
@sock
|
1092
|
-
end
|
1093
|
-
|
1094
|
-
def method_missing(meth, *args)
|
1095
|
-
@sock.__send__(meth, *args)
|
1096
|
-
end
|
1097
|
-
|
1098
|
-
def closed?
|
1099
|
-
@sock.closed?
|
1100
|
-
end
|
1101
|
-
|
1102
|
-
def close
|
1103
|
-
@sock.close
|
1104
|
-
end
|
1105
|
-
end
|
1106
|
-
|
1107
1077
|
module Continuum
|
1108
1078
|
POINTS_PER_SERVER = 160 # this is the default in libmemcached
|
1109
1079
|
|
@@ -1142,3 +1112,4 @@ module Continuum
|
|
1142
1112
|
end
|
1143
1113
|
end
|
1144
1114
|
end
|
1115
|
+
require 'continuum_native'
|
data/test/test_mem_cache.rb
CHANGED
@@ -1076,7 +1076,8 @@ class TestMemCache < Test::Unit::TestCase
|
|
1076
1076
|
def test_crazy_multithreaded_access
|
1077
1077
|
requirement(memcached_running?, 'A real memcached server must be running for performance testing') do
|
1078
1078
|
|
1079
|
-
|
1079
|
+
# Use a null logger to verify logging doesn't blow up at runtime
|
1080
|
+
cache = MemCache.new(['localhost:11211', '127.0.0.1:11211'], :logger => Logger.new('/dev/null'))
|
1080
1081
|
cache.flush_all
|
1081
1082
|
workers = []
|
1082
1083
|
|
@@ -1111,9 +1112,9 @@ class TestMemCache < Test::Unit::TestCase
|
|
1111
1112
|
assert cache.decr('c', 5) > 14
|
1112
1113
|
assert_equal 11, cache.get('b')
|
1113
1114
|
d = cache.get('d', true)
|
1114
|
-
assert_match /\Aab
|
1115
|
+
assert_match /\Aab*\Z/, d
|
1115
1116
|
e = cache.get('e', true)
|
1116
|
-
assert_match /\Ay
|
1117
|
+
assert_match /\Ay*x\Z/, e
|
1117
1118
|
end
|
1118
1119
|
end
|
1119
1120
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: memcache-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.7.
|
4
|
+
version: 1.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Hodel
|
@@ -11,7 +11,7 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date: 2009-03-
|
14
|
+
date: 2009-03-28 00:00:00 -05:00
|
15
15
|
default_executable:
|
16
16
|
dependencies: []
|
17
17
|
|
@@ -24,11 +24,13 @@ extensions: []
|
|
24
24
|
extra_rdoc_files: []
|
25
25
|
|
26
26
|
files:
|
27
|
+
- FAQ.rdoc
|
27
28
|
- README.rdoc
|
28
29
|
- LICENSE.txt
|
29
30
|
- History.rdoc
|
30
31
|
- Rakefile
|
31
32
|
- lib/memcache.rb
|
33
|
+
- lib/continuum_native.rb
|
32
34
|
has_rdoc: false
|
33
35
|
homepage: http://github.com/mperham/memcache-client
|
34
36
|
post_install_message:
|