memcache 1.2.0 → 1.2.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/.gitignore CHANGED
@@ -1 +1,12 @@
1
- pkg
1
+ pkg
2
+ *.bundle
3
+ *.so
4
+ *.o
5
+ Makefile
6
+ mkmf.log
7
+ *.gem
8
+ *.swp
9
+ ext/bin
10
+ ext/lib
11
+ ext/share
12
+ ext/include
data/README.rdoc CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  This is the Geni memcached client. It started out as a fork of fiveruns/memcache-client,
4
4
  which was a fork of seattle.rb's memcache-client, but over time, our client has diverged,
5
- and I've rewritten most of the code. Of course, a lot of credit is due to those whose code
6
- served as a starting point for this code.
5
+ and I've rewritten the majority of the code. Of course, a lot of credit is due to those
6
+ whose code served as a starting point for this code.
7
7
 
8
8
  == Usage
9
9
 
@@ -16,11 +16,11 @@ served as a starting point for this code.
16
16
  cache['things']
17
17
  => {:foo => "1", :bar => [1,2,3]}
18
18
 
19
- == How is this different from memcache-client?
19
+ == How is this different from other memcache clients?
20
20
 
21
- Like memcache-client, _memcache_ (shown in italics when I am referring to this
22
- library) is a memcached client, but it differs significantly from memcache-client in
23
- several important ways.
21
+ Like fiveruns/memcache-client and fauna/memcached, _memcache_ (shown in italics when I am
22
+ referring to this library) is a memcached client, but it differs significantly from
23
+ these clients in several important ways.
24
24
 
25
25
  === Interface
26
26
 
@@ -80,8 +80,8 @@ changes:
80
80
  cache.add('foo', 0)
81
81
  cache.get('foo')
82
82
  => 1
83
-
84
- cache.replace('foo', 2)
83
+
84
+ cache.replace('foo', 2)
85
85
  cache.get('foo')
86
86
  => 2
87
87
 
@@ -111,13 +111,13 @@ changes:
111
111
 
112
112
  The underlying architechture of _memcache_ is more modular than memcache-client.
113
113
  A given +Memcache+ instance has a group of servers, just like before, but much more of the
114
- functionality in encapsulated inside the <tt>Memcache::Server</tt> object. Really, a +Server+
114
+ functionality is encapsulated inside the <tt>Memcache::Server</tt> object. Really, a +Server+
115
115
  object is a thin wrapper around an remote memcached server that takes care of the socket
116
116
  and protocol details along with basic error handling. The +Memcache+ class handles the
117
117
  partitioning algorithm, marshaling of ruby objects and various higher-level methods.
118
118
 
119
119
  By encapsulating the protocol inside the +Server+ object, it becomes very easy to plug-in
120
- alternate server implementations. Right now, there are two basic, alternate servers:
120
+ alternate backend server implementations. Right now, there are three basic, alternate servers:
121
121
 
122
122
  [+LocalServer+] This is an in-process server for storing keys and values in local
123
123
  memory. It is good for testing, when you don't want to spin up an instance
@@ -131,6 +131,9 @@ alternate server implementations. Right now, there are two basic, alternate serv
131
131
  are very large. It can also be used in a multi-level cache setup with
132
132
  <tt>Memcache::Server</tt> to provide persistence without sacrificing speed.
133
133
 
134
+ [+NativeServer+] This implementation uses native bindings to libmemcached. It is described
135
+ in more detail in the "Native Bindings" section below.
136
+
134
137
  === Very Large Values
135
138
 
136
139
  Memcached limits the size of values to 1MB. This is done to reduce memory usage, but it
@@ -170,10 +173,61 @@ finally marking a server as dead and raising an exception. We will not attempt t
170
173
  from a dead server for 5 seconds, but a write will always attempt to revive a dead server
171
174
  by attempting to connect.
172
175
 
