redis_migrator 0.0.1 → 0.1.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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/Gemfile +3 -0
  4. data/README.md +13 -1
  5. data/Rakefile +6 -0
  6. data/lib/redis_migrator/redis_helper.rb +5 -36
  7. data/lib/redis_migrator/redis_native_migrator.rb +27 -0
  8. data/lib/redis_migrator/redis_pipe_migrator.rb +66 -0
  9. data/lib/redis_migrator/redis_populator.rb +1 -1
  10. data/lib/redis_migrator.rb +19 -20
  11. data/migrator_benchmark.rb +1 -2
  12. data/redis_migrator.gemspec +8 -4
  13. data/spec/different_redis_type_migrator.rb +67 -0
  14. data/spec/pretested_migrator.rb +47 -0
  15. data/spec/redis_migrator_spec.rb +41 -0
  16. data/spec/redis_native_migrator_spec.rb +44 -0
  17. data/spec/redis_pipe_migrator_spec.rb +51 -0
  18. data/spec/shared_hosts_context.rb +10 -0
  19. data/spec/spec_helper.rb +9 -7
  20. metadata +85 -49
  21. data/spec/migrator_spec.rb +0 -63
  22. data/spec/mock_redis/lib/mock_redis/assertions.rb +0 -13
  23. data/spec/mock_redis/lib/mock_redis/database.rb +0 -432
  24. data/spec/mock_redis/lib/mock_redis/distributed.rb +0 -6
  25. data/spec/mock_redis/lib/mock_redis/exceptions.rb +0 -3
  26. data/spec/mock_redis/lib/mock_redis/expire_wrapper.rb +0 -25
  27. data/spec/mock_redis/lib/mock_redis/hash_methods.rb +0 -118
  28. data/spec/mock_redis/lib/mock_redis/list_methods.rb +0 -187
  29. data/spec/mock_redis/lib/mock_redis/multi_db_wrapper.rb +0 -86
  30. data/spec/mock_redis/lib/mock_redis/set_methods.rb +0 -126
  31. data/spec/mock_redis/lib/mock_redis/string_methods.rb +0 -203
  32. data/spec/mock_redis/lib/mock_redis/transaction_wrapper.rb +0 -80
  33. data/spec/mock_redis/lib/mock_redis/undef_redis_methods.rb +0 -11
  34. data/spec/mock_redis/lib/mock_redis/utility_methods.rb +0 -25
  35. data/spec/mock_redis/lib/mock_redis/version.rb +0 -3
  36. data/spec/mock_redis/lib/mock_redis/zset.rb +0 -110
  37. data/spec/mock_redis/lib/mock_redis/zset_methods.rb +0 -210
  38. data/spec/mock_redis/lib/mock_redis.rb +0 -119
  39. data/spec/redis_helper_spec.rb +0 -58
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 98a618ae2641c4bf6eadc6cae753d8219f0ab039
4
+ data.tar.gz: e07f091488059a7d20f7ce0bdf05fb43566632ef
5
+ SHA512:
6
+ metadata.gz: 7cda9a9894f2a79c4a500eb15cf9ab6f5efe6fcf512112b9dcfe818c0f672ee4e1894d2aae5d03624db69aee3452821cabf1597c3c6f1588f93785bdd6b5711f
7
+ data.tar.gz: f6d7e25a19eff8d52cc6704d528eeb6c5fa03bfd5ea4d8f6ac5fec92ba675b14c55d1c3ff8b5d332511b6c69ef86f784e5e5a6d2a0fc381ca9026a1a24126bd5
data/.gitignore CHANGED
@@ -17,3 +17,4 @@ tmp
17
17
  _yardoc
18
18
  doc/
19
19
  .DS_Store
20
+ .idea/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/README.md CHANGED
@@ -22,7 +22,7 @@ and determines for which keys routes were changed. Then it moves those keys to n
22
22
  old_redis_hosts = ["redis://host1.com:6379", "redis://host2.com:6379"]
23
23
 
24
24
  # a list of redis-urls for a new cluster
