cache_migration 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile +3 -0
- data/README.md +40 -0
- data/Rakefile +12 -0
- data/cache_migration.gemspec +17 -0
- data/lib/cache_migration.rb +82 -0
- data/test/cache_migration_test.rb +83 -0
- metadata +66 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Gemfile.lock
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
cache_migration
|
2
|
+
===============
|
3
|
+
|
4
|
+
CacheMigration is an ActiveSupport::Cache::Store implementation to aid in the transition between a hot cache server and a new cold cache server.
|
5
|
+
|
6
|
+
If you don't have a process for warming a new cache server, this process can be difficult. Switching your application "cold-turkey" to a new cache server can have a detrimental effect on performance and usability.
|
7
|
+
|
8
|
+
The CacheMigration gem will allow you to keep both the old and new cache servers online for a transition period, while the new cache server gradually becomes warmed through normal usage.
|
9
|
+
|
10
|
+
During the transition period, all writes will be sent to both cache servers.
|
11
|
+
|
12
|
+
When a read occurs, CacheMigration will first try to read from the new server. If there is a miss, the gem will fall back to the old server. If there is a hit on the new server, the data will be written back to the new server, thus warming it.
|
13
|
+
|
14
|
+
After enough time, when the new cache server is sufficiently warm, you can remove this gem and move back to using your normal cache interface. The old servers can be safely taken offline.
|
15
|
+
|
16
|
+
Gemfile
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'cache_migration'
|
20
|
+
```
|
21
|
+
|
22
|
+
production.rb
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require 'active_support/cache/dalli_store'
|
26
|
+
|
27
|
+
old_cache = ActiveSupport::Cache::DalliStore.new(
|
28
|
+
ENV["OLD_MEMCACHE_SERVERS"].split(","),
|
29
|
+
:username => ENV['OLD_MEMCACHE_USERNAME'],
|
30
|
+
:password => ENV['OLD_MEMCACHE_PASSWORD'])
|
31
|
+
|
32
|
+
new_cache = ActiveSupport::Cache::DalliStore.new(
|
33
|
+
ENV["NEW_MEMCACHE_SERVERS"].split(","),
|
34
|
+
:username => ENV['NEW_MEMCACHE_USERNAME'],
|
35
|
+
:password => ENV['NEW_MEMCACHE_PASSWORD'])
|
36
|
+
|
37
|
+
config.cache_store = CacheMigration.new(new_cache, old_cache)
|
38
|
+
```
|
39
|
+
|
40
|
+
This gem was designed to work with DalliStore, but it should work with any API compliant cache store.
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
Gem::Specification.new do |gem|
|
3
|
+
gem.name = "cache_migration"
|
4
|
+
gem.version = '0.0.1'
|
5
|
+
gem.authors = ["Jonathan Baudanza"]
|
6
|
+
gem.email = ["jon@jonb.org"]
|
7
|
+
gem.description = %q{ActiveSupport::Cache::Store implementation to help migrating caches}
|
8
|
+
gem.summary = %q{ActiveSupport::Cache::Store implementation to help migrating caches}
|
9
|
+
gem.homepage = "https://github.com/jbaudanza/cache_migration"
|
10
|
+
|
11
|
+
gem.add_dependency('activesupport', '>= 3.0.0')
|
12
|
+
|
13
|
+
gem.files = `git ls-files`.split($/).collect{ |str| str[0] == '"' ? eval(str) : str }
|
14
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
15
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'active_support/cache'
|
2
|
+
|
3
|
+
class CacheMigration
|
4
|
+
attr_accessor :new_cache, :old_cache
|
5
|
+
|
6
|
+
def initialize(new_cache, old_cache)
|
7
|
+
@new_cache = new_cache
|
8
|
+
@old_cache = old_cache
|
9
|
+
end
|
10
|
+
|
11
|
+
def fetch(name, options=nil)
|
12
|
+
@new_cache.fetch(name, options) do
|
13
|
+
@old_cache.fetch(name, options) do
|
14
|
+
yield
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def read(name, options=nil)
|
20
|
+
value = @new_cache.read(name, options)
|
21
|
+
if !value
|
22
|
+
value = @old_cache.read(name, options)
|
23
|
+
if value
|
24
|
+
@new_cache.write(name, value, options)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
value
|
28
|
+
end
|
29
|
+
|
30
|
+
def write(name, value, options=nil)
|
31
|
+
@new_cache.write(name, value, options)
|
32
|
+
@old_cache.write(name, value, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def exist?(name, options=nil)
|
36
|
+
@new_cache.exist?(name, options) || @old_cache.exist?(name, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete(name, options=nil)
|
40
|
+
@new_cache.delete(name, options)
|
41
|
+
@old_cache.delete(name, options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def read_multi(*names)
|
45
|
+
@new_cache.read_multi(*names).tap do |result|
|
46
|
+
if result.length < names.length
|
47
|
+
result.reverse_merge!(@old_cache.read_multi(*names))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch_multi(*names, &proc)
|
53
|
+
@new_cache.fetch_multi(*names) do |key|
|
54
|
+
@old_cache.fetch(key, &proc)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def clear(options = nil)
|
59
|
+
@new_cache.clear(options)
|
60
|
+
@old_cache.clear(options)
|
61
|
+
end
|
62
|
+
|
63
|
+
def cleanup(options = nil)
|
64
|
+
@new_cache.cleanup(options)
|
65
|
+
@old_cache.cleanup(options)
|
66
|
+
end
|
67
|
+
|
68
|
+
def increment(name, amount = 1, options = nil)
|
69
|
+
@new_cache.increment(name, amount, options)
|
70
|
+
@old_cache.increment(name, amount, options)
|
71
|
+
end
|
72
|
+
|
73
|
+
def decrement(name, amount = 1, options = nil)
|
74
|
+
@new_cache.decrement(name, amount, options)
|
75
|
+
@old_cache.decrement(name, amount, options)
|
76
|
+
end
|
77
|
+
|
78
|
+
def delete_matched(matcher, options = nil)
|
79
|
+
@new_cache.delete_matched(matcher, options)
|
80
|
+
@old_cache.delete_matched(matcher, options)
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'minitest/unit'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'cache_migration'
|
4
|
+
require 'active_support/cache/memory_store'
|
5
|
+
|
6
|
+
class CacheMigrationTest < MiniTest::Unit::TestCase
|
7
|
+
def setup
|
8
|
+
@old_cache = ActiveSupport::Cache::MemoryStore.new
|
9
|
+
@new_cache = ActiveSupport::Cache::MemoryStore.new
|
10
|
+
@migration = CacheMigration.new(@new_cache, @old_cache)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_it_writes_to_both_caches
|
14
|
+
@migration.write('hello', 'world')
|
15
|
+
assert_equal 'world', @old_cache.read('hello')
|
16
|
+
assert_equal 'world', @new_cache.read('hello')
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_hit
|
20
|
+
@new_cache.write('hello', 'world')
|
21
|
+
assert_equal 'world', @migration.read('hello')
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_half_miss
|
25
|
+
@old_cache.write('hello', 'world')
|
26
|
+
assert_equal 'world', @migration.read('hello')
|
27
|
+
assert_equal 'world', @new_cache.read('hello')
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_full_miss
|
31
|
+
assert_nil @migration.read('hello')
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_exists
|
35
|
+
@old_cache.write('hello', 'world')
|
36
|
+
@new_cache.write('foo', 'bar')
|
37
|
+
|
38
|
+
assert @migration.exist?('hello')
|
39
|
+
assert @migration.exist?('foo')
|
40
|
+
assert !@migration.exist?('goodbye')
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_fetch
|
44
|
+
@migration.fetch('hello') { 'world' }
|
45
|
+
assert_equal 'world', @old_cache.read('hello')
|
46
|
+
assert_equal 'world', @new_cache.read('hello')
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_delete
|
50
|
+
@old_cache.write('hello', 'world')
|
51
|
+
@new_cache.write('hello', 'world')
|
52
|
+
@migration.delete('hello')
|
53
|
+
assert_nil @new_cache.read('hello')
|
54
|
+
assert_nil @old_cache.read('hello')
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_read_multi
|
58
|
+
@new_cache.write('hello', 'world')
|
59
|
+
@old_cache.write('foo', 'bar')
|
60
|
+
|
61
|
+
expected = {'hello' => 'world', 'foo' => 'bar'}
|
62
|
+
|
63
|
+
assert_equal expected, @migration.read_multi('foo', 'hello')
|
64
|
+
end
|
65
|
+
|
66
|
+
# Commenting this test out because MemoryStore won't support fetch_multi
|
67
|
+
# until Rails 4.1
|
68
|
+
# def test_fetch_multi
|
69
|
+
# expected = {'hello' => 'world', 'foo' => 'bar'}
|
70
|
+
|
71
|
+
# @migration.fetch_multi('hello', 'world') do |key|
|
72
|
+
# case key
|
73
|
+
# when 'hello'; 'world'
|
74
|
+
# when 'foo'; 'bar'
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
|
78
|
+
# assert_equal 'world', @old_cache.read('hello')
|
79
|
+
# assert_equal 'world', @new_cache.read('hello')
|
80
|
+
# assert_equal 'foo', @old_cache.read('black')
|
81
|
+
# assert_equal 'foo', @new_cache.read('black')
|
82
|
+
# end
|
83
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cache_migration
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jonathan Baudanza
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-26 00:00:00.000000000 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
requirement: &2157760900 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 3.0.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *2157760900
|
26
|
+
description: ActiveSupport::Cache::Store implementation to help migrating caches
|
27
|
+
email:
|
28
|
+
- jon@jonb.org
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- .gitignore
|
34
|
+
- Gemfile
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- cache_migration.gemspec
|
38
|
+
- lib/cache_migration.rb
|
39
|
+
- test/cache_migration_test.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: https://github.com/jbaudanza/cache_migration
|
42
|
+
licenses: []
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.6.2
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: ActiveSupport::Cache::Store implementation to help migrating caches
|
65
|
+
test_files:
|
66
|
+
- test/cache_migration_test.rb
|