dalli 0.9.0 → 0.9.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/Gemfile CHANGED
@@ -1,3 +1,7 @@
1
1
  source :rubygems
2
2
 
3
- gemspec
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'simplecov-html'
7
+ end
@@ -0,0 +1,14 @@
1
+ Dalli Changelog
2
+ =====================
3
+
4
+ 0.9.1
5
+ ----
6
+
7
+ - Add fetch and cas operations (mperham)
8
+ - Add incr and decr operations (mperham)
9
+ - Initial support for SASL authentication via the MEMCACHE_{USERNAME,PASSWORD} environment variables, needed for Heroku (mperham)
10
+
11
+ 0.9.0
12
+ -----
13
+
14
+ - Initial gem release.
@@ -2,23 +2,24 @@ Performance
2
2
  ====================
3
3
 
4
4
  Caching is all about performance, so I carefully track Dalli performance to ensure no regressions.
5
+ Times are from a Unibody MBP 2.4Ghz Core 2 Duo running Snow Leopard.
5
6
 
6
- Testing 1.8.5 with ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
7
- user system total real
8
- set:plain:memcache-client 1.600000 0.390000 1.990000 ( 2.020491)
9
- set:ruby:memcache-client 1.680000 0.390000 2.070000 ( 2.108217)
10
- get:plain:memcache-client 1.740000 0.250000 1.990000 ( 2.018315)
11
- get:ruby:memcache-client 1.790000 0.250000 2.040000 ( 2.065529)
12
- multiget:ruby:memcache-client 0.800000 0.090000 0.890000 ( 0.914336)
13
- missing:ruby:memcache-client 1.480000 0.250000 1.730000 ( 1.761555)
14
- mixed:ruby:memcache-client 3.470000 0.640000 4.110000 ( 4.195236)
7
+ Testing 1.8.5 with ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
8
+ user system total real
9
+ set:plain:memcache-client 2.070000 0.590000 2.660000 ( 2.669744)
10
+ set:ruby:memcache-client 2.150000 0.570000 2.720000 ( 2.734616)
11
+ get:plain:memcache-client 2.240000 0.400000 2.640000 ( 2.675747)
12
+ get:ruby:memcache-client 2.290000 0.380000 2.670000 ( 2.682108)
13
+ multiget:ruby:memcache-client 1.030000 0.140000 1.170000 ( 1.174503)
14
+ missing:ruby:memcache-client 1.900000 0.370000 2.270000 ( 2.282264)
15
+ mixed:ruby:memcache-client 4.430000 0.950000 5.380000 ( 5.420251)
15
16
 
16
- Testing 0.1.0 with ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
17
- user system total real
18
- set:plain:dalli 0.430000 0.180000 0.610000 ( 1.051395)
19
- set:ruby:dalli 0.490000 0.180000 0.670000 ( 1.124848)
20
- get:plain:dalli 0.490000 0.210000 0.700000 ( 1.141887)
21
- get:ruby:dalli 0.540000 0.200000 0.740000 ( 1.188353)
22
- multiget:ruby:dalli 0.510000 0.200000 0.710000 ( 0.772860)
23
- missing:ruby:dalli 0.450000 0.210000 0.660000 ( 1.070748)
24
- mixed:ruby:dalli 1.050000 0.390000 1.440000 ( 2.304933)
17
+ Testing 0.9.0 with ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
18
+ user system total real
19
+ set:plain:dalli 1.610000 0.360000 1.970000 ( 2.032947)
20
+ set:ruby:dalli 1.690000 0.360000 2.050000 ( 2.108120)
21
+ get:plain:dalli 1.710000 0.400000 2.110000 ( 2.123895)
22
+ get:ruby:dalli 1.760000 0.390000 2.150000 ( 2.170964)
23
+ multiget:ruby:dalli 0.950000 0.310000 1.260000 ( 1.269679)
24
+ missing:ruby:dalli 1.650000 0.380000 2.030000 ( 2.054383)
25
+ mixed:ruby:dalli 3.470000 0.750000 4.220000 ( 4.323265)
data/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  Dalli
2
2
  =========
3
3
 
4
- Dalli is a high performance pure Ruby client for accessing memcached servers. It works with memcached 1.4+ only as it uses the newer binary protocol. The API tries to be mostly compatible with memcache-client with the goal being to make it a drop-in replacement for Rails.
4
+ Dalli is a high performance pure Ruby client for accessing memcached servers. It works with memcached 1.4+ only as it uses the newer binary protocol. It should be considered a replacement for the memcache-client gem. The API tries to be mostly compatible with memcache-client with the goal being to make it a drop-in replacement for Rails.
5
5
 
