rails2_libmemcached_store 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,47 @@
1
+ = LibmemcachedStore
2
+
3
+ An ActiveSupport cache store that uses the C-based libmemcached client through
4
+ Evan Weaver's Ruby/SWIG wrapper, memcached. libmemcached is fast, lightweight,
5
+ and supports consistent hashing, non-blocking IO, and graceful server failover.
6
+
7
+ == Prerequisites
8
+
9
+ You'll need both the libmemcached client and the memcached gem:
10
+
11
+ * http://tangent.org/552/libmemcached.html
12
+ * http://blog.evanweaver.com/files/doc/fauna/memcached
13
+
14
+ Make sure you install libmemcached first, before you try installing the gem. If
15
+ you're using OS X, the easiest way to install libmemcached is through MacPorts:
16
+
17
+ sudo port install libmemcached
18
+
19
+ For other platforms, download and extract the libmemcached tarball and install
20
+ manually:
21
+
22
+ ./configure
23
+ make && sudo make install
24
+
25
+ Once libmemcached is installed, install the memcached gem:
26
+
27
+ gem install memcached --no-rdoc --no-ri
28
+
29
+ == Usage
30
+
31
+ This is a drop-in replacement for the memcache store that ships with Rails. To
32
+ enable, set the <tt>config.cache_store</tt> option to <tt>:libmemcached_store</tt>
33
+ in the config for your environment
34
+
35
+ config.cache_store = :libmemcached_store
36
+
37
+ If no servers are specified, localhost is assumed. You can specify a list of
38
+ server addresses, either as hostnames or IP addresses, with or without a port
39
+ designation. If no port is given, 11211 is assumed:
40
+
41
+ config.cache_store = :libmemcached_store, %w(cache-01 cache-02 127.0.0.1:11212)
42
+
43
+ == Props
44
+
45
+ Thanks to Brian Aker (http://tangent.org) for creating libmemcached, and Evan
46
+ Weaver (http://blog.evanweaver.com) for the Ruby wrapper.
47
+
@@ -0,0 +1,15 @@
1
+ module ActiveSupport
2
+ module Cache
3
+ class CompressedLibmemcachedStore < LibmemcachedStore
4
+ def read(name, options = {})
5
+ if value = super(name, (options || {}).merge(:raw => true))
6
+ Marshal.load(ActiveSupport::Gzip.decompress(value))
7
+ end
8
+ end
9
+
10
+ def write(name, value, options = {})
11
+ super(name, ActiveSupport::Gzip.compress(Marshal.dump(value)), (options || {}).merge(:raw => true))
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,182 @@
1
+ require 'memcached'
2
+ require 'digest/sha1'
3
+
4
+ class Memcached
5
+ # The latest version of memcached (0.11) doesn't support hostnames with dashes
6
+ # in their names, so we overwrite it here to be more lenient.
7
+ def set_servers(servers)
8
+ [*servers].each_with_index do |server, index|
9
+ host, port = server.split(":")
10
+ Lib.memcached_server_add(@struct, host, port.to_i)
11
+ end
12
+ end
13
+ end
14
+
15
+ module ActiveSupport
16
+ module Cache
17
+ class LibmemcachedStore < Store
18
+ attr_reader :addresses
19
+
20
+ ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
21
+
22
+ DEFAULT_OPTIONS = {
23
+ :distribution => :consistent,
24
+ :no_block => true,
25
+ :failover => true
26
+ }
27
+
28
+ def initialize(*addresses)
29
+ addresses.flatten!
30
+ options = addresses.extract_options!
31
+ addresses = %w(localhost) if addresses.empty?
32
+
33
+ if options[:prefix_key]
34
+ @namespace_length = options[:prefix_key].length
35
+ @namespace_length += options[:prefix_delimiter].length if options[:prefix_delimiter]
36
+ else
37
+ @namespace_length = 0
38
+ end
39
+
40
+ @addresses = addresses
41
+ @cache = Memcached.new(@addresses, options.reverse_merge(DEFAULT_OPTIONS))
42
+ extend ActiveSupport::Cache::Strategy::LocalCache
43
+ end
44
+
45
+ def read(key, options = nil)
46
+ key = expanded_key(key)
47
+ super
48
+ @cache.get(escape_and_normalize(key), marshal?(options))
49
+ rescue Memcached::NotFound
50
+ nil
51
+ rescue Memcached::Error => e
52
+ log_error(e)
53
+ nil
54
+ end
55
+
56
+ def read_multi(*names)
57
+ names.flatten!
58
+ options = names.extract_options!
59
+
60
+ return {} if names.empty?
61
+
62
+ mapping = Hash[names.map {|name| [escape_and_normalize(expanded_key(name)), name] }]
63
+ raw_values = @cache.get(mapping.keys, marshal?(options))
64
+
65
+ values = {}
66
+ raw_values.each do |key, value|
67
+ values[mapping[key]] = value
68
+ end
69
+ values
70
+ end
71
+
72
+ # Set the key to the given value. Pass :unless_exist => true if you want to
73
+ # skip setting a key that already exists.
74
+ def write(key, value, options = nil)
75
+ key = expanded_key(key)
76
+ super
77
+ method = (options && options[:unless_exist]) ? :add : :set
78
+ @cache.send(method, escape_and_normalize(key), value, expires_in(options), marshal?(options))
79
+ true
80
+ rescue Memcached::Error => e
81
+ log_error(e)
82
+ false
83
+ end
84
+
85
+ def delete(key, options = nil)
86
+ key = expanded_key(key)
87
+ super
88
+ @cache.delete(escape_and_normalize(key))
89
+ true
90
+ rescue Memcached::NotFound
91
+ nil
92
+ rescue Memcached::Error => e
93
+ log_error(e)
94
+ false
95
+ end
96
+
97
+ def exist?(key, options = nil)
98
+ key = expanded_key(key)
99
+ !read(key, options).nil?
100
+ end
101
+
102
+ def increment(key, amount=1)
103
+ key = expanded_key(key)
104
+ log 'incrementing', key, amount
105
+ @cache.incr(escape_and_normalize(key), amount)
106
+ rescue Memcached::Error
107
+ nil
108
+ end
109
+
110
+ def decrement(key, amount=1)
111
+ key = expanded_key(key)
112
+ log 'decrementing', key, amount
113
+ @cache.decr(escape_and_normalize(key), amount)
114
+ rescue Memcached::Error
115
+ nil
116
+ end
117
+
118
+ def delete_matched(matcher, options = nil)
119
+ super
120
+ raise NotImplementedError
121
+ end
122
+
123
+ # Flushes all data in memory
124
+ def clear
125
+ @cache.flush
126
+ end
127
+
128
+ def stats
129
+ @cache.stats
130
+ end
131
+
132
+ # Resets server connections, forcing a reconnect. This is required in
133
+ # cases where processes fork, but continue sharing the same memcached
134
+ # connection. You want to call this after the fork to make sure the
135
+ # new process has its own connection.
136
+ def reset
137
+ @cache.reset
138
+ end
139
+
140
+ private
141
+
142
+ def escape_and_normalize(key)
143
+ key = key.to_s.dup.force_encoding("BINARY").gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
144
+ key_length = key.length
145
+
146
+ return key if @namespace_length + key_length <= 250
147
+
148
+ max_key_length = 213 - @namespace_length
149
+ "#{key[0, max_key_length]}:md5:#{Digest::MD5.hexdigest(key)}"
150
+ end
151
+
152
+ def expanded_key(key) # :nodoc:
153
+ return key.cache_key.to_s if key.respond_to?(:cache_key)
154
+
155
+ case key
156
+ when Array
157
+ if key.size > 1
158
+ key = key.collect { |element| expanded_key(element) }
159
+ else
160
+ key = key.first
161
+ end
162
+ when Hash
163
+ key = key.sort_by { |k,_| k.to_s }.collect { |k, v| "#{k}=#{v}" }
164
+ end
165
+
166
+ key.to_param
167
+ end
168
+
169
+ def expires_in(options)
170
+ (options || {})[:expires_in] || 0
171
+ end
172
+
173
+ def marshal?(options)
174
+ !(options || {})[:raw]
175
+ end
176
+
177
+ def log_error(exception)
178
+ logger.error "MemcachedError (#{exception.inspect}): #{exception.message}" if logger && !@logger_off
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,2 @@
1
+ require 'active_support/cache/libmemcached_store'
2
+ require 'active_support/cache/compressed_libmemcached_store'
@@ -0,0 +1,84 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'active_support'
4
+ require 'memcached'
5
+
6
+ require File.dirname(__FILE__) + '/../lib/libmemcached_store'
7
+
8
+ # Make it easier to get at the underlying cache options during testing.
9
+ class ActiveSupport::Cache::LibmemcachedStore
10
+ delegate :options, :to => '@cache'
11
+ end
12
+
13
+ class LibmemcachedStoreTest < Test::Unit::TestCase
14
+ def setup
15
+ @store = ActiveSupport::Cache.lookup_store :libmemcached_store
16
+ @store.clear
17
+ end
18
+
19
+ def test_should_identify_cache_store
20
+ assert_kind_of ActiveSupport::Cache::LibmemcachedStore, @store
21
+ end
22
+
23
+ def test_should_set_server_addresses_to_localhost_if_none_are_given
24
+ assert_equal %w(localhost), @store.addresses
25
+ end
26
+
27
+ def test_should_set_custom_server_addresses
28
+ store = ActiveSupport::Cache.lookup_store :libmemcached_store, 'localhost', '192.168.1.1'
29
+ assert_equal %w(localhost 192.168.1.1), store.addresses
30
+ end
31
+
32
+ def test_should_enable_consistent_hashing_by_default
33
+ assert_equal :consistent, @store.options[:distribution]
34
+ end
35
+
36
+ def test_should_enable_non_blocking_io_by_default
37
+ assert_equal true, @store.options[:no_block]
38
+ end
39
+
40
+ def test_should_enable_server_failover_by_default
41
+ assert_equal true, @store.options[:auto_eject_hosts]
42
+ end
43
+
44
+ def test_should_allow_configuration_of_custom_options
45
+ options = {
46
+ :prefix_key => 'test',
47
+ :distribution => :modula,
48
+ :no_block => false,
49
+ :auto_eject_hosts => false
50
+ }
51
+
52
+ store = ActiveSupport::Cache.lookup_store :libmemcached_store, 'localhost', options
53
+
54
+ assert_equal 'test', store.instance_variable_get(:@cache).prefix_key
55
+ assert_equal :modula, store.options[:distribution]
56
+ assert_equal false, store.options[:no_block]
57
+ assert_equal false, store.options[:auto_eject_hosts]
58
+ end
59
+
60
+ def test_should_use_local_cache
61
+ @store.with_local_cache do
62
+ @store.write('key', 'value')
63
+ assert_equal 'value', @store.send(:local_cache).read('key')
64
+ end
65
+
66
+ assert_equal 'value', @store.read('key')
67
+ end
68
+
69
+ def test_should_read_multiple_keys
70
+ @store.write('a', 1)
71
+ @store.write('b', 2)
72
+
73
+ assert_equal({ 'a' => 1, 'b' => 2 }, @store.read_multi('a', 'b', 'c'))
74
+ assert_equal({}, @store.read_multi())
75
+ end
76
+
77
+ def test_should_fix_long_keys
78
+ key = ("0123456789" * 100).freeze
79
+ assert key.size > 250
80
+ @store.write(key, 1)
81
+ assert_equal 1, @store.read(key)
82
+ end
83
+
84
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails2_libmemcached_store
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jeffrey Hardy
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: memcached
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: activesupport
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - <
52
+ - !ruby/object:Gem::Version
53
+ version: '3'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - <
60
+ - !ruby/object:Gem::Version
61
+ version: '3'
62
+ description: ! "An ActiveSupport cache store that uses the C-based libmemcached client
63
+ through\n Evan Weaver's Ruby/SWIG wrapper, memcached. libmemcached is fast,
64
+ lightweight,\n and supports consistent hashing, non-blocking IO, and graceful
65
+ server failover."
66
+ email:
67
+ - packagethief@gmail.com
68
+ executables: []
69
+ extensions: []
70
+ extra_rdoc_files: []
71
+ files:
72
+ - lib/active_support/cache/compressed_libmemcached_store.rb
73
+ - lib/active_support/cache/libmemcached_store.rb
74
+ - lib/libmemcached_store.rb
75
+ - README
76
+ - test/libmemcached_store_test.rb
77
+ homepage: http://github.com/37signals/libmemcached_store
78
+ licenses: []
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ segments:
90
+ - 0
91
+ hash: 2896348711499300858
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ segments:
99
+ - 0
100
+ hash: 2896348711499300858
101
+ requirements: []
102
+ rubyforge_project:
103
+ rubygems_version: 1.8.25
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: ActiveSupport::Cache wrapper for libmemcached
107
+ test_files:
108
+ - test/libmemcached_store_test.rb