atomic_mem_cache_store 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 77ba4d9c1bc102bcbde344a6d09967c1e39ffbb1
4
- data.tar.gz: 01b0150dca2282c8552ed32e5fc61871e2b075d0
3
+ metadata.gz: 3d0b40d56a1ff47b39cc4c91ecb5e0f20191b3b0
4
+ data.tar.gz: 8468c58414e2629320a7e9be9d92baf085fa1ec0
5
5
  SHA512:
6
- metadata.gz: ed9fa3ba3fdb4ffb7f43f23178043ce7d632213f3b456c8c670095c9b43d30911dbf6e57a7a03151e04363b1d83ae88ee6b13252177548f985d04cfe50ec0597
7
- data.tar.gz: 0424e91427c574aec7d0e75a439ac195ad990f8d1d4b80dcdd4b39174f5995f5245df2aeb7804c0e02c46f553a6482a5ebc73586c738e104ac1ff60a7e686cb4
6
+ metadata.gz: b2fd8b0d3d5ef67121c1291b68e936c9a99693483337b215b7fbe2c5b09f37bf422823d28350f1e1d11a48643a00e7bd02466e32256dc95f99aece25612eb207
7
+ data.tar.gz: 0c1f63889d399a11b9272bcb95a0c384d38b48a62d82e3f0931f70597386bc63fa56c9653fdaf1215deb572863089e04f8ab0f3df3b52251cb083fad7171e560
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ - 1.9.2
6
+ - 1.8.7
7
+ - ree
8
+ services: memcached
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # AtomicMemCacheStore
1
+ # AtomicMemCacheStore [![Build Status](https://travis-ci.org/nel/atomic_mem_cache_store.png?branch=master)](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 herb issue](http://en.wikipedia.org/wiki/Thundering_herd_problem) also called Dog-pile Effect.
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
- gem 'atomic_mem_cache_store'
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
- cache = AtomicMemCacheStore.new
31
- cache.write('key', 'value', :expires_in => 10)
32
- cache.read('key')
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
- config.cache_store = :atomic_mem_cache_store, %w( 127.0.0.1 ), { :namespace => "cache:#{Rails.env}" }
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.3
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 herb 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.}
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.add_runtime_dependency "activesupport", "~>2.1"
23
- s.add_runtime_dependency "memcache-client"
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
@@ -0,0 +1,10 @@
1
+ require 'dalli'
2
+ require 'dalli/memcache-client'
3
+ require 'active_support/cache/dalli_store23'
4
+ require 'atomic_store'
5
+
6
+ class AtomicDalliStore < ActiveSupport::Cache::DalliStore
7
+ RAW_ARG = { :raw => true }
8
+
9
+ include AtomicStore
10
+ end
@@ -1,49 +1,8 @@
1
1
  require 'active_support'
2
+ require 'atomic_store'
2
3
 
3
4
  class AtomicMemCacheStore < ActiveSupport::Cache::CompressedMemCacheStore
4
- VERSION = File.read(File.join(File.dirname(__FILE__),'..','VERSION') ).strip
5
- NEWLY_STORED = "STORED\r\n"
5
+ RAW_ARG = true
6
6
 
7
- class << self; attr_accessor :grace_period; end
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
@@ -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
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe AtomicDalliStore do
4
+ it_behaves_like 'an atomic store'
5
+ end
@@ -1,83 +1,5 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe AtomicMemCacheStore do
4
- before(:all) do
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
@@ -6,3 +6,5 @@ rescue LoadError
6
6
  require 'rspec'
7
7
  end
8
8
  require File.expand_path(File.dirname(__FILE__) + '/../lib/atomic_mem_cache_store')
9
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/atomic_dalli_store')
10
+ Dir["./spec/support/**/*.rb"].sort.each {|f| require f}
@@ -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.3
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-08-29 00:00:00.000000000 Z
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: activesupport
56
+ name: dalli
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ~>
60
60
  - !ruby/object:Gem::Version
61
- version: '2.1'
62
- type: :runtime
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: '2.1'
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: :runtime
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
- description: Rails memcached store optimized for the thundering herb issue. This limit
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.0.3
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