25
- old_redis_hosts = ["redis://host1.com:6379", "redis://host2.com:6379", "redis://host3.com:6379"]
25
+ new_redis_hosts = ["redis://host1.com:6379", "redis://host2.com:6379", "redis://host3.com:6379"]
26
26
 
27
27
  migrator = Redis::Migrator.new(old_redis_hosts, new_redis_hosts)
28
28
  migrator.run
@@ -31,5 +31,17 @@ and determines for which keys routes were changed. Then it moves those keys to n
31
31
  * ruby 1.9 or jruby (with --1.9 flag)
32
32
  * redis >=2.4.14 (only on machine where migrator will be running)
33
33
 
34
+ ##Contributing
35
+
36
+ # First fork the project.
37
+ # Then bundle
38
+ bundle
39
+
40
+ # and make sure tests pass
41
+ bundle exec rspec spec
42
+
43
+ # Add features and profit.
44
+ # Send a pull request back to the original repository.
45
+
34
46
  ##TODO
35
47
  * Error handling
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :test => :spec
@@ -1,9 +1,8 @@
1
1
  class Redis
2
2
  module Helper
3
-
4
3
  def to_redis_proto(*cmd)
5
4
  cmd.inject("*#{cmd.length}\r\n") {|acc, arg|
6
- acc << "$#{arg.length}\r\n#{arg}\r\n"
5
+ acc << "$#{arg.to_s.bytesize}\r\n#{arg}\r\n"
7
6
  }
8
7
  end
9
8
 
@@ -13,40 +12,10 @@ class Redis
13
12
  db = path[1..-1].to_i rescue 0
14
13
 
15
14
  {
16
- :host => node.host,
17
- :port => node.port || 6379,
18
- :db => db
15
+ host: node.host,
16
+ port: node.port || 6379,
17
+ db: db
19
18
  }
20
19
  end
21
-
22
- def copy_string(pipe, key)
23
- value = redis.get(key)
24
- pipe << to_redis_proto("SET", key, value)
25
- end
26
-
27
- def copy_hash(pipe, key)
28
- redis.hgetall(key).each do |field, value|
29
- pipe << to_redis_proto("HSET", key, field, value)
30
- end
31
- end
32
-
33
- def copy_list(pipe, key)
34
- redis.lrange(key, 0, -1).each do |value|
35
- pipe << to_redis_proto("LPUSH", key, value)
36
- end
37
- end
38
-
39
- def copy_set(pipe, key)
40
- redis.smembers(key).each do |member|
41
- pipe << to_redis_proto("SADD", key, member)
42
- end
43
- end
44
-
45
- def copy_zset(pipe, key)
46
- redis.zrange(key, 0, -1, :with_scores => true).each_slice(2) do |member, score|
47
- pipe << to_redis_proto("ZADD", key, score, member)
48
- end
49
- end
50
-
51
20
  end
