Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Optimization: use raw socket timeouts where possible.
  • Loading branch information
mperham committed Mar 16, 2009
1 parent cd4b998 commit 9f5201b
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 61 deletions.
6 changes: 6 additions & 0 deletions History.rdoc
@@ -1,3 +1,9 @@
= 1.7.1

* Performance optimization. Rely on higher performance operating system socket timeouts for
low-level socket read/writes where possible, instead of the (slower) SystemTimer or (slowest,
unreliable) Timeout libraries.

= 1.7.0 (2009-03-08)

* Go through the memcached protocol document and implement any commands not already implemented:
Expand Down
92 changes: 32 additions & 60 deletions lib/memcache.rb
Expand Up @@ -33,7 +33,7 @@ class MemCache
##
# The version of MemCache you are using.

VERSION = '1.7.0'
VERSION = '1.7.1'

##
# Default options for the cache object.
Expand Down Expand Up @@ -822,7 +822,7 @@ def with_socket_management(server, &block)

block.call(socket)

rescue SocketError, Timeout::Error => err
rescue SocketError, Errno::EAGAIN, Timeout::Error => err
logger.warn { "Socket failure: #{err.message}" } if logger
server.mark_dead(err)
handle_error(server, err)
Expand Down Expand Up @@ -921,13 +921,6 @@ def check_multithread_status!

class Server

##
# The amount of time to wait to establish a connection with a memcached
# server. If a connection cannot be established within this time limit,
# the server will be marked as down.

CONNECT_TIMEOUT = 0.25

##
# The amount of time to wait before attempting to re-establish a
# connection with a server that is marked dead.
Expand Down Expand Up @@ -1011,21 +1004,45 @@ def socket

# Attempt to connect if not already connected.
begin
@sock = @timeout ? TCPTimeoutSocket.new(@host, @port, @timeout) : TCPSocket.new(@host, @port)

if Socket.constants.include? 'TCP_NODELAY' then
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
end
@sock = connect_to(@host, @port, @timeout)
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
@retry = nil
@status = 'CONNECTED'
rescue SocketError, SystemCallError, IOError, Timeout::Error => err
rescue SocketError, SystemCallError, IOError => err
logger.warn { "Unable to open socket: #{err.class.name}, #{err.message}" } if logger
mark_dead err
end

return @sock
end

def connect_to(host, port, timeout=nil)
addr = Socket.getaddrinfo(host, nil)
sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)

if timeout
secs = Integer(timeout)
usecs = Integer((timeout - secs) * 1_000_000)
optval = [secs, usecs].pack("l_2")
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval

# Socket timeouts don't work for more complex IO operations
# like gets which lay on top of read. We need to fall back to
# the standard Timeout mechanism.
sock.instance_eval <<-EOR
alias :blocking_gets :gets
def gets
MemCacheTimer.timeout(#{timeout}) do
self.blocking_gets
end
end
EOR
end
sock.connect(Socket.pack_sockaddr_in(port, addr[0][3]))
sock
end

##
# Close the connection to the memcached server targeted by this
# object. The server is not considered dead.
Expand Down Expand Up @@ -1059,51 +1076,6 @@ class MemCacheError < RuntimeError; end

end

# TCPSocket facade class which implements timeouts.
class TCPTimeoutSocket

def initialize(host, port, timeout)
MemCacheTimer.timeout(MemCache::Server::CONNECT_TIMEOUT) do
@sock = TCPSocket.new(host, port)
@len = timeout
end
end

def write(*args)
MemCacheTimer.timeout(@len) do
@sock.write(*args)
end
end

def gets(*args)
MemCacheTimer.timeout(@len) do
@sock.gets(*args)
end
end

def read(*args)
MemCacheTimer.timeout(@len) do
@sock.read(*args)
end
end

def _socket
@sock
end

def method_missing(meth, *args)
@sock.__send__(meth, *args)
end

def closed?
@sock.closed?
end

def close
@sock.close
end
end

module Continuum
POINTS_PER_SERVER = 160 # this is the default in libmemcached

Expand Down
81 changes: 81 additions & 0 deletions performance.txt
@@ -0,0 +1,81 @@
== 1.5.0, 1.8.6 (default in Rails 2.2 and lower)

user system total real
set:plain:memcache-client 41.550000 0.590000 42.140000 ( 43.740685)
set:ruby:memcache-client 41.540000 0.590000 42.130000 ( 43.733796)
get:plain:memcache-client 41.920000 0.610000 42.530000 ( 44.031005)
get:ruby:memcache-client 41.940000 0.600000 42.540000 ( 44.082447)
multiget:ruby:memcache-client 46.120000 0.440000 46.560000 ( 47.354041)
missing:ruby:memcache-client 41.490000 0.580000 42.070000 ( 43.610837)
mixed:ruby:memcache-client 83.820000 1.190000 85.010000 ( 88.117077)


== 1.7.0, timeout, 1.8.6 (closest to default in Rails 2.3)
user system total real
set:plain:memcache-client 4.320000 2.280000 6.600000 ( 7.102900)
set:ruby:memcache-client 4.400000 2.300000 6.700000 ( 6.856992)
get:plain:memcache-client 9.890000 6.830000 16.720000 ( 16.984208)
get:ruby:memcache-client 10.040000 6.890000 16.930000 ( 17.141128)
multiget:ruby:memcache-client 5.350000 4.110000 9.460000 ( 9.542898)
missing:ruby:memcache-client 4.710000 3.180000 7.890000 ( 8.030969)
mixed:ruby:memcache-client 14.540000 9.200000 23.740000 ( 24.121824)

== 1.7.0, timeout, system_timer, 1.8.6
user system total real
set:plain:memcache-client 3.840000 0.640000 4.480000 ( 4.643790)
set:ruby:memcache-client 3.930000 0.650000 4.580000 ( 4.731868)
get:plain:memcache-client 8.320000 1.290000 9.610000 ( 9.903877)
get:ruby:memcache-client 8.460000 1.310000 9.770000 ( 9.986694)
multiget:ruby:memcache-client 4.250000 0.560000 4.810000 ( 4.935326)
missing:ruby:memcache-client 3.840000 0.640000 4.480000 ( 4.569696)
mixed:ruby:memcache-client 12.400000 1.960000 14.360000 ( 14.857924)

== 1.7.0, timeout, 1.9.1
user system total real
set:plain:memcache-client 2.130000 2.150000 4.280000 ( 3.774238)
set:ruby:memcache-client 2.230000 2.230000 4.460000 ( 3.883686)
get:plain:memcache-client 4.030000 4.250000 8.280000 ( 6.702740)
get:ruby:memcache-client 4.090000 4.220000 8.310000 ( 6.749134)
multiget:ruby:memcache-client 1.960000 1.840000 3.800000 ( 3.089448)
missing:ruby:memcache-client 2.110000 2.210000 4.320000 ( 3.659019)
mixed:ruby:memcache-client 6.400000 6.560000 12.960000 ( 11.116317)

== 1.7.0, no timeout, 1.9.1
user system total real
set:plain:memcache-client 0.560000 0.320000 0.880000 ( 1.849380)
set:ruby:memcache-client 0.630000 0.320000 0.950000 ( 1.968208)
get:plain:memcache-client 0.640000 0.330000 0.970000 ( 1.962473)
get:ruby:memcache-client 0.690000 0.320000 1.010000 ( 2.002295)
multiget:ruby:memcache-client 0.460000 0.110000 0.570000 ( 0.885827)
missing:ruby:memcache-client 0.530000 0.320000 0.850000 ( 1.721371)
mixed:ruby:memcache-client 1.340000 0.660000 2.000000 ( 3.973213)

== 1.7.0, no timeout, 1.8.6
user system total real
set:plain:memcache-client 1.220000 0.310000 1.530000 ( 2.763310)
set:ruby:memcache-client 1.270000 0.300000 1.570000 ( 2.806251)
get:plain:memcache-client 1.400000 0.300000 1.700000 ( 2.944343)
get:ruby:memcache-client 1.450000 0.310000 1.760000 ( 2.997234)
multiget:ruby:memcache-client 1.120000 0.110000 1.230000 ( 1.665716)
missing:ruby:memcache-client 1.160000 0.300000 1.460000 ( 2.683376)
mixed:ruby:memcache-client 2.760000 0.610000 3.370000 ( 5.851047)

== 1.7.1, timeout, 1.8.6, raw + gets SystemTimer
user system total real
set:plain:memcache-client 2.670000 0.510000 3.180000 ( 3.489509)
set:ruby:memcache-client 2.810000 0.530000 3.340000 ( 3.675955)
get:plain:memcache-client 4.380000 0.720000 5.100000 ( 5.400587)
get:ruby:memcache-client 4.490000 0.730000 5.220000 ( 5.477543)
multiget:ruby:memcache-client 2.570000 0.310000 2.880000 ( 3.034944)
missing:ruby:memcache-client 2.800000 0.530000 3.330000 ( 3.547073)
mixed:ruby:memcache-client 7.460000 1.250000 8.710000 ( 9.272177)

== 1.7.1, timeout, 1.9.1, raw + gets Timeout
user system total real
set:plain:memcache-client 1.370000 1.300000 2.670000 ( 2.708669)
set:ruby:memcache-client 1.400000 1.240000 2.640000 ( 2.713737)
get:plain:memcache-client 2.070000 2.020000 4.090000 ( 3.950879)
get:ruby:memcache-client 2.160000 2.090000 4.250000 ( 3.924613)
multiget:ruby:memcache-client 1.080000 0.820000 1.900000 ( 1.744107)
missing:ruby:memcache-client 1.330000 1.270000 2.600000 ( 2.547597)
mixed:ruby:memcache-client 3.540000 3.270000 6.810000 ( 6.735349)
2 changes: 1 addition & 1 deletion test/test_benchmark.rb
@@ -1,5 +1,5 @@
HERE = File.dirname(__FILE__)
$LOAD_PATH << "#{HERE}/../lib/"
$LOAD_PATH.unshift "#{HERE}/../lib"
#$LOAD_PATH << "/Library/Ruby/Gems/1.8/gems/activesupport-2.2.2/lib/active_support/vendor/memcache-client-1.5.1"

require 'benchmark'
Expand Down

0 comments on commit 9f5201b

Please sign in to comment.