memcache-client 1.7.0 → 1.7.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|