52
- end
21
+ end
@@ -0,0 +1,27 @@
1
+ class Redis
2
+ class NativeMigrator
3
+ def initialize(old_hosts)
4
+ Thread.current[:redis] = Redis::Distributed.new(old_hosts)
5
+ end
6
+
7
+ def redis
8
+ Thread.current[:redis]
9
+ end
10
+
11
+ def migrate(node_options, keys, _)
12
+ new_node_options = { host: node_options[:host],
13
+ port: node_options[:port],
14
+ db: node_options[:db] }
15
+
16
+ grouped_by_old_nodes = keys.group_by do |key|
17
+ redis.node_for(key)
18
+ end
19
+
20
+ grouped_by_old_nodes.each do |old_node, node_keys|
21
+ old_node.pipelined do
22
+ node_keys.each { |key| old_node.migrate(key, new_node_options) }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,66 @@
1
+ class Redis
2
+ class PipeMigrator
3
+ include Redis::Helper
4
+
5
+ def initialize(old_hosts)
6
+ Thread.current[:redis] = Redis::Distributed.new(old_hosts)
7
+ end
8
+
9
+ def redis
10
+ Thread.current[:redis]
11
+ end
12
+
13
+ def migrate(node_options, keys, options)
14
+ host, port, db = node_options[:host], node_options[:port], node_options[:db]
15
+ pipe = IO.popen("redis-cli -h #{host} -p #{port} -n #{db} --pipe", IO::RDWR)
16
+
17
+ keys.each {|key|
18
+ copy_key(pipe, key)
19
+
20
+ #remove key from old node
21
+ redis.node_for(key).del(key) unless options[:do_not_remove]
22
+ }
23
+
24
+ pipe.close
25
+ end
26
+
27
+ # Copy a given Redis key to a Redis pipe
28
+ # @param pipe [IO] a pipe opened redis-cli --pipe
29
+ # @param key [String] a Redis key that needs to be copied
30
+ def copy_key(pipe, key)
31
+ key_type = redis.type(key)
32
+ return false unless ['list', 'hash', 'string', 'set', 'zset'].include?(key_type)
33
+
34
+ self.send("copy_#{key_type}", pipe, key)
35
+ end
36
+
37
+ def copy_string(pipe, key)
38
+ value = redis.get(key)
39
+ pipe << to_redis_proto('SET', key, value)
40
+ end
41
+
42
+ def copy_hash(pipe, key)
43
+ redis.hgetall(key).each do |field, value|
44
+ pipe << to_redis_proto('HSET', key, field, value)
45
+ end
46
+ end
47
+
48
+ def copy_list(pipe, key)
49
+ redis.lrange(key, 0, -1).each do |value|
50
+ pipe << to_redis_proto('RPUSH', key, value)
51
+ end
52
+ end
53
+
54
+ def copy_set(pipe, key)
55
+ redis.smembers(key).reverse.each do |member|
56
+ pipe << to_redis_proto('SADD', key, member)
57
+ end
58
+ end
59
+
60
+ def copy_zset(pipe, key)
61
+ redis.zrange(key, 0, -1, with_scores: true).each do |member, score|
62
+ pipe << to_redis_proto('ZADD', key, score, member)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -33,7 +33,7 @@ class Redis
33
33
 
34
34
  keys.each do |key|
35
35
  size.times.map do |x|
36
- f << to_redis_proto("SADD", key, ::Digest::MD5.hexdigest("f" + x.to_s))
36
+ f << to_redis_proto('SADD', key, ::Digest::MD5.hexdigest('f' + x.to_s))
37
37
  end
38
38
  end
39
39
 
@@ -1,7 +1,8 @@
1
- require 'rubygems'
2
1
  require 'redis'
3
2
  require 'redis/distributed'
4
3
  require_relative 'redis_migrator/redis_helper'
4
+ require_relative 'redis_migrator/redis_pipe_migrator'
5
+ require_relative 'redis_migrator/redis_native_migrator'
5
6
 
6
7
  class Redis
7
8
  class Migrator
@@ -48,19 +49,8 @@ class Redis
48
49
  # @param options [Hash] additional options, such as :do_not_remove => true
49
50
  def migrate_keys(node, keys, options={})
50
51
  return false if keys.empty? || keys.nil?
51
-
52
- Thread.current[:redis] = Redis::Distributed.new(old_hosts)
53
52
 
54
- pipe = IO.popen("redis-cli -h #{node[:host]} -p #{node[:port]} -n #{node[:db]} --pipe", IO::RDWR)
55
-
56
- keys.each {|key|
57
- copy_key(pipe, key)
58
-
59
- #remove key from old node
60
- redis.node_for(key).del(key) unless options[:do_not_remove]
61
- }
62
-
63
- pipe.close
53
+ migrator(options[:do_not_remove]).new(old_hosts).migrate(node, keys, options)
64
54
  end
65
55
 
66
56
  # Runs a migration process for a Redis cluster.
@@ -82,15 +72,24 @@ class Redis
82
72
  threads.each{|t| t.join}
83
73
  end
84
74
 
