memcached_snappy_store 0.0.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 +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +36 -0
- data/Rakefile +12 -0
- data/lib/active_support/cache/memcached_snappy_store.rb +48 -0
- data/lib/memcached_snappy_store.rb +2 -0
- data/memcached_snappy_store.gemspec +25 -0
- data/test/test_memcached_snappy_store.rb +94 -0
- metadata +205 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Camilo Lopez
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# MemcachedSnappyStore
|
2
|
+
|
3
|
+
ActiveSupport cache store that adds snappy compression at the cost of making the ```incr, decr, add``` operations unavailable.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'memcached_snappy_store'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install memcached_snappy_store
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
In your environment file:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
|
25
|
+
config.cache_store = :memcached_snappy_store,
|
26
|
+
Memcached::Rails.new(:servers => ['memcached1.foo.com', 'memcached2.foo.com'])
|
27
|
+
|
28
|
+
```
|
29
|
+
|
30
|
+
## Contributing
|
31
|
+
|
32
|
+
1. Fork it
|
33
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
34
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
35
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
36
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module ActiveSupport
|
2
|
+
module Cache
|
3
|
+
class MemcachedSnappyStore < Cache::MemCacheStore
|
4
|
+
class UnsupportedOperation < StandardError; end
|
5
|
+
|
6
|
+
def increment(*args)
|
7
|
+
raise UnsupportedOperation.new("increment is not supported by: #{self.class.name}")
|
8
|
+
end
|
9
|
+
|
10
|
+
def decrement(*args)
|
11
|
+
raise UnsupportedOperation.new("decrement is not supported by: #{self.class.name}")
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def write_entry(key, entry, options)
|
17
|
+
# normally unless_exist would make this method use add, add will not make sense on compressed entries
|
18
|
+
raise UnsupportedOperation.new("unless_exist would try to use the unsupported add method") if options && options[:unless_exist]
|
19
|
+
|
20
|
+
value = options[:raw] ? entry.value.to_s : entry
|
21
|
+
expires_in = options[:expires_in].to_i
|
22
|
+
if expires_in > 0 && !options[:raw]
|
23
|
+
# Set the memcache expire a few minutes in the future to support race condition ttls on read
|
24
|
+
expires_in += 5.minutes
|
25
|
+
end
|
26
|
+
|
27
|
+
serialized_value = Marshal.dump(value)
|
28
|
+
serialized_compressed_value = Snappy.deflate(serialized_value)
|
29
|
+
|
30
|
+
response = @data.set(escape_key(key), serialized_compressed_value, expires_in, true)
|
31
|
+
response == Response::STORED
|
32
|
+
rescue MemCache::MemCacheError => e
|
33
|
+
logger.error("MemCacheError (#{e}): #{e.message}") if logger
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def deserialize_entry_with_snappy(*args)
|
39
|
+
compressed_value = args.first
|
40
|
+
decompressed_value = compressed_value.nil? ? compressed_value : Snappy.inflate(compressed_value)
|
41
|
+
args[0] = decompressed_value
|
42
|
+
deserialize_entry_without_snappy(*args)
|
43
|
+
end
|
44
|
+
|
45
|
+
alias_method_chain :deserialize_entry, :snappy
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.authors = ["Camilo Lopez"]
|
5
|
+
gem.email = ["camilo@camilolopez.com"]
|
6
|
+
gem.description = %q{Memcached store that will compress all entries using snappy}
|
7
|
+
gem.summary = %q{Memcached store that will compress all entries using snappy}
|
8
|
+
gem.homepage = ""
|
9
|
+
|
10
|
+
gem.files = `git ls-files`.split($\)
|
11
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
12
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
13
|
+
gem.name = "memcached_snappy_store"
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
gem.version = '0.0.1'
|
16
|
+
gem.add_runtime_dependency "activesupport", "~>3.2.12"
|
17
|
+
gem.add_runtime_dependency "snappy", "0.0.4"
|
18
|
+
gem.add_development_dependency "i18n"
|
19
|
+
gem.add_development_dependency "rake"
|
20
|
+
gem.add_development_dependency "minitest"
|
21
|
+
gem.add_development_dependency "mocha"
|
22
|
+
gem.add_development_dependency "timecop"
|
23
|
+
gem.add_development_dependency "memcache"
|
24
|
+
gem.add_development_dependency "memcache-client"
|
25
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'snappy'
|
2
|
+
require 'memcache'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'active_support/cache'
|
5
|
+
require 'active_support'
|
6
|
+
require 'mocha/setup'
|
7
|
+
require 'timecop'
|
8
|
+
|
9
|
+
|
10
|
+
class TestMemcachedSnappyStore < ActiveSupport::TestCase
|
11
|
+
|
12
|
+
setup do
|
13
|
+
@cache = ActiveSupport::Cache.lookup_store(:memcached_snappy_store)
|
14
|
+
@cache.clear
|
15
|
+
end
|
16
|
+
|
17
|
+
test "test should not allow increment" do
|
18
|
+
assert_raise(ActiveSupport::Cache::MemcachedSnappyStore::UnsupportedOperation) do
|
19
|
+
@cache.increment('foo')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
test "should not allow decrement" do
|
24
|
+
assert_raise(ActiveSupport::Cache::MemcachedSnappyStore::UnsupportedOperation) do
|
25
|
+
@cache.decrement('foo')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
test "write should not allow the implicit add operation when unless_exist is passed to write" do
|
30
|
+
assert_raise(ActiveSupport::Cache::MemcachedSnappyStore::UnsupportedOperation) do
|
31
|
+
@cache.write('foo', 'bar', :unless_exist => true)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
test "should use snappy to write cache entries" do
|
36
|
+
# Freezing time so created_at is the same in entry and the entry created
|
37
|
+
# internally and assert_equal between the raw data in the cache and the
|
38
|
+
# compressed explicitly makes sense
|
39
|
+
Timecop.freeze do
|
40
|
+
entry_value = { :omg => 'data' }
|
41
|
+
entry = ActiveSupport::Cache::Entry.new(entry_value)
|
42
|
+
key = 'moarponies'
|
43
|
+
assert @cache.write(key, entry_value)
|
44
|
+
|
45
|
+
serialized_entry = Marshal.dump(entry)
|
46
|
+
serialized_compressed_entry = Snappy.deflate(serialized_entry)
|
47
|
+
actual_cache_value = @cache.instance_eval{ @data.get(key, true ) }
|
48
|
+
|
49
|
+
assert_equal serialized_compressed_entry, actual_cache_value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
test "should use snappy to read cache entries" do
|
54
|
+
entry_value = { :omg => 'data' }
|
55
|
+
key = 'ponies'
|
56
|
+
|
57
|
+
@cache.write(key, entry_value)
|
58
|
+
cache_entry = ActiveSupport::Cache::Entry.new(entry_value)
|
59
|
+
serialized_cached_entry = Marshal.dump(cache_entry)
|
60
|
+
|
61
|
+
Snappy.expects(:inflate).returns(serialized_cached_entry)
|
62
|
+
assert_equal entry_value, @cache.read(key)
|
63
|
+
end
|
64
|
+
|
65
|
+
test "should skip snappy to reading not found" do
|
66
|
+
key = 'ponies2'
|
67
|
+
Snappy.expects(:inflate).never
|
68
|
+
assert_nil @cache.read(key)
|
69
|
+
end
|
70
|
+
|
71
|
+
test "should use snappy to multi read cache entries but not on missing entries" do
|
72
|
+
keys = %w{ one tow three }
|
73
|
+
values = keys.map{ |k| k * 10 }
|
74
|
+
entries = values.map{ |v| ActiveSupport::Cache::Entry.new(v) }
|
75
|
+
|
76
|
+
keys.each_with_index{ |k, i| @cache.write(k, values[i]) }
|
77
|
+
|
78
|
+
keys_and_missing = keys << 'missing'
|
79
|
+
|
80
|
+
Snappy.expects(:inflate).times(3).returns(*entries)
|
81
|
+
assert_equal values, @cache.read_multi(*keys_and_missing).values
|
82
|
+
end
|
83
|
+
|
84
|
+
test "should use snappy to multi read cache entries" do
|
85
|
+
keys = %w{ one tow three }
|
86
|
+
values = keys.map{ |k| k * 10 }
|
87
|
+
entries = values.map{ |v| ActiveSupport::Cache::Entry.new(v) }
|
88
|
+
|
89
|
+
keys.each_with_index{ |k, i| @cache.write(k, values[i]) }
|
90
|
+
|
91
|
+
Snappy.expects(:inflate).times(3).returns(*entries)
|
92
|
+
assert_equal values, @cache.read_multi(*keys).values
|
93
|
+
end
|
94
|
+
end
|
metadata
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: memcached_snappy_store
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Camilo Lopez
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
type: :runtime
|
16
|
+
version_requirements: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.12
|
22
|
+
name: activesupport
|
23
|
+
prerelease: false
|
24
|
+
requirement: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.2.12
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - '='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.0.4
|
38
|
+
name: snappy
|
39
|
+
prerelease: false
|
40
|
+
requirement: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - '='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.0.4
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
type: :development
|
48
|
+
version_requirements: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
name: i18n
|
55
|
+
prerelease: false
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
type: :development
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
name: rake
|
71
|
+
prerelease: false
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
type: :development
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
name: minitest
|
87
|
+
prerelease: false
|
88
|
+
requirement: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
type: :development
|
96
|
+
version_requirements: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
name: mocha
|
103
|
+
prerelease: false
|
104
|
+
requirement: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
type: :development
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
name: timecop
|
119
|
+
prerelease: false
|
120
|
+
requirement: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
type: :development
|
128
|
+
version_requirements: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
name: memcache
|
135
|
+
prerelease: false
|
136
|
+
requirement: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
type: :development
|
144
|
+
version_requirements: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
name: memcache-client
|
151
|
+
prerelease: false
|
152
|
+
requirement: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
description: Memcached store that will compress all entries using snappy
|
159
|
+
email:
|
160
|
+
- camilo@camilolopez.com
|
161
|
+
executables: []
|
162
|
+
extensions: []
|
163
|
+
extra_rdoc_files: []
|
164
|
+
files:
|
165
|
+
- .gitignore
|
166
|
+
- Gemfile
|
167
|
+
- LICENSE
|
168
|
+
- README.md
|
169
|
+
- Rakefile
|
170
|
+
- lib/active_support/cache/memcached_snappy_store.rb
|
171
|
+
- lib/memcached_snappy_store.rb
|
172
|
+
- memcached_snappy_store.gemspec
|
173
|
+
- test/test_memcached_snappy_store.rb
|
174
|
+
homepage: ''
|
175
|
+
licenses: []
|
176
|
+
post_install_message:
|
177
|
+
rdoc_options: []
|
178
|
+
require_paths:
|
179
|
+
- lib
|
180
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
181
|
+
none: false
|
182
|
+
requirements:
|
183
|
+
- - ! '>='
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
segments:
|
186
|
+
- 0
|
187
|
+
hash: 4438705946747853744
|
188
|
+
version: '0'
|
189
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
|
+
none: false
|
191
|
+
requirements:
|
192
|
+
- - ! '>='
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
segments:
|
195
|
+
- 0
|
196
|
+
hash: 4438705946747853744
|
197
|
+
version: '0'
|
198
|
+
requirements: []
|
199
|
+
rubyforge_project:
|
200
|
+
rubygems_version: 1.8.23
|
201
|
+
signing_key:
|
202
|
+
specification_version: 3
|
203
|
+
summary: Memcached store that will compress all entries using snappy
|
204
|
+
test_files:
|
205
|
+
- test/test_memcached_snappy_store.rb
|