atomic_mem_cache_store 0.0.3 → 0.1.0
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.
- checksums.yaml +4 -4
- data/.travis.yml +8 -0
- data/README.md +17 -7
- data/VERSION +1 -1
- data/atomic_mem_cache_store.gemspec +4 -3
- data/lib/atomic_dalli_store.rb +10 -0
- data/lib/atomic_mem_cache_store.rb +3 -44
- data/lib/atomic_store.rb +55 -0
- data/spec/lib/atomic_dalli_store_spec.rb +5 -0
- data/spec/lib/atomic_mem_cache_store_spec.rb +1 -79
- data/spec/spec_helper.rb +2 -0
- data/spec/support/shared_atomic_store_spec.rb +83 -0
- metadata +30 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d0b40d56a1ff47b39cc4c91ecb5e0f20191b3b0
|
4
|
+
data.tar.gz: 8468c58414e2629320a7e9be9d92baf085fa1ec0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2fd8b0d3d5ef67121c1291b68e936c9a99693483337b215b7fbe2c5b09f37bf422823d28350f1e1d11a48643a00e7bd02466e32256dc95f99aece25612eb207
|
7
|
+
data.tar.gz: 0c1f63889d399a11b9272bcb95a0c384d38b48a62d82e3f0931f70597386bc63fa56c9653fdaf1215deb572863089e04f8ab0f3df3b52251cb083fad7171e560
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
# AtomicMemCacheStore
|
1
|
+
# AtomicMemCacheStore [](https://travis-ci.org/nel/atomic_mem_cache_store)
|
2
2
|
|
3
3
|
## Why ?
|
4
4
|
|
5
5
|
Anyone caching slow content on a website with moderate to heavy traffic will
|
6
|
-
sooner of later be a victim of the [thundering
|
6
|
+
sooner of later be a victim of the [thundering herd issue](http://en.wikipedia.org/wiki/Thundering_herd_problem) also called Dog-pile Effect.
|
7
7
|
|
8
8
|
Basically cache invalidation of a high traffic page will trigger several concurrent cache recalculation that could lead to pikes of load and transient slow down of your architecture.
|
9
9
|
|
@@ -23,17 +23,27 @@ Install the gem
|
|
23
23
|
|
24
24
|
or add it to your Gemfile
|
25
25
|
|
26
|
-
|
26
|
+
gem 'atomic_mem_cache_store'
|
27
27
|
|
28
28
|
Then use it directly
|
29
|
+
|
30
|
+
cache = AtomicMemCacheStore.new
|
31
|
+
cache.write('key', 'value', :expires_in => 10)
|
32
|
+
cache.read('key')
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
34
|
+
Or for Rails add it in your config/environments/<env>.rb
|
35
|
+
|
36
|
+
config.cache_store = :atomic_mem_cache_store, %w( 127.0.0.1 ), { :namespace => "cache:#{Rails.env}" }
|
37
|
+
|
38
|
+
If you want to use Dalli instead, do the following:
|
39
|
+
|
40
|
+
cache = AtomicDalliStore.new
|
41
|
+
cache.write('key', 'value', :expires_in => 10)
|
42
|
+
cache.read('key')
|
33
43
|
|
34
44
|
Or for Rails add it in your config/environments/<env>.rb
|
35
45
|
|
36
|
-
|
46
|
+
config.cache_store = :atomic_dalli_store, %w( 127.0.0.1 ), { :namespace => "cache:#{Rails.env}" }
|
37
47
|
|
38
48
|
It supports the same parameters as [ActiveSupport::Cache::MemCacheStore](http://apidock.com/rails/ActiveSupport/Cache/MemCacheStore)
|
39
49
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.email = ["nel@w3fu.com"]
|
8
8
|
s.homepage = "https://github.com/nel/atomic_mem_cache_store"
|
9
9
|
s.summary = %q{Rails memcached store with atomic expiration}
|
10
|
-
s.description = %q{Rails memcached store optimized for the thundering
|
10
|
+
s.description = %q{Rails memcached store optimized for the thundering herd issue. This limit cache recalculation to a single process while as long as key is not swept by LRU. Drop-in replacement of Rails memcached store.}
|
11
11
|
|
12
12
|
s.rubyforge_project = "atomic_mem_cache_store"
|
13
13
|
|
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.add_development_dependency "rspec"
|
20
20
|
s.add_development_dependency "rake"
|
21
21
|
s.add_development_dependency "iconv"
|
22
|
-
s.
|
23
|
-
s.
|
22
|
+
s.add_development_dependency "dalli", "~> 1.0.4"
|
23
|
+
s.add_development_dependency "memcache-client"
|
24
|
+
s.add_runtime_dependency "activesupport", "~> 2.1"
|
24
25
|
end
|
@@ -1,49 +1,8 @@
|
|
1
1
|
require 'active_support'
|
2
|
+
require 'atomic_store'
|
2
3
|
|
3
4
|
class AtomicMemCacheStore < ActiveSupport::Cache::CompressedMemCacheStore
|
4
|
-
|
5
|
-
NEWLY_STORED = "STORED\r\n"
|
5
|
+
RAW_ARG = true
|
6
6
|
|
7
|
-
|
8
|
-
@grace_period = 90
|
9
|
-
|
10
|
-
def read(key, options = nil)
|
11
|
-
result = super
|
12
|
-
|
13
|
-
if result.present?
|
14
|
-
timer_key = timer_key(key)
|
15
|
-
#check whether the cache is expired
|
16
|
-
if @data.get(timer_key, true).nil?
|
17
|
-
#optimistic lock to avoid concurrent recalculation
|
18
|
-
if @data.add(timer_key, '', self.class.grace_period, true) == NEWLY_STORED
|
19
|
-
#trigger cache recalculation
|
20
|
-
return handle_expired_read(key,result)
|
21
|
-
end
|
22
|
-
#already recalculated or expirated in another process/thread
|
23
|
-
end
|
24
|
-
#key not expired
|
25
|
-
end
|
26
|
-
result
|
27
|
-
end
|
28
|
-
|
29
|
-
def write(key, value, options = nil)
|
30
|
-
expiry = (options && options[:expires_in]) || 0
|
31
|
-
#extend write expiration period and reset expiration timer
|
32
|
-
options[:expires_in] = expiry + 2*self.class.grace_period unless expiry.zero?
|
33
|
-
@data.set(timer_key(key), '', expiry, true)
|
34
|
-
super
|
35
|
-
end
|
36
|
-
|
37
|
-
protected
|
38
|
-
|
39
|
-
#to be overidden for something else than synchronous cache recalculation
|
40
|
-
def handle_expired_read(key,result)
|
41
|
-
nil
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def timer_key(key)
|
47
|
-
"tk:#{key}"
|
48
|
-
end
|
7
|
+
include AtomicStore
|
49
8
|
end
|
data/lib/atomic_store.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module AtomicStore
|
2
|
+
VERSION = File.read(File.join(File.dirname(__FILE__),'..','VERSION') ).strip
|
3
|
+
NEWLY_STORED = "STORED\r\n"
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
attr_accessor :grace_period
|
7
|
+
end
|
8
|
+
|
9
|
+
@grace_period = 90
|
10
|
+
|
11
|
+
def self.included(base)
|
12
|
+
@raw_arg = base::RAW_ARG
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
def read(key, options = nil)
|
17
|
+
result = super
|
18
|
+
|
19
|
+
if result.present?
|
20
|
+
timer_key = timer_key(key)
|
21
|
+
#check whether the cache is expired
|
22
|
+
if @data.get(timer_key, true).nil?
|
23
|
+
#optimistic lock to avoid concurrent recalculation
|
24
|
+
if @data.add(timer_key, '', self.class.grace_period, @raw_arg) == NEWLY_STORED
|
25
|
+
#trigger cache recalculation
|
26
|
+
return handle_expired_read(key,result)
|
27
|
+
end
|
28
|
+
#already recalculated or expirated in another process/thread
|
29
|
+
end
|
30
|
+
#key not expired
|
31
|
+
end
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def write(key, value, options = nil)
|
36
|
+
expiry = (options && options[:expires_in]) || 0
|
37
|
+
#extend write expiration period and reset expiration timer
|
38
|
+
options[:expires_in] = expiry + 2*self.class.grace_period unless expiry.zero?
|
39
|
+
@data.set(timer_key(key), '', expiry, @raw_arg)
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
#to be overidden for something else than synchronous cache recalculation
|
46
|
+
def handle_expired_read(key,result)
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def timer_key(key)
|
53
|
+
"tk:#{key}"
|
54
|
+
end
|
55
|
+
end
|
@@ -1,83 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe AtomicMemCacheStore do
|
4
|
-
|
5
|
-
begin
|
6
|
-
@store = AtomicMemCacheStore.new('127.0.0.1', :namespace => "spec-atomic")
|
7
|
-
@store.read('test')
|
8
|
-
rescue MemCache::MemCacheError
|
9
|
-
puts "You need a real memcache server to execute the specs, you can run those test on production server, this won't flush your memcache"
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
before(:each) do
|
14
|
-
@seed = "#{Time.now.to_i}#{rand(1000000000000000000000000)}"
|
15
|
-
AtomicMemCacheStore.grace_period = 90
|
16
|
-
end
|
17
|
-
|
18
|
-
def prefix_key(value)
|
19
|
-
"#{@seed}#{value}"
|
20
|
-
end
|
21
|
-
|
22
|
-
describe "with expiry" do
|
23
|
-
it "returns nil when key has not been set" do
|
24
|
-
@store.read(prefix_key('unknown')).should be_nil
|
25
|
-
end
|
26
|
-
|
27
|
-
it "returns value when key has been set and is not expired" do
|
28
|
-
key = prefix_key('not-expired')
|
29
|
-
@store.write(key, true, :expires_in => 10)
|
30
|
-
@store.read(key).should be
|
31
|
-
end
|
32
|
-
|
33
|
-
it "returns new value when key is rewriten" do
|
34
|
-
key = prefix_key('not-expired')
|
35
|
-
@store.write(key, 1, :expires_in => 10)
|
36
|
-
@store.write(key, 2, :expires_in => 10)
|
37
|
-
@store.read(key).should be 2
|
38
|
-
end
|
39
|
-
|
40
|
-
it "returns nil once when key is expired, and then the old value" do
|
41
|
-
key = prefix_key('expired')
|
42
|
-
|
43
|
-
@store.write(key, 1, :expires_in => 1)
|
44
|
-
sleep 2
|
45
|
-
@store.read(key).should be_nil
|
46
|
-
@store.read(key).should be 1
|
47
|
-
@store.read(key).should be 1
|
48
|
-
end
|
49
|
-
|
50
|
-
it "returns nil when 2 times the grace period is passed" do
|
51
|
-
AtomicMemCacheStore.grace_period = 1
|
52
|
-
key = prefix_key('expired')
|
53
|
-
|
54
|
-
@store.write(key, 1, :expires_in => 1)
|
55
|
-
sleep 2
|
56
|
-
@store.read(key).should be_nil
|
57
|
-
@store.read(key).should be 1
|
58
|
-
sleep 2
|
59
|
-
@store.read(key).should be_nil
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
describe "without expiry" do
|
64
|
-
it "returns nil when key has not been set" do
|
65
|
-
@store.read(prefix_key('unknown')).should be_nil
|
66
|
-
end
|
67
|
-
|
68
|
-
it "returns value when key is set" do
|
69
|
-
key = prefix_key('key-without-expiry')
|
70
|
-
|
71
|
-
@store.write(key, 1)
|
72
|
-
@store.read(key).should be 1
|
73
|
-
end
|
74
|
-
|
75
|
-
it "returns new value when key is rewriten" do
|
76
|
-
key = prefix_key('key-without-expiry')
|
77
|
-
|
78
|
-
@store.write(key, 1)
|
79
|
-
@store.write(key, 2)
|
80
|
-
@store.read(key).should be 2
|
81
|
-
end
|
82
|
-
end
|
4
|
+
it_behaves_like 'an atomic store'
|
83
5
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,83 @@
|
|
1
|
+
shared_examples "an atomic store" do
|
2
|
+
|
3
|
+
before(:all) do
|
4
|
+
begin
|
5
|
+
@store = described_class.new('127.0.0.1', :namespace => "spec-atomic")
|
6
|
+
@store.read('test')
|
7
|
+
rescue MemCache::MemCacheError
|
8
|
+
puts "You need a real memcache server to execute the specs, you can run those test on production server, this won't flush your memcache"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
before(:each) do
|
13
|
+
@seed = "#{Time.now.to_i}#{rand(1000000000000000000000000)}"
|
14
|
+
described_class.grace_period = 90
|
15
|
+
end
|
16
|
+
|
17
|
+
def prefix_key(value)
|
18
|
+
"#{@seed}#{value}"
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "with expiry" do
|
22
|
+
it "returns nil when key has not been set" do
|
23
|
+
@store.read(prefix_key('unknown')).should be_nil
|
24
|
+
end
|
25
|
+
|
26
|
+
it "returns value when key has been set and is not expired" do
|
27
|
+
key = prefix_key('not-expired')
|
28
|
+
@store.write(key, true, :expires_in => 10)
|
29
|
+
@store.read(key).should be
|
30
|
+
end
|
31
|
+
|
32
|
+
it "returns new value when key is rewriten" do
|
33
|
+
key = prefix_key('not-expired')
|
34
|
+
@store.write(key, 1, :expires_in => 10)
|
35
|
+
@store.write(key, 2, :expires_in => 10)
|
36
|
+
@store.read(key).should be 2
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns nil once when key is expired, and then the old value" do
|
40
|
+
key = prefix_key('expired')
|
41
|
+
|
42
|
+
@store.write(key, 1, :expires_in => 1)
|
43
|
+
sleep 2
|
44
|
+
@store.read(key).should be_nil
|
45
|
+
@store.read(key).should be 1
|
46
|
+
@store.read(key).should be 1
|
47
|
+
end
|
48
|
+
|
49
|
+
it "returns nil when 2 times the grace period is passed" do
|
50
|
+
described_class.grace_period = 1
|
51
|
+
key = prefix_key('expired')
|
52
|
+
|
53
|
+
@store.write(key, 1, :expires_in => 1)
|
54
|
+
sleep 2
|
55
|
+
@store.read(key).should be_nil
|
56
|
+
@store.read(key).should be 1
|
57
|
+
sleep 2
|
58
|
+
@store.read(key).should be_nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "without expiry" do
|
63
|
+
it "returns nil when key has not been set" do
|
64
|
+
@store.read(prefix_key('unknown')).should be_nil
|
65
|
+
end
|
66
|
+
|
67
|
+
it "returns value when key is set" do
|
68
|
+
key = prefix_key('key-without-expiry')
|
69
|
+
|
70
|
+
@store.write(key, 1)
|
71
|
+
@store.read(key).should be 1
|
72
|
+
end
|
73
|
+
|
74
|
+
it "returns new value when key is rewriten" do
|
75
|
+
key = prefix_key('key-without-expiry')
|
76
|
+
|
77
|
+
@store.write(key, 1)
|
78
|
+
@store.write(key, 2)
|
79
|
+
@store.read(key).should be 2
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: atomic_mem_cache_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Renaud (Nel) Morvan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -53,19 +53,19 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: dalli
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ~>
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
62
|
-
type: :
|
61
|
+
version: 1.0.4
|
62
|
+
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ~>
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 1.0.4
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: memcache-client
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -73,14 +73,28 @@ dependencies:
|
|
73
73
|
- - '>='
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
|
-
type: :
|
76
|
+
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - '>='
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
-
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activesupport
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.1'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.1'
|
97
|
+
description: Rails memcached store optimized for the thundering herd issue. This limit
|
84
98
|
cache recalculation to a single process while as long as key is not swept by LRU.
|
85
99
|
Drop-in replacement of Rails memcached store.
|
86
100
|
email:
|
@@ -90,14 +104,19 @@ extensions: []
|
|
90
104
|
extra_rdoc_files: []
|
91
105
|
files:
|
92
106
|
- .gitignore
|
107
|
+
- .travis.yml
|
93
108
|
- Gemfile
|
94
109
|
- README.md
|
95
110
|
- Rakefile
|
96
111
|
- VERSION
|
97
112
|
- atomic_mem_cache_store.gemspec
|
113
|
+
- lib/atomic_dalli_store.rb
|
98
114
|
- lib/atomic_mem_cache_store.rb
|
115
|
+
- lib/atomic_store.rb
|
116
|
+
- spec/lib/atomic_dalli_store_spec.rb
|
99
117
|
- spec/lib/atomic_mem_cache_store_spec.rb
|
100
118
|
- spec/spec_helper.rb
|
119
|
+
- spec/support/shared_atomic_store_spec.rb
|
101
120
|
homepage: https://github.com/nel/atomic_mem_cache_store
|
102
121
|
licenses: []
|
103
122
|
metadata: {}
|
@@ -117,10 +136,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
136
|
version: '0'
|
118
137
|
requirements: []
|
119
138
|
rubyforge_project: atomic_mem_cache_store
|
120
|
-
rubygems_version: 2.
|
139
|
+
rubygems_version: 2.1.2
|
121
140
|
signing_key:
|
122
141
|
specification_version: 4
|
123
142
|
summary: Rails memcached store with atomic expiration
|
124
143
|
test_files:
|
144
|
+
- spec/lib/atomic_dalli_store_spec.rb
|
125
145
|
- spec/lib/atomic_mem_cache_store_spec.rb
|
126
146
|
- spec/spec_helper.rb
|
147
|
+
- spec/support/shared_atomic_store_spec.rb
|