85
- # Copy a given Redis key to a Redis pipe
86
- # @param pipe [IO] a pipe opened redis-cli --pipe
87
- # @param key [String] a Redis key that needs to be copied
88
- def copy_key(pipe, key)
89
- key_type = old_cluster.type(key)
90
- return false unless ['list', 'hash', 'string', 'set', 'zset'].include?(key_type)
75
+ private
76
+
77
+ def nodes
78
+ old_cluster.nodes + new_cluster.nodes
79
+ end
91
80
 
92
- self.send("copy_#{key_type}", pipe, key)
81
+ def old_nodes
82
+ @old_nodes ||= nodes.select { |node| node.info['redis_version'].to_f < 2.6 }
93
83
  end
94
84
 
85
+ def migrator(keep_original)
86
+ @migrator ||= begin
87
+ if old_nodes.any? || keep_original
88
+ Redis::PipeMigrator
89
+ else
90
+ Redis::NativeMigrator
91
+ end
92
+ end
93
+ end
95
94
  end # class Migrator
96
95
  end # class Redis
@@ -18,5 +18,4 @@ migrator = Redis::Migrator.new(old_redis_hosts, new_redis_hosts)
18
18
  Benchmark.bm do |x|
19
19
  x.report("populate:") { r.populate_cluster(1000, 100) }
20
20
  x.report("migrate:") { migrator.run }