6
6
  The name is a variant of Salvador Dali for his famous painting [The Persistence of Memory](http://en.wikipedia.org/wiki/The_Persistence_of_Memory).
7
7
 
8
+ ![Persistence of Memory](http://www.virtualdali.com/assets/paintings/31PersistenceOfMemory.jpg)
9
+
8
10
  Dalli's development is sponsored by [NorthScale](http://www.northscale.com/). Many thanks to them!
9
11
 
10
12
 
@@ -24,11 +26,14 @@ So a few notes. Dalli:
24
26
  1. is approximately 20% faster than memcache-client (which itself was heavily optimized) in Ruby 1.9.2.
25
27
  2. contains explicit "chokepoint" methods which handle all requests; these can be hooked into by monitoring tools (NewRelic, Rack::Bug, etc) to track memcached usage.
26
28
  3. comes with hooks to replace memcache-client in Rails.
29
+ 4. is approx 700 lines of Ruby. memcache-client is approx 1250 lines.
27
30
 
28
31
 
29
32
  Installation and Usage
30
33
  ------------------------
31
34
 
35
+ Remember, Dalli **requires** memcached 1.4+. You can check the version with `memcached -h`.
36
+
32
37
  gem install dalli
33
38
 
34
39
  require 'dalli'
@@ -36,6 +41,8 @@ Installation and Usage
36
41
  dc.set('abc', 123)
37
42
  value = dc.get('abc')
38
43
 
44
+ The test suite requires memcached 1.4.3+ with SASL enabled (./configure --enable-sasl). Currently only supports the PLAIN mechanism.
45
+
39
46
 
40
47
  Usage with Rails
41
48
  ---------------------------
@@ -63,6 +70,12 @@ By default, Dalli is thread-safe. Disable thread-safety at your own peril.
63
70
  Note that Dalli does not require ActiveSupport or Rails. You can safely use it in your own Ruby projects.
64
71
 
65
72
 
73
+ Helping Out
74
+ -------------
75
+
76
+ If you have a fix you wish to provide, please fork the code, fix in your local project and then send a pull request on github. Please ensure that you include a test which verifies your fix and update History.md with a one sentence description of your fix so you get credit as a contributor.
77
+
78
+
66
79
  Author
67
80
  ----------
68
81
 
data/TODO.md ADDED
@@ -0,0 +1,6 @@
1
+ TODO
2
+ ========
3
+
4
+ * More of the memcached instruction set. This will be done based on user demand. Email me if the API is missing a feature you'd like to use.
5
+ * Better API documentation
6
+ * SASL for Heroku memcached add-on support.
@@ -7,12 +7,14 @@ Gem::Specification.new do |s|
7
7
  s.version = Dalli::VERSION
8
8
 
9
9
  s.authors = ["Mike Perham"]
10
- s.date = %q{2010-08-19}
10
+ s.date = %q{2010-08-28}
11
11
  s.description = %q{High performance memcached client for Ruby}
12
12
  s.email = %q{mperham@gmail.com}
13
13
  s.files = Dir.glob("lib/**/*") + [
14
14
  "LICENSE",
15
15
  "README.md",
16
+ "History.md",
17
+ "TODO.md",
16
18
  "Rakefile",
17
19
  "Gemfile",
18
20
  "dalli.gemspec",
@@ -24,7 +26,8 @@ Gem::Specification.new do |s|
24
26
  s.summary = %q{High performance memcached client for Ruby}
25
27
  s.test_files = Dir.glob("test/**/*")
26
28
  s.add_development_dependency(%q<shoulda>, [">= 0"])
27
- s.add_development_dependency(%q<rails>, [">= 3.0.0.rc"])
29
+ s.add_development_dependency(%q<mocha>, [">= 0"])
30
+ s.add_development_dependency(%q<rails>, [">= 3.0.0.rc2"])
28
31
  s.add_development_dependency(%q<memcache-client>, [">= 1.8.5"])
29
32
  end
30
33
 
@@ -4,21 +4,27 @@ require 'dalli/server'
4
4
  require 'dalli/version'
5
5
  require 'dalli/options'
6
6
 
7
- require 'logger'
8
-
9
7
  module Dalli
10
- # socket communication error
8
+ # generic error
11
9
  class DalliError < RuntimeError; end
10
+ # socket/server communication error
12
11
  class NetworkError < DalliError; end
13
12
 
14
13
  def self.logger
15
14
  @logger ||= begin
16
15
  (defined?(Rails) && Rails.logger) ||
17
16
  (defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER) ||
18
- (l = Logger.new(STDOUT); l.level = Logger::INFO; l)
17
+ default_logger
19
18
  end
20
19
  end
21
20
 
21
+ def self.default_logger
22
+ require 'logger'
23
+ l = Logger.new(STDOUT)
24
+ l.level = Logger::INFO
25
+ l
26
+ end
27
+
22
28
  def self.logger=(logger)
23
29
  @logger = logger
24
30
  end
@@ -12,19 +12,19 @@ module Dalli
12
12
  # Both weight and port are optional.
13
13
  #
14
14
  # Options:
15
+ # :failover - if a server is down, store the value on another server. Default: true.
15
16
  # :threadsafe - ensure that only one thread is actively using a socket at a time. Default: true.
16
17
  # :marshal - ensure that the value you store is exactly what is returned. Otherwise you can see this:
17
18
  # set('abc', 123)
18
19
  # get('abc') ==> '123' (Note you set an Integer but got back a String)
19
20
  # Default: true.
20
21
  #
21
- def initialize(servers, options={})
22
+ def initialize(servers=nil, options={})
22
23
  @ring = Dalli::Ring.new(
23
- Array(servers).map do |s|
24
+ Array(servers || env_servers).map do |s|
24
25
  Dalli::Server.new(s)
25
- end
26
+ end, options
26
27
  )
27
- @ring.threadsafe! unless options[:threadsafe] == false
28
28
  self.extend(Dalli::Marshal) unless options[:marshal] == false
29
29
  end
30
30
 
@@ -46,13 +46,31 @@ module Dalli
46
46
  values.inject(values) { |memo, (k,v)| memo[k] = deserialize(v); memo }
47
47
  end
48
48
  end
49
-
49
+
50
+ def fetch(key, ttl=0)
51
+ val = get(key)
52
+ if val.nil? && block_given?
53
+ val = yield
54
+ add(key, val, ttl)
55
+ end
56
+ val
57
+ end
58
+
59
+ def cas(key, ttl=0, &block)
60
+ (value, cas) = perform(:cas, key)
61
+ value = (!value || value == 'Not found') ? nil : deserialize(value)
62
+ if value
63
+ newvalue = block.call(value)
64
+ perform(:add, key, serialize(newvalue), ttl, cas)
65
+ end
66
+ end
67
+
50
68
  def set(key, value, ttl=0)
51
69
  perform(:set, key, serialize(value), ttl)
52
70
  end
53
71
 
54
72
  def add(key, value, ttl=0)
55
- perform(:add, key, serialize(value), ttl)
73
+ perform(:add, key, serialize(value), ttl, 0)
56
74
  end
57
75
 
58
76
  def replace(key, value, ttl=0)
@@ -64,11 +82,11 @@ module Dalli
64
82
  end
65
83
 
66
84
  def append(key, value)
67
- perform(:append, key, value)
85
+ perform(:append, key, value.to_s)
68
86
  end
69
87
 
70
88
  def prepend(key, value)
71
- perform(:prepend, key, value)
89
+ perform(:prepend, key, value.to_s)
72
90
  end
73
91
 
74
92
  def flush(delay=0)
@@ -80,12 +98,34 @@ module Dalli
80
98
  flush(0)
81
99
  end
82
100
 
83
- def incr(key, amt)
84
- perform(:incr, key, amt)
101
+ ##
102
+ # Incr adds the given amount to the counter on the memcached server.
103
+ # Amt must be a positive value.
104
+ #
105
+ # memcached counters are unsigned and cannot hold negative values. Calling
106
+ # decr on a counter which is 0 will just return 0.
107
+ #
108
+ # If default is nil, the counter must already exist or the operation
109
+ # will fail and will return nil. Otherwise this method will return
110
+ # the new value for the counter.
111
+ def incr(key, amt, ttl=0, default=nil)
112
+ raise ArgumentError, "Positive values only: #{amt}" if amt < 0
113
+ perform(:incr, key, amt, ttl, default)
85
114
  end
86
115
 
87
- def decr(key, amt)
88
- perform(:decr, key, amt)
116
+ ##
117
+ # Decr subtracts the given amount from the counter on the memcached server.
118
+ # Amt must be a positive value.
119
+ #
120
+ # memcached counters are unsigned and cannot hold negative values. Calling
121
+ # decr on a counter which is 0 will just return 0.
122
+ #
123
+ # If default is nil, the counter must already exist or the operation
124
+ # will fail and will return nil. Otherwise this method will return
125
+ # the new value for the counter.
126
+ def decr(key, amt, ttl=0, default=nil)
127
+ raise ArgumentError, "Positive values only: #{amt}" if amt < 0
128
+ perform(:decr, key, amt, ttl, default)
89
129
  end
90
130
 
91
131
  def stats
@@ -94,6 +134,7 @@ module Dalli
94
134
 
95
135
  def close
96
136
  @ring.servers.map { |s| s.close }
137
+ @ring = nil
97
138
  end
98
139
 
99
140
  private
@@ -106,6 +147,11 @@ module Dalli
106
147
  value
107
148
  end
108
149
 
150
+ def env_servers
151
+ ENV['MEMCACHE_SERVERS'].split(',')
152
+ end
153
+
154
+ # Chokepoint method for instrumentation
109
155
  def perform(op, *args)
110
156
  key = args.first
111
157
  validate_key(key)
@@ -7,7 +7,7 @@ module Dalli
7
7
 
8
8
  attr_accessor :servers, :continuum
9
9
 
10
- def initialize(servers)
10
+ def initialize(servers, options)
11
11
  @servers = servers
12
12
  if servers.size > 1
13
13
  total_weight = servers.inject(0) { |memo, srv| memo + srv.weight }
@@ -22,6 +22,9 @@ module Dalli
22
22
  continuum.sort { |a, b| a.value <=> b.value }
23
23
  @continuum = continuum
24
24
  end
25
+
26
+ threadsafe! unless options[:threadsafe] == false
27
+ @failover = options[:failover] != false
25
28
  end
26
29
 
27
30
  def server_for_key(key)
@@ -33,18 +36,13 @@ module Dalli
33
36
  entryidx = self.class.binary_search(@continuum, hkey)
34
37
  server = @continuum[entryidx].server
35
38
  return server if server.alive?
39
+ break unless @failover
36
40
  hkey = hash_for("#{try}#{key}")
37
41
  end
38
42
 
39
43
  raise Dalli::NetworkError, "No servers available"
40
44
  end
41
45
 
42
- def threadsafe!
43
- @servers.each do |s|
44
- s.extend(Dalli::Threadsafe)
45
- end
46
- end
47
-
48
46
  def lock
49
47
  @servers.each { |s| s.lock! }
50
48
  begin
@@ -55,7 +53,13 @@ module Dalli
55
53
  end
56
54
 
57
55
  private
58
-
56
+
57
+ def threadsafe!
58
+ @servers.each do |s|
59
+ s.extend(Dalli::Threadsafe)
60
+ end
61
+ end
62
+
59
63
  def hash_for(key)
60
64
  Zlib.crc32(key)
61
65
  end
@@ -0,0 +1,14 @@
1
+ module SASL
2
+ ##
3
+ # SASL ANONYMOUS where you only send a username that may not get
4
+ # evaluated by the server.
5
+ #
6
+ # RFC 4505:
7
+ # http://tools.ietf.org/html/rfc4505
8
+ class Anonymous < Mechanism
9
+ def start
10
+ @state = nil
11
+ ['auth', preferences.username.to_s]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,89 @@
1
+ module SASL
2
+
3
+ MECHANISMS = {
4
+ }
5
+
6
+ class Preferences
7
+ def authzid
8
+ nil
9
+ end
10
+
11
+ def realm
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def digest_uri
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def username
20
+ ENV['MEMCACHE_USERNAME']
21
+ end
22
+
23
+ def has_password?
24
+ false
25
+ end
26
+
27
+ def allow_plaintext?
28
+ false
29
+ end
30
+
31
+ def password
32
+ ENV['MEMCACHE_PASSWORD']
33
+ end
34
+
35
+ def want_anonymous?
36
+ false
37
+ end
38
+ end
39
+
40
+ def SASL.new(mechanisms)
41
+ mechanisms.each do |mech|
42
+ if MECHANISMS.has_key?(mech)
43
+ x = MECHANISMS[mech]
44
+ return x.new(mech, Preferences.new)
45
+ end
46
+ end
47
+
48
+ raise NotImplementedError, "No supported mechanisms in #{mechanisms.join(',')}"
49
+ end
50
+
51
+ ##
52
+ # Common functions for mechanisms
53
+ #
54
+ # Mechanisms implement handling of methods start and receive. They
55
+ # return: [message_name, content] or nil where message_name is
56
+ # either 'auth' or 'response' and content is either a string which
57
+ # may transmitted encoded as Base64 or nil.
58
+ class Mechanism
59
+ attr_reader :preferences
60
+ attr_reader :name
61
+
62
+ def initialize(name, preferences)
63
+ @name = name
64
+ @preferences = preferences
65
+ @state = nil
66
+ end
67
+
68
+ def success?
69
+ @state == :success
70
+ end
71
+ def failure?
72
+ @state == :failure
73
+ end
74
+
75
+ def start
76
+ raise NotImplementedError
77
+ end
78
+
79
+ def receive(message_name, content)
80
+ case message_name
81
+ when 'success'
82
+ @state = :success
83
+ when 'failure'
84
+ @state = :failure
85
+ end
86
+ nil
87
+ end
88
+ end
89
+ end