176
+ === Keys, Namespaces, and Prefixes
177
+
178
+ Unlike the other ruby memcache clients, keys in _memcache_ can contain spaces. This is
179
+ possible because the backend transparently enscapes all space characters, and is
180
+ especially important if you are using method_cache[http://github.com/ninjudd/method_cache]
181
+ or record_cache[http://github.com/ninjudd/record_cache]. <tt>Memcache::Server</tt> implements
182
+ this escaping using gsub and it adds a slight performance penalty when escaping is
183
+ necessary. +NativeServer+ implements this escaping directly in C, and the performance
184
+ overhead is negligible.
185
+
186
+ You can also partition your keys into different namespaces for convenience. This is done
187
+ by prefixing all keys in the backend server with "namespace:". However, the hash keys
188
+ returned by multi gets do not contain the prefix. In this way, the namespace can be
189
+ totally transparent to your code. You can also determine whether the prefix is used for
190
+ hashing with the following option:
191
+
192
+ [+hash_with_prefix+] Determines whether the prefix/namespace is used when hashing keys to
193
+ determine which server to use. Defaults to true.
194
+
195
+ == Native Bindings
196
+
197
+ The <tt>Memcache::NativeServer</tt> backend provides native bindings to libmecached. This is
198
+ significantly faster than using <tt>Memcache::Server</tt> as demonstrated by runnning
199
+ bench/benchmark.rb. NativeServer encapsulates a set of remote servers and allows you to
200
+ use the various hashing methods in libmemcached.
201
+
202
+ You can use native bindings either by passing +NativeServer+ objects to +Memcache+, or you
203
+ can use the +native+ option. Native bindings are compatible with segmented values through
204
+ the +SegmentedNativeServer+ object or by combining the +native+ option with
205
+ +segment_large_values+.
206
+
207
+ server = Memcache::NativeServer.new(:servers => ['localhost:11211', 'localhost:11212'])
208
+ cache = Memcache.new(:server => server)
209
+
210
+ cache = Memcache.new(:servers => ['localhost:11211', 'localhost:11212'], :native => true)
211
+
212
+ NativeServer also accepts a few other options:
213
+
214
+ [+hash+] The libmemcached hashing method. See http://docs.tangent.org/libmemcached/index.html
215
+ for more detail. One of:
216
+
217
+ <tt>:default :md5 :crc :fnv1_64 :fnv1a_64 :fnv1_32 :fnv1a_32 :jenkins
218
+ :hsieh :murmur</tt>.
219
+
220
+ NOTE: Even though there is a libmemcached method named <tt>:default</tt> (which
221
+ is actually Jenkins's one-at-a-time hash), the default hashing method if you
222
+ don't specify one is <tt>:crc</tt>.
223
+
224
+ [+binary+] A boolean value specifying whether to use memcached's binary protocol instead
225
+ of the default ascii protocol. This is slightly slower, but should allow you to use unicode keys.
226
+
173
227
  == Installation
174
228
 
175
229
  $ sudo gem install memcache --source http://gemcutter.org
176
230
 
177
231
  == License:
178
232
 
179
- Copyright (c) 2009 Justin Balthrop, Geni.com; Published under The MIT License, see LICENSE
233
+ Copyright (c) 2010 Justin Balthrop, Geni.com; Published under The MIT License, see LICENSE
data/Rakefile CHANGED
@@ -41,6 +41,10 @@ task :test => :check_dependencies
41
41
 
42
42
  task :default => :test
43
43
 
44
+ task :clean do
45
+ `rm -rf ext/lib ext/bin ext/share ext/include`
46
+ end
47
+
44
48
  require 'rake/rdoctask'
45
49
  Rake::RDocTask.new do |rdoc|
46
50
  if File.exist?('VERSION')
@@ -54,3 +58,13 @@ Rake::RDocTask.new do |rdoc|
54
58
  rdoc.rdoc_files.include('README*')
55
59
  rdoc.rdoc_files.include('lib/**/*.rb')
56
60
  end
61
+
62
+ namespace :test do
63
+ Rake::TestTask.new(:native) do |t|
64
+ `cd ext && make && cp native_server.bundle native_server.o ../lib/memcache/`
65
+ t.libs << 'test'
66
+ t.pattern = 'test/memcache_native_server_test.rb'
67
+ t.verbose
68
+ end
69
+ end
70
+
data/VERSION.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
2
  :build:
3
- :patch: 0
4
- :major: 1
5
3
  :minor: 2
4
+ :patch: 1
5
+ :major: 1
@@ -0,0 +1,126 @@
1
+ require 'rubygems'
2
+ require 'benchmark'
3
+ require 'pp'
4
+
5
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
6
+ require 'memcache'
7
+
8
+ def array(*args)
9
+ arr = []
10
+ args.each do |arg|
11
+ if arg.kind_of?(Enumerable)
12
+ arr.concat(arg.to_a)
13
+ else
14
+ arr << arg
15
+ end
16
+ end
17
+ arr
18
+ end
19
+
20
+ CHARS = array('a'..'z', 'A'..'Z', '0'..'9', '_', '+', '-')
21
+ def rand_string(len)
22
+ str = ''
23
+ len.times do
24
+ str << pick_rand(CHARS)
25
+ end
26
+ str
27
+ end
28
+
29
+ def pick_rand(items)
30
+ i = rand(items.size)
31
+ items[i]
32
+ end
33
+
34
+ def pick_mod(items, i)
35
+ i = i % items.size
36
+ items[i]
37
+ end
38
+
39
+ class MemcacheBench
40
+ attr_reader :num_items, :key_length, :val_length, :keys, :vals, :n
41
+
42
+ def initialize(opts = {})
43
+ @n = opts[:n] || 100_000
44
+ @num_items = opts[:num_items] || 5000
45
+ @key_length = array(opts[:key_length] || 10)
46
+ @val_length = array(opts[:val_length] || 100)
47
+
48
+ puts "N = #{@n}"
49
+ puts "key_length: #{@key_length.join(' or ')}"
50
+ puts "val_length: #{@val_length.join(' or ')}"
51
+ puts "Generating #{@num_items} random keys and values..."
52
+ @keys = []
53
+ @vals = []
54
+ @num_items.times do
55
+ @keys << rand_string( pick_rand(@key_length) )
56
+ @vals << rand_string( pick_rand(@val_length) )
57
+ end
58
+
59
+ Benchmark.bm(36) do |x|
60
+ @bench = x
61
+ end
62
+ end
63
+
64
+ def bench(name, nkeys = 1, &block)
65
+ if nkeys > 1
66
+ keyseq = keys + keys
67
+ block = lambda do |i|
68
+ i = i % keys.size
69
+ yield(keyseq[i, nkeys])
70
+ end
71
+ name = "#{name}-#{nkeys}"
72
+ elsif block.arity == 1
73
+ block = lambda {|i| yield(pick_mod(keys, i))}
74
+ else
75
+ block = lambda {|i| yield(pick_mod(keys, i), pick_mod(vals, i))}
76
+ end
77
+
78
+ @bench.report(name) do
79
+ (n/nkeys).times do |i|
80
+ block.call(i)
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ def init_servers(*ports)
87
+ servers = []
88
+ ports.each do |port|
89
+ system("memcached -p #{port} -U 0 -d -P /tmp/memcached_#{port}.pid")
90
+ servers << "127.0.0.1:#{port}"
91
+ sleep 0.3
92
+ end
93
+ memcache = yield(servers)
94
+ memcache.flush_all
95
+ memcache
96
+ end
97
+
98
+ def ___
99
+ puts('=' * 81)
100
+ end
101
+
102
+ puts `uname -a`
103
+ puts "Ruby #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}"
104
+
105
+ ns = 'namespace'
106
+ memcache = init_servers(10000,10001) {|s| Memcache.new(:servers => s, :namespace => ns)}
107
+ native = init_servers(10002,10003) {|s| Memcache.new(:servers => s, :namespace => ns, :native => true)}
108
+ native_nowrap = init_servers(10004,10005) {|s| Memcache::NativeServer.new(:servers => s, :prefix => "#{ns}:")}
109
+
110
+ b = MemcacheBench.new(:num_items => 5000, :n => 100_000, :key_length => 20, :val_length => 100)
111
+
112
+ 2.times do
113
+ ___
114
+ b.bench( 'set:native-nowrap' ) {|key, val| native_nowrap.set(key, val) }
115
+ b.bench( 'get:native-nowrap' ) {|key | native_nowrap.get(key) }
116
+ b.bench( 'get:native-nowrap', 100 ) {|keys | native_nowrap.get(keys) }
117
+ ___
118
+ b.bench( 'set:native' ) {|key, val| native.set(key, val, :raw => true) }
119
+ b.bench( 'get:native' ) {|key | native.get(key, :raw => true) }
120
+ b.bench( 'get:native', 100 ) {|keys | native.get(keys, :raw => true) }
121
+ ___
122
+ b.bench( 'set:ruby' ) {|key, val| memcache.set(key, val, :raw => true) }
123
+ b.bench( 'get:ruby' ) {|key | memcache.get(key, :raw => true) }
124
+ b.bench( 'get:ruby', 100 ) {|keys | memcache.get(keys, :raw => true) }
125
+ ___
126
+ end
data/ext/extconf.rb CHANGED
@@ -1,3 +1,52 @@
1
1
  require 'mkmf'
2
- have_library('memcached')
3
- create_makefile('native_server')
2
+ require 'rbconfig'
3
+
4
+ # Code taken from Evan's Weaver memcached library: http://github.com/fauna/memcached
5
+
6
+ HERE = File.expand_path(File.dirname(__FILE__))
7
+ BUNDLE = Dir.glob("libmemcached-*.tar.gz").first
8
+ BUNDLE_PATH = BUNDLE.sub(".tar.gz", "")
9
+
10
+ $CXXFLAGS = " -std=gnu++98"
11
+
12
+ if !ENV["EXTERNAL_LIB"]
13
+ $includes = " -I#{HERE}/include"
14
+ $libraries = " -L#{HERE}/lib"
15
+ $CFLAGS = "#{$includes} #{$libraries} #{$CFLAGS}"
16
+ $LDFLAGS = "#{$libraries} #{$LDFLAGS}"
17
+ $LIBPATH = ["#{HERE}/lib"]
18
+ $DEFLIBPATH = []
19
+
20
+ Dir.chdir(HERE) do
21
+ if false and File.exist?("lib")
22
+ puts "Libmemcached already built; run 'rake clean' first if you need to rebuild."
23
+ else
24
+ puts "Building libmemcached."
25
+ puts(cmd = "tar xzf #{BUNDLE} 2>&1")
26
+ raise "'#{cmd}' failed" unless system(cmd)
27
+
28
+ Dir.chdir(BUNDLE_PATH) do
29
+ puts(cmd = "env CFLAGS='-fPIC' ./configure --prefix=#{HERE} --without-memcached --disable-shared --disable-utils --disable-dependency-tracking #{$EXTRA_CONF} 2>&1")
30
+ raise "'#{cmd}' failed" unless system(cmd)
31
+
32
+ puts(cmd = "make CXXFLAGS='#{$CXXFLAGS}' || true 2>&1")
33
+ raise "'#{cmd}' failed" unless system(cmd)
34
+
35
+ puts(cmd = "make install || true 2>&1")
36
+ raise "'#{cmd}' failed" unless system(cmd)
37
+ end
38
+
39
+ system("rm -rf #{BUNDLE_PATH}") unless ENV['DEBUG'] or ENV['DEV']
40
+ end
41
+ end
42
+
43
+ # Absolutely prevent the linker from picking up any other libmemcached
44
+ Dir.chdir("#{HERE}/lib") do
45
+ system('cp -f libmemcached.a libmemcached_gem.a')
46
+ system('cp -f libmemcached.la libmemcached_gem.la')
47
+ end
48
+
49
+ $LIBS << " -lmemcached_gem"
50
+ end
51
+
52
+ create_makefile('memcache/native_server')
Binary file