libmemcached_store 0.2.3 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.travis.yml +7 -0
- data/BENCHMARKS +37 -0
- data/CHANGELOG.md +20 -0
- data/Gemfile +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +78 -0
- data/Rakefile +24 -0
- data/gemfiles/rails30.gemfile +7 -0
- data/gemfiles/rails31.gemfile +7 -0
- data/gemfiles/rails32.gemfile +7 -0
- data/lib/action_dispatch/session/libmemcached_store.rb +80 -0
- data/lib/active_support/cache/libmemcached_store.rb +227 -81
- data/lib/libmemcached_store.rb +1 -1
- data/lib/memcached/get_with_flags.rb +42 -0
- data/lib/version.rb +3 -0
- data/libmemcached_store.gemspec +30 -0
- data/test/action_dispatch/abstract_unit.rb +43 -0
- data/test/action_dispatch/libmemcached_store_test.rb +200 -0
- data/test/active_support/libmemcached_store_test.rb +341 -0
- data/test/fixtures/session_autoload_test.rb +10 -0
- data/test/profile/benchmark.rb +94 -0
- data/test/test_helper.rb +5 -0
- metadata +132 -86
- data/README +0 -47
- data/lib/active_support/cache/compressed_libmemcached_store.rb +0 -15
- data/test/libmemcached_store_test.rb +0 -84
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fd3263466b00f991543c49eae9ed95cf60808d48
|
4
|
+
data.tar.gz: 3f8780d95967e725cc6b85a2d11236616ee34baa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 60147888a5c9aedc996e3597dc99deba43fac634c6e57febba5447208da63147fdd5387ede6cff4c3f6e6d5edccd25a137b6fe1840b6ca692e70c1cb68764a0a
|
7
|
+
data.tar.gz: 8be98b2735c5c6f9a0d004463622523defce2ca0e76263aae6f616fffc19dfedf7ffd3eedf026796bf46455b17843de910efd5ee4702d9b8c94d930dbffdd2bc
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/BENCHMARKS
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
Testing with
|
2
|
+
ruby 1.9.3p286 (2012-10-12 revision 37165) [x86_64-darwin12.2.0]
|
3
|
+
Dalli 2.6.3
|
4
|
+
Libmemcached_store 0.6.0
|
5
|
+
user system total real
|
6
|
+
write:short:dalli 0.380000 0.040000 0.420000 ( 0.422342)
|
7
|
+
write:short:libm 0.040000 0.020000 0.060000 ( 0.125838)
|
8
|
+
write:long:dalli 0.370000 0.040000 0.410000 ( 0.407400)
|
9
|
+
write:long:libm 0.050000 0.030000 0.080000 ( 0.130482)
|
10
|
+
write:raw:dalli 0.330000 0.030000 0.360000 ( 0.371981)
|
11
|
+
write:raw:libm 0.050000 0.030000 0.080000 ( 0.122899)
|
12
|
+
|
13
|
+
read:miss:dalli 0.310000 0.040000 0.350000 ( 0.352729)
|
14
|
+
read:miss:libm 0.080000 0.040000 0.120000 ( 0.182143)
|
15
|
+
read:miss2:dalli 0.350000 0.040000 0.390000 ( 0.394071)
|
16
|
+
read:miss2:libm 0.090000 0.050000 0.140000 ( 0.190611)
|
17
|
+
read:exist:dalli 0.350000 0.040000 0.390000 ( 0.391384)
|
18
|
+
read:exist:libm 0.060000 0.040000 0.100000 ( 0.161597)
|
19
|
+
read:expired:dalli 0.350000 0.040000 0.390000 ( 0.399342)
|
20
|
+
read:expired:libm 0.080000 0.050000 0.130000 ( 0.175674)
|
21
|
+
read:raw:dalli 0.360000 0.040000 0.400000 ( 0.400142)
|
22
|
+
read:raw:libm 0.050000 0.040000 0.090000 ( 0.160905)
|
23
|
+
|
24
|
+
exist:miss:dalli 0.310000 0.040000 0.350000 ( 0.342454)
|
25
|
+
exist:miss:libm 0.040000 0.030000 0.070000 ( 0.141901)
|
26
|
+
exist:hit:dalli 0.320000 0.040000 0.360000 ( 0.354205)
|
27
|
+
exist:hit:libm 0.030000 0.020000 0.050000 ( 0.107114)
|
28
|
+
|
29
|
+
delete:miss:dalli 0.310000 0.040000 0.350000 ( 0.342343)
|
30
|
+
delete:miss:libm 0.060000 0.030000 0.090000 ( 0.141960)
|
31
|
+
delete:hit:dalli 0.350000 0.040000 0.390000 ( 0.389187)
|
32
|
+
delete:hit:libm 0.050000 0.020000 0.070000 ( 0.130604)
|
33
|
+
|
34
|
+
increment:dalli 0.380000 0.050000 0.430000 ( 0.422802)
|
35
|
+
increment:libm 0.040000 0.020000 0.060000 ( 0.119200)
|
36
|
+
decrement:dalli 0.390000 0.040000 0.430000 ( 0.426890)
|
37
|
+
decrement:libm 0.030000 0.030000 0.060000 ( 0.118954)
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## 0.6.0
|
4
|
+
* New gem name _libmemcached_store_
|
5
|
+
* Handle Memcached::Error in read_multi (staugaard)
|
6
|
+
|
7
|
+
## 0.5.1
|
8
|
+
* Remove warning from latest version of mocha
|
9
|
+
* Make #clear compatible with Rails.cache#clear (grosser)
|
10
|
+
|
11
|
+
## 0.5.0
|
12
|
+
* Use Memcached#exist if available (performance improvement ~25%)
|
13
|
+
* Correctly escape bad characters and too long keys
|
14
|
+
* Add benchmarks
|
15
|
+
* Remove the use of ActiveSupport::Entry which was a performance bottleneck #3
|
16
|
+
|
17
|
+
## 0.4.0
|
18
|
+
* Optimize read_multi to only make one call to memecached server
|
19
|
+
* Update test suite to reflect Rails' one
|
20
|
+
* Add session store tests
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 37signals
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# LibmemcachedStore
|
2
|
+
|
3
|
+
An ActiveSupport cache store that uses the C-based libmemcached client through Evan Weaver's Ruby/SWIG wrapper, [memcached](https://github.com/evan/memcached). libmemcached is fast (fastest memcache client for Ruby), lightweight, and supports consistent hashing, non-blocking IO, and graceful server failover.
|
4
|
+
|
5
|
+
This cache is designed for Rails 3+ applications.
|
6
|
+
|
7
|
+
## Prerequisites
|
8
|
+
|
9
|
+
You'll need the memcached gem installed:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem install memcached
|
13
|
+
```
|
14
|
+
|
15
|
+
or in your Gemfile
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'memcached'
|
19
|
+
```
|
20
|
+
|
21
|
+
There are no other dependencies.
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
Just add to your Gemfile
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
gem 'libmemcached_store', '~> 0.6.0'
|
29
|
+
```
|
30
|
+
|
31
|
+
and you're set.
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
This is a drop-in replacement for the memcache store that ships with Rails. To
|
36
|
+
enable, set the `config.cache_store` option to `libmemcached_store`
|
37
|
+
in the config for your environment
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
config.cache_store = :libmemcached_store
|
41
|
+
```
|
42
|
+
|
43
|
+
If no servers are specified, localhost is assumed. You can specify a list of
|
44
|
+
server addresses, either as hostnames or IP addresses, with or without a port
|
45
|
+
designation. If no port is given, 11211 is assumed:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
config.cache_store = :libmemcached_store, %w(cache-01 cache-02 127.0.0.1:11212)
|
49
|
+
```
|
50
|
+
|
51
|
+
Standard Rails cache store options can be used
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
config.cache_store = :libmemcached_store, '127.0.0.1:11211', {:compress => true, :expires_in => 3600}
|
55
|
+
```
|
56
|
+
|
57
|
+
More advanced options can be passed directly to the client
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
config.cache_store = :libmemcached_store, '127.0.0.1:11211', {:client => { :binary_protocol => true, :no_block => true }}
|
61
|
+
```
|
62
|
+
|
63
|
+
You can also use `:libmemcached_store` to store your application sessions
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
require 'action_dispatch/session/libmemcached_store'
|
67
|
+
config.session_store = :libmemcached_store, :namespace => '_session', :expire_after => 1800
|
68
|
+
```
|
69
|
+
|
70
|
+
## Performance
|
71
|
+
|
72
|
+
Used with Rails, __libmemcached_store__ is at least 1.5x faster than __dalli__. See [BENCHMARKS](https://github.com/ccocchi/libmemcached_store/blob/master/BENCHMARKS)
|
73
|
+
for details
|
74
|
+
|
75
|
+
## Props
|
76
|
+
|
77
|
+
Thanks to Brian Aker ([http://tangent.org](http://tangent.org)) for creating libmemcached, and Evan
|
78
|
+
Weaver ([http://blog.evanweaver.com](http://blog.evanweaver.com)) for the Ruby wrapper.
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'rdoc/task'
|
7
|
+
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
Rake::TestTask.new do |t|
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.warning = false
|
14
|
+
t.verbose = true
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'Generate documentation for the libmemcached_store plugin.'
|
18
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
19
|
+
rdoc.rdoc_dir = 'rdoc'
|
20
|
+
rdoc.title = 'LibmemcachedStore'
|
21
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
22
|
+
rdoc.rdoc_files.include('README')
|
23
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
24
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'memcached'
|
2
|
+
require 'action_dispatch/middleware/session/abstract_store'
|
3
|
+
|
4
|
+
module ActionDispatch
|
5
|
+
module Session
|
6
|
+
class LibmemcachedStore < AbstractStore
|
7
|
+
|
8
|
+
def initialize(app, options = {})
|
9
|
+
options[:expire_after] ||= options[:expires]
|
10
|
+
super
|
11
|
+
client_options = { default_ttl: options.fetch(:expire_after, 0) }
|
12
|
+
client_options[:namespace] = options[:namespace] || 'rack:session'
|
13
|
+
@mutex = Mutex.new
|
14
|
+
@pool = options[:cache] || Memcached.new(@default_options[:memcache_server], client_options)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def generate_sid
|
20
|
+
loop do
|
21
|
+
sid = super
|
22
|
+
begin
|
23
|
+
@pool.exist(sid)
|
24
|
+
rescue Memcached::NotFound
|
25
|
+
break sid
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_session(env, sid)
|
31
|
+
sid ||= generate_sid
|
32
|
+
session = with_lock(env, {}) do
|
33
|
+
begin
|
34
|
+
@pool.get(sid)
|
35
|
+
rescue Memcached::NotFound
|
36
|
+
{}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
[sid, session]
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_session(env, session_id, new_session, options = {})
|
43
|
+
expiry = options[:expire_after].to_i
|
44
|
+
|
45
|
+
with_lock(env, false) do
|
46
|
+
@pool.set(session_id, new_session, expiry)
|
47
|
+
session_id
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def destroy_session(env, session_id, options = {})
|
52
|
+
with_lock(env, nil) do
|
53
|
+
@pool.delete(session_id)
|
54
|
+
generate_sid unless options[:drop]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Deprecated since Rails 3.1.0
|
60
|
+
#
|
61
|
+
def destroy(env)
|
62
|
+
if sid = current_session_id(env)
|
63
|
+
with_lock(env, false) do
|
64
|
+
@pool.delete(sid)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def with_lock(env, default)
|
70
|
+
@mutex.lock if env['rack.multithread']
|
71
|
+
yield
|
72
|
+
rescue Memcached::Error => e
|
73
|
+
default
|
74
|
+
ensure
|
75
|
+
@mutex.unlock if @mutex.locked?
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -1,53 +1,138 @@
|
|
1
1
|
require 'memcached'
|
2
|
-
require '
|
3
|
-
|
4
|
-
|
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
|
2
|
+
require 'memcached/get_with_flags'
|
3
|
+
|
4
|
+
require 'digest/md5'
|
14
5
|
|
15
6
|
module ActiveSupport
|
16
7
|
module Cache
|
17
|
-
|
8
|
+
|
9
|
+
#
|
10
|
+
# Store using memcached gem as client
|
11
|
+
#
|
12
|
+
# Global options can be passed to be applied to each method by default.
|
13
|
+
# Supported options are
|
14
|
+
# * <tt>:compress</tt> : if set to true, data will be compress before stored
|
15
|
+
# * <tt>:compress_threshold</tt> : specify the threshold at which to compress
|
16
|
+
# value, default is 4K
|
17
|
+
# * <tt>:namespace</tt> : prepend each key with this value for simple namespacing
|
18
|
+
# * <tt>:expires_in</tt> : default TTL in seconds for each. Default value is 0, i.e. forever
|
19
|
+
# Specific value can be passed per key with write and fetch command.
|
20
|
+
#
|
21
|
+
# Options can also be passed direclty to the memcache client, via the <tt>:client</tt>
|
22
|
+
# option. For example, if you want to use pipelining, you can use
|
23
|
+
# :client => { :no_block => true }
|
24
|
+
#
|
25
|
+
class LibmemcachedStore
|
18
26
|
attr_reader :addresses
|
19
27
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
28
|
+
DEFAULT_CLIENT_OPTIONS = { distribution: :consistent_ketama, binary_protocol: true, default_ttl: 0 }
|
29
|
+
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
|
30
|
+
DEFAULT_COMPRESS_THRESHOLD = 4096
|
31
|
+
FLAG_COMPRESSED = 0x2
|
32
|
+
|
33
|
+
attr_reader :silence, :options
|
34
|
+
alias_method :silence?, :silence
|
35
|
+
|
36
|
+
# Silence the logger.
|
37
|
+
def silence!
|
38
|
+
@silence = true
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
# Silence the logger within a block.
|
43
|
+
def mute
|
44
|
+
previous_silence, @silence = defined?(@silence) && @silence, true
|
45
|
+
yield
|
46
|
+
ensure
|
47
|
+
@silence = previous_silence
|
48
|
+
end
|
25
49
|
|
26
50
|
def initialize(*addresses)
|
27
51
|
addresses.flatten!
|
28
52
|
options = addresses.extract_options!
|
29
|
-
|
53
|
+
client_options = options.delete(:client) || {}
|
54
|
+
if options[:namespace]
|
55
|
+
client_options[:prefix_key] = options.delete(:namespace)
|
56
|
+
client_options[:prefix_delimiter] = ':'
|
57
|
+
@namespace_length = client_options[:prefix_key].length + 1
|
58
|
+
else
|
59
|
+
@namespace_length = 0
|
60
|
+
end
|
61
|
+
client_options[:default_ttl] = options.delete(:expires_in).to_i if options[:expires_in]
|
30
62
|
|
63
|
+
@options = options.reverse_merge(compress_threshold: DEFAULT_COMPRESS_THRESHOLD)
|
31
64
|
@addresses = addresses
|
32
|
-
@cache = Memcached.new(@addresses,
|
33
|
-
extend
|
65
|
+
@cache = Memcached.new(@addresses, client_options.reverse_merge(DEFAULT_CLIENT_OPTIONS))
|
66
|
+
@cache.instance_eval { send(:extend, GetWithFlags) }
|
34
67
|
end
|
35
68
|
|
36
|
-
def
|
37
|
-
if
|
38
|
-
key
|
39
|
-
|
40
|
-
|
41
|
-
|
69
|
+
def fetch(key, options = nil)
|
70
|
+
if block_given?
|
71
|
+
key = expanded_key(key)
|
72
|
+
unless options && options[:force]
|
73
|
+
entry = instrument(:read, key, options) do |payload|
|
74
|
+
payload[:super_operation] = :fetch if payload
|
75
|
+
read_entry(key, options)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
if entry.nil?
|
80
|
+
result = instrument(:generate, key, options) do |payload|
|
81
|
+
yield
|
82
|
+
end
|
83
|
+
write_entry(key, result, options)
|
84
|
+
result
|
42
85
|
else
|
43
|
-
key
|
86
|
+
instrument(:fetch_hit, key, options) { |payload| }
|
87
|
+
entry
|
44
88
|
end
|
89
|
+
else
|
90
|
+
read(key, options)
|
45
91
|
end
|
46
92
|
end
|
47
93
|
|
48
94
|
def read(key, options = nil)
|
49
|
-
|
50
|
-
|
95
|
+
key = expanded_key(key)
|
96
|
+
instrument(:read, key, options) do |payload|
|
97
|
+
entry = read_entry(key, options)
|
98
|
+
payload[:hit] = !!entry if payload
|
99
|
+
entry
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def write(key, value, options = nil)
|
104
|
+
key = expanded_key(key)
|
105
|
+
instrument(:write, key, options) do |payload|
|
106
|
+
write_entry(key, value, options)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def delete(key, options = nil)
|
111
|
+
key = expanded_key(key)
|
112
|
+
instrument(:delete, key) do |payload|
|
113
|
+
delete_entry(key, options)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def exist?(key, options = nil)
|
118
|
+
key = expanded_key(key)
|
119
|
+
instrument(:exist?, key) do |payload|
|
120
|
+
if @cache.respond_to?(:exist)
|
121
|
+
@cache.exist(escape_and_normalize(key))
|
122
|
+
true
|
123
|
+
else
|
124
|
+
read_entry(key, options) != nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
rescue Memcached::NotFound
|
128
|
+
false
|
129
|
+
end
|
130
|
+
|
131
|
+
def increment(key, amount = 1, options = nil)
|
132
|
+
key = expanded_key(key)
|
133
|
+
instrument(:increment, key, amount: amount) do
|
134
|
+
@cache.incr(escape_and_normalize(key), amount)
|
135
|
+
end
|
51
136
|
rescue Memcached::NotFound
|
52
137
|
nil
|
53
138
|
rescue Memcached::Error => e
|
@@ -55,86 +140,147 @@ module ActiveSupport
|
|
55
140
|
nil
|
56
141
|
end
|
57
142
|
|
58
|
-
def
|
59
|
-
|
143
|
+
def decrement(key, amount = 1, options = nil)
|
144
|
+
key = expanded_key(key)
|
145
|
+
instrument(:decrement, key, amount: amount) do
|
146
|
+
@cache.decr(escape_and_normalize(key), amount)
|
147
|
+
end
|
148
|
+
rescue Memcached::NotFound
|
149
|
+
nil
|
150
|
+
rescue Memcached::Error => e
|
151
|
+
log_error(e)
|
152
|
+
nil
|
60
153
|
end
|
61
154
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
155
|
+
def read_multi(*names)
|
156
|
+
names.flatten!
|
157
|
+
options = names.extract_options!
|
158
|
+
|
159
|
+
return {} if names.empty?
|
160
|
+
|
161
|
+
mapping = Hash[names.map {|name| [escape_and_normalize(expanded_key(name)), name] }]
|
162
|
+
raw_values, flags = @cache.get(mapping.keys, false, true)
|
163
|
+
|
164
|
+
values = {}
|
165
|
+
raw_values.each do |key, value|
|
166
|
+
values[mapping[key]] = deserialize(value, options[:raw], flags[key])
|
167
|
+
end
|
168
|
+
values
|
169
|
+
rescue Memcached::Error => e
|
170
|
+
log_error(e)
|
171
|
+
{}
|
172
|
+
end
|
173
|
+
|
174
|
+
def clear(options = nil)
|
175
|
+
@cache.flush
|
176
|
+
end
|
177
|
+
|
178
|
+
def stats
|
179
|
+
@cache.stats
|
180
|
+
end
|
181
|
+
|
182
|
+
protected
|
183
|
+
|
184
|
+
def read_entry(key, options = nil)
|
185
|
+
options ||= {}
|
186
|
+
raw_value, flags = @cache.get(escape_and_normalize(key), false, true)
|
187
|
+
deserialize(raw_value, options[:raw], flags)
|
188
|
+
rescue Memcached::NotFound
|
189
|
+
nil
|
190
|
+
rescue Memcached::Error => e
|
191
|
+
log_error(e)
|
192
|
+
nil
|
193
|
+
end
|
194
|
+
|
195
|
+
def write_entry(key, entry, options = nil)
|
196
|
+
options = options ? @options.merge(options) : @options
|
197
|
+
method = options[:unless_exist] ? :add : :set
|
198
|
+
entry = options[:raw] ? entry.to_s : Marshal.dump(entry)
|
199
|
+
flags = 0
|
200
|
+
|
201
|
+
if options[:compress] && entry.bytesize >= options[:compress_threshold]
|
202
|
+
entry = Zlib::Deflate.deflate(entry)
|
203
|
+
flags |= FLAG_COMPRESSED
|
204
|
+
end
|
205
|
+
|
206
|
+
@cache.send(method, escape_and_normalize(key), entry, options[:expires_in].to_i, false, flags)
|
68
207
|
true
|
69
208
|
rescue Memcached::Error => e
|
70
209
|
log_error(e)
|
71
210
|
false
|
72
211
|
end
|
73
212
|
|
74
|
-
def
|
75
|
-
|
76
|
-
@cache.delete(valid_key(key))
|
213
|
+
def delete_entry(key, options = nil)
|
214
|
+
@cache.delete(escape_and_normalize(key))
|
77
215
|
true
|
78
216
|
rescue Memcached::NotFound
|
79
|
-
|
217
|
+
false
|
80
218
|
rescue Memcached::Error => e
|
81
219
|
log_error(e)
|
82
220
|
false
|
83
221
|
end
|
84
222
|
|
85
|
-
|
86
|
-
!read(key, options).nil?
|
87
|
-
end
|
223
|
+
private
|
88
224
|
|
89
|
-
def
|
90
|
-
|
91
|
-
|
92
|
-
rescue
|
93
|
-
|
225
|
+
def deserialize(value, raw = false, flags = 0)
|
226
|
+
value = Zlib::Inflate.inflate(value) if (flags & FLAG_COMPRESSED) != 0
|
227
|
+
raw ? value : Marshal.load(value)
|
228
|
+
rescue TypeError, ArgumentError
|
229
|
+
value
|
94
230
|
end
|
95
231
|
|
96
|
-
def
|
97
|
-
|
98
|
-
|
99
|
-
rescue Memcached::Error
|
100
|
-
nil
|
101
|
-
end
|
232
|
+
def escape_and_normalize(key)
|
233
|
+
key = key.to_s.force_encoding("BINARY").gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
|
234
|
+
key_length = key.length
|
102
235
|
|
103
|
-
|
104
|
-
super
|
105
|
-
raise NotImplementedError
|
106
|
-
end
|
236
|
+
return key if @namespace_length + key_length <= 250
|
107
237
|
|
108
|
-
|
109
|
-
|
110
|
-
@cache.flush
|
238
|
+
max_key_length = 213 - @namespace_length
|
239
|
+
"#{key[0, max_key_length]}:md5:#{Digest::MD5.hexdigest(key)}"
|
111
240
|
end
|
112
241
|
|
113
|
-
def
|
114
|
-
|
115
|
-
|
242
|
+
def expanded_key(key) # :nodoc:
|
243
|
+
return key.cache_key.to_s if key.respond_to?(:cache_key)
|
244
|
+
|
245
|
+
case key
|
246
|
+
when Array
|
247
|
+
if key.size > 1
|
248
|
+
key = key.collect { |element| expanded_key(element) }
|
249
|
+
else
|
250
|
+
key = key.first
|
251
|
+
end
|
252
|
+
when Hash
|
253
|
+
key = key.sort_by { |k,_| k.to_s }.collect { |k, v| "#{k}=#{v}" }
|
254
|
+
end
|
116
255
|
|
117
|
-
|
118
|
-
# cases where processes fork, but continue sharing the same memcached
|
119
|
-
# connection. You want to call this after the fork to make sure the
|
120
|
-
# new process has its own connection.
|
121
|
-
def reset
|
122
|
-
@cache.reset
|
256
|
+
key.to_param
|
123
257
|
end
|
124
258
|
|
125
|
-
|
259
|
+
def instrument(operation, key, options=nil)
|
260
|
+
log(operation, key, options)
|
126
261
|
|
127
|
-
|
128
|
-
|
262
|
+
if ActiveSupport::Cache::Store.instrument
|
263
|
+
payload = { :key => key }
|
264
|
+
payload.merge!(options) if options.is_a?(Hash)
|
265
|
+
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
|
266
|
+
else
|
267
|
+
yield(nil)
|
129
268
|
end
|
269
|
+
end
|
130
270
|
|
131
|
-
|
132
|
-
|
133
|
-
|
271
|
+
def log(operation, key, options=nil)
|
272
|
+
return unless !silence? && logger && logger.debug?
|
273
|
+
logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}")
|
274
|
+
end
|
134
275
|
|
135
|
-
|
136
|
-
|
137
|
-
|
276
|
+
def log_error(exception)
|
277
|
+
return unless !silence? && logger && logger.error?
|
278
|
+
logger.error "MemcachedError (#{exception.inspect}): #{exception.message}"
|
279
|
+
end
|
280
|
+
|
281
|
+
def logger
|
282
|
+
Rails.logger
|
283
|
+
end
|
138
284
|
end
|
139
285
|
end
|
140
286
|
end
|