21
- end
22
-
21
+ end
@@ -3,8 +3,8 @@ $:.push File.expand_path("./lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "redis_migrator"
6
- s.version = "0.0.1"
7
- s.date = "2012-07-05"
6
+ s.version = "0.1.1"
7
+ s.date = "2014-04-10"
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ["Artem Yankov"]
10
10
  s.email = ["artem.yankov@gmail.com"]
@@ -16,5 +16,9 @@ Gem::Specification.new do |s|
16
16
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
18
  s.require_paths = ["lib"]
19
- s.add_dependency('redis', '>= 2.2.2')
20
- end
19
+ s.add_dependency('redis', '>= 3.0.0')
20
+ s.add_development_dependency 'rspec', '~> 2.6'
21
+ s.add_development_dependency 'rake'
22
+ s.add_development_dependency 'debugger'
23
+ s.add_development_dependency 'mock_redis'
24
+ end
@@ -0,0 +1,67 @@
1
+ shared_examples 'different redis type migrator' do
2
+ context 'key of type' do
3
+ let(:keys) { [key] }
4
+
5
+ subject { migrator.migrate(node, keys, {}) }
6
+
7
+ context 'string' do
8
+ let(:key) { 'a' }
9
+
10
+ it 'should copy' do
11
+ old_cluster.set(key, 'some_string')
12
+ subject
13
+ destination_cluster.get(key).should == 'some_string'
14
+ end
15
+ end
16
+
17
+
18
+ context 'hash' do
19
+ let(:key) { 'myhash' }
20
+
21
+ it 'should copy' do
22
+ old_cluster.hmset(key,
23
+ 'first_name', 'James',
24
+ 'last_name', 'Randi',
25
+ 'age', '83')
26
+ subject
27
+ destination_cluster.hgetall(key).should == {'first_name' => 'James',
28
+ 'last_name' => 'Randi',
29
+ 'age' => '83'}
30
+ end
31
+ end
32
+
33
+ context 'list' do
34
+ let(:key) { 'mylist' }
35
+
36
+ it 'should copy' do
37
+ ('a'..'z').to_a.each { |val| old_cluster.lpush(key, val) }
38
+ values = old_cluster.lrange(key, 0, -1)
39
+ subject
40
+ destination_cluster.lrange(key, 0, -1).should == values
41
+ end
42
+ end
43
+
44
+ context 'set' do
45
+ let(:key) { 'myset' }
46
+ it 'should copy' do
47
+ ('a'..'z').to_a.each { |val| old_cluster.sadd(key, val) }
48
+ values = old_cluster.smembers(key)
49
+ subject
50
+ destination_cluster.smembers(key).should == values
51
+ end
52
+ end
53
+
54
+ context 'zset' do
55
+ let(:key) { 'myzset' }
56
+ it 'should copy' do
57
+ ('a'..'z').to_a.each { |val| old_cluster.zadd(key, rand(100), val) }
58
+ old_range = old_cluster.zrange(key, 0, -1, with_scores: true).sort
59
+
60
+ subject
61
+
62
+ new_range = destination_cluster.zrange(key, 0, -1, with_scores: true).sort
63
+ new_range.should == old_range
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,47 @@
1
+ shared_context 'common keys' do
2
+ let(:keys) { %w(q s j) }
3
+ let(:node) { { host: 'localhost', port: 6378, db: 1 } }
4
+ let(:options) { {} }
5
+
6
+ before do
7
+ prefill_cluster(old_cluster)
8
+ migrator.migrate(node, keys, options)
9
+ end
10
+
11
+ def common_keys(cluster)
12
+ (cluster.keys('*') & keys).sort
13
+ end
14
+
15
+ subject { common_keys(cluster) }
16
+ end
17
+
18
+ shared_examples 'pretested migrator' do
19
+ include_context 'common keys'
20
+
21
+ context do
22
+ let(:cluster) { destination_cluster }
23
+ it 'should copy given keys to a new cluster' do
24
+ should == %w(j q s)
25
+ end
26
+ end
27
+ end
28
+
29
+ shared_examples 'safe pretested migrator' do
30
+ include_context 'common keys'
31
+
32
+ context do
33
+ let(:cluster) { old_cluster }
34
+
35
+ it 'should remove copied keys from the old redis node' do
36
+ should == []
37
+ end
38
+
39
+ context 'when asked to not remove' do
40
+ let(:options) { { do_not_remove: true } }
41
+ it 'should keep keys on old node' do
42
+ should == ["j", "q", "s"]
43
+ end
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,41 @@
1
+ require_relative 'shared_hosts_context'
2
+
3
+ describe Redis::Migrator do
4
+ include_context 'shared hosts context'
5
+ let(:migrator) { Redis::Migrator.new(old_hosts, new_hosts) }
6
+
7
+ describe '#changed_keys' do
8
+ before { prefill_cluster(migrator.old_cluster) }
9
+
10
+ it 'should show keys which need migration' do
11
+ migrator.changed_keys.should == {'redis://localhost:6377/0' => %w(h q s y j m n o)}
12
+ end
13
+ end
14
+
15
+ describe '#migrator' do
16
+ subject { migrator.send(:migrator, keep_original) }
17
+ let(:keep_original) { false }
18
+
19
+ before do
20
+ allow_any_instance_of(MockRedis).to(
21
+ receive(:info).and_return({ 'redis_version' => version })
22
+ )
23
+ end
24
+
25
+ context 'when all instances are old' do
26
+ let(:version) { '2.4.1' }
27
+ it { should == Redis::PipeMigrator}
28
+ end
29
+
30
+ context 'when all instances are new' do
31
+ let(:version) { '2.6.14' }
32
+ it { should == Redis::NativeMigrator }
33
+
34
+ context 'when asking to preserve data on source' do
35
+ let(:keep_original) { true }
36
+
37
+ it { should == Redis::PipeMigrator }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,44 @@
1
+ require_relative 'shared_hosts_context'
2
+ require_relative 'different_redis_type_migrator'
3
+ require_relative 'pretested_migrator'
4
+
5
+ describe Redis::NativeMigrator do
6
+ let(:migrator) { Redis::NativeMigrator.new(old_hosts) }
7
+ include_context 'shared hosts context'
8
+
9
+ let(:old_cluster) { Redis::Distributed.new(old_hosts) }
10
+ let(:new_cluster) { Redis::Distributed.new(new_hosts) }
11
+
12
+ before { allow(migrator).to receive(:redis).and_return(old_cluster) }
13
+
14
+ describe '#migrate' do
15
+ context do
16
+ let(:node) { new_cluster.node_for(key) }
17
+ let(:source_node) { old_cluster.node_for(key) }
18
+ let(:destination_cluster) { source_node.client.select(10); source_node }
19
+ before do
20
+ allow_any_instance_of(MockRedis).to receive(:migrate) do |key|
21
+ source_node.move(key, 10)
22
+ end
23
+ end
24
+
25
+ it_behaves_like 'different redis type migrator'
26
+ end
27
+
28
+ context do
29
+ let(:old_cluster) { Redis::Distributed.new([old_hosts.first]) }
30
+ let(:source_node) { old_cluster.nodes.first }
31
+ let(:destination_cluster) { source_node.client.select(10); source_node }
32
+
33
+ before do
34
+ allow_any_instance_of(MockRedis).to receive(:migrate) do |key|
35
+ source_node.move(key, 10)
36
+ end
37
+ end
38
+
39
+ it_behaves_like 'pretested migrator'
40
+ # Not supported in Redis < 3.0
41
+ # it_behaves_like 'safe pretested migrator'
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,51 @@
1
+ require_relative 'shared_hosts_context'
2
+ require_relative 'different_redis_type_migrator'
3
+ require_relative 'pretested_migrator'
4
+
5
+ describe Redis::PipeMigrator do
6
+ let(:migrator) { Redis::PipeMigrator.new(old_hosts) }
7
+ include_context 'shared hosts context'
8
+
9
+ let(:old_cluster) { Redis::Distributed.new(old_hosts) }
10
+ let(:destination_cluster) { Redis::Distributed.new(new_hosts) }
11
+ let(:pipe) { PipeMock.new(destination_cluster) }
12
+
13
+ before { allow(migrator).to receive(:redis).and_return(old_cluster) }
14
+
15
+ describe '#migrate' do
16
+ context do
17
+ let(:node) { {} }
18
+ before { expect(IO).to receive(:popen).and_return(pipe) }
19
+ it_behaves_like 'different redis type migrator'
20
+ end
21
+
22
+ context do
23
+ before do
24
+ command = 'redis-cli -h localhost -p 6378 -n 1 --pipe'
25
+ expect(IO).to receive(:popen).with(command, IO::RDWR).and_return(pipe)
26
+ end
27
+
28
+ it_behaves_like 'pretested migrator'
29
+ it_behaves_like 'safe pretested migrator'
30
+ end
31
+ end
32
+
33
+ describe '#copy_key' do
34
+ subject { migrator.copy_key(nil, key) }
35
+
36
+ context 'with unknown key' do
37
+ let(:key) { 'some_key' }
38
+ it { should == false }
39
+ end
40
+
41
+ context 'when known set key' do
42
+ let(:key) { 'a' }
43
+ before { old_cluster.sadd('a', 1) }
44
+
45
+ it 'calls copy_set' do
46
+ expect(migrator).to receive(:copy_set).with(nil, 'a')
47
+ subject
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,10 @@
1
+ shared_context 'shared hosts context' do
2
+ let(:old_hosts) { %w(redis://localhost:6379 redis://localhost:6378) }
3
+ let(:new_hosts) { old_hosts + ['redis://localhost:6377'] }
4
+
5
+ before do
6
+ expect(Redis).to receive(:new).at_least(1).times do |options|
7
+ MockRedis.new(options)
8
+ end
9
+ end
10
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,10 +1,6 @@
1
- $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), "mock_redis/lib"))
2
-
1
+ require 'rspec'
3
2
  require_relative "../lib/redis_migrator.rb"
4
-
5
- # include patched version of mock_redis
6
- # that works with Redis::Distributed
7
- require "mock_redis.rb"
3
+ require 'mock_redis'
8
4
 
9
5
  class PipeMock
10
6
  def initialize(redis)
@@ -26,4 +22,10 @@ class Redis
26
22
  cmd
27
23
  end
28
24
  end
29
- end
25
+ end
26
+
27
+ def prefill_cluster(cluster)
28
+ ('a'..'z').to_a.each do |key|
29
+ (1..5).to_a.each {|val| cluster.sadd(key, val)}
30
+ end
31
+ end