cache_migration 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
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,12 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'bundler/gem_tasks'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/*_test.rb']
8
+ t.verbose = true
9
+ t.warning = true
10
+ end
11
+
12
+ task :default => :test
@@ -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