redi 0.0.5 → 0.0.7

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.
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use --create 1.9.3-p0@redi
data/ChangeLog.md CHANGED
@@ -1,3 +1,12 @@
1
+ 0.0.7
2
+ -----
3
+ * Complete redis interface @bleything
4
+ * Improved test coverage
5
+
6
+ 0.0.6
7
+ ------
8
+ * Reorganized project to be easier to navigate
9
+
1
10
  0.0.5
2
11
  ------
3
12
  * add del method
data/Gemfile.lock CHANGED
@@ -1,13 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redi (0.0.2)
4
+ redi (0.0.6)
5
5
  redis
6
6
  redis-namespace
7
7
 
8
8
  GEM
9
9
  remote: http://rubygems.org/
10
10
  specs:
11
+ rake (0.9.2.2)
11
12
  redis (2.2.2)
12
13
  redis-namespace (1.1.0)
13
14
  redis (< 3.0.0)
@@ -16,4 +17,5 @@ PLATFORMS
16
17
  ruby
17
18
 
18
19
  DEPENDENCIES
20
+ rake
19
21
  redi!
data/README.md CHANGED
@@ -1,14 +1,21 @@
1
1
  Redi
2
2
  ----------
3
3
 
4
- Pooled redis puts a layer of indirection between server pool and key ring
5
-
4
+ Pooled redis, add a layer of indirection between server pool and key ring
6
5
 
7
6
  The idea comes from http://blog.zawodny.com/2011/02/26/redis-sharding-at-craigslist/
8
7
 
8
+ - - -
9
+ Install
10
+ ----------
9
11
  gem install redi
10
12
 
11
- Create a configuration file:
13
+ Configure
14
+ ----------
15
+ The configuration should look like a normal redis configuration with the addition of a buckets key.
16
+ This tells redi how many buckets it should hash keys to before mapping them to the configured server.
17
+
18
+ redi.yml:
12
19
 
13
20
  development:
14
21
  - :host: 192.168.0.10
@@ -20,12 +27,37 @@ Create a configuration file:
20
27
  :db: 0
21
28
  :buckets: 65 - 127
22
29
 
23
- The configuration should look like a normal redis configuration with the addition of a buckets key. This tells redi how many buckets it should
24
- hash keys to before mapping them to the configured server. In the example above, it would be possible to scale those 2 servers up to 128 servers without
25
- rekeying, you can follow the 5 steps below to add a new server each time.
26
-
27
- 1. create new server
28
- 2. identify buckets to move to new server
29
- 3. setup new server as slave to replicate
30
- 4. update configuration to point buckets to new server
31
- 5. use bucket key prefixes to prune old keys from old server.
30
+ In the example above, it is possible to scale the 2 configured servers up to 128 servers without
31
+ re-keying.
32
+ - - -
33
+
34
+ Adding a new server can be done as follows:
35
+
36
+ * start a new server process, call it r3.
37
+ * identify buckets to move to r3 from existing server r2.
38
+ * setup r3 as slave to replicate from r2.
39
+ * update configuration to point buckets to r3
40
+
41
+ <pre>
42
+ production:
43
+ - :host: 192.168.0.10 # r1
44
+ :port: 6379
45
+ :db: 0
46
+ :buckets: 0 - 64
47
+ - :host: 192.168.0.11 # r2
48
+ :port: 6380
49
+ :db: 0
50
+ :buckets: 65 - 95
51
+ - :host: 192.168.0.11 # r3
52
+ :port: 6380
53
+ :db: 0
54
+ :buckets: 96 - 127
55
+ </pre>
56
+
57
+ * use bucket key prefixes to prune old keys from r2.
58
+
59
+ How you do this can vary depending on your application, but something like the pseudo code below is the idea:
60
+
61
+ 96..127.times do|i| # NOTE: using redis here not redi as we want to talk to r2 explicitly
62
+ redis.del(redis.keys("n#{i}*"))
63
+ end
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+ Rake::TestTask.new
data/lib/redi.rb CHANGED
@@ -1,33 +1,37 @@
1
- require 'rubygems' if __FILE__ == $0
2
- require 'zlib'
3
- require 'yaml'
4
- require 'redis'
5
- require 'redis/hash_ring'
6
- require 'redis/namespace'
1
+ require 'redi/pool'
7
2
 
8
3
  class Redi
9
4
 
10
- def self.get(key)
11
- pool.redis_by_key(key).get(key)
12
- end
5
+ ### these commands are complicated to distribute across a pool and so
6
+ ### for now are unimplemented. Translation: we are lazy.
7
+ UNIMPLEMENTED_COMMANDS = %w[
8
+ keys move object randomkey rename renamenx eval
9
+ mget mset msetnx
10
+ brpoplpush rpoplpush
11
+ sdiff sdiffstore sinter sinterstore smove sunion sunionstore
12
+ zinterstore zunionstore
13
+ psubscribe publish punsubscribe subscribe unsubscribe
14
+ discard exec multi unwatch watch
15
+ auth echo ping quit select
16
+ bgrewriteaof bgsave config dbsize debug flushdb info lastsave monitor save shutdown slaveof slowlog sync
17
+ ]
13
18
 
14
- def self.set(key, val)
15
- pool.redis_by_key(key).set(key, val)
16
- end
19
+ ### raise exceptions on unimplemented/unknown commands, delegate
20
+ ### everything else down to the actual Redis connections
21
+ def self.method_missing( cmd, *args )
22
+ if UNIMPLEMENTED_COMMANDS.include?( cmd.to_s )
23
+ raise NotImplementedError, "#{cmd} has not yet been implemented. Patches welcome!"
24
+ end
17
25
 
18
- def self.del(key)
19
- pool.redis_by_key(key).del(key)
26
+ pool.redis_by_key( args.first ).send( cmd, *args )
20
27
  end
21
28
 
22
29
  def self.flushall
23
30
  pool.flushall
24
31
  end
25
32
 
26
- def self.mock!
27
- pool(true).mock!
28
- end
29
-
30
33
  def self.pool(mock=false)
34
+ require 'redi/mock' if mock
31
35
  @pool ||= Pool.new(self.config,mock)
32
36
  end
33
37
 
@@ -39,162 +43,4 @@ class Redi
39
43
  @config
40
44
  end
41
45
 
42
- # provide a key to name to host:port mapping
43
- #
44
- # should have a larger keyspace than servers, this allows scaling up the servers without changing the keyspace mapping
45
- #
46
- # sample configuration:
47
- #
48
- # - :host:
49
- # :port:
50
- # :db:
51
- # :buckets: 0 - 64
52
- # - :host:
53
- # :port:
54
- # :db:
55
- # :buckets: 65 - 127
56
- #
57
- class Pool
58
- attr_reader :keyspace, :servers
59
- def initialize(config,mock=false)
60
- key_type = Struct.new(:id, :to_s)
61
-
62
- # build server pool
63
- @bucket2server = {}
64
- buckets = []
65
- @servers = config.map {|cfg|
66
- bucket_range = cfg.delete(:buckets)
67
- s, e = bucket_range.split('-').map {|n| n.to_i }
68
- if mock
69
- conn = Mock.new
70
- else
71
- conn = Redis.new(cfg)
72
- end
73
- (s..e).each do|i|
74
- bucket_name = "n#{i}"
75
- buckets << key_type.new(i, bucket_name)
76
- @bucket2server[bucket_name] = conn
77
- end
78
- conn
79
- }
80
-
81
- # create the keyring to map redis keys to buckets
82
- @keyring = Redis::HashRing.new(buckets)
83
- end
84
-
85
- def qualified_key_for(key)
86
- bucket = @keyring.get_node(key)
87
- "#{bucket.to_s}:#{key}"
88
- end
89
-
90
- def redis_by_key(key)
91
- bucket = @keyring.get_node(key)
92
- Redis::Namespace.new(bucket.to_s, :redis => @bucket2server[bucket.to_s])
93
- end
94
-
95
- def flushall
96
- @servers.map {|s| s.flushall }
97
- end
98
-
99
- def mock!
100
- @servers.map! {|s| Mock.new }
101
- @bucket2server.keys.each_with_index do|k,i|
102
- @bucket2server[k] = @servers[i % @servers.size]
103
- end
104
- end
105
-
106
- end
107
-
108
- class Mock
109
- def initialize
110
- @store = {}
111
- end
112
-
113
- def get(key)
114
- @store[key]
115
- end
116
-
117
- def del(key)
118
- @store.delete(key)
119
- end
120
-
121
- def set(key, val)
122
- @store[key] = val
123
- end
124
-
125
- def mget(*keys)
126
- keys.map {|k| get(k) }
127
- end
128
-
129
- def flushall
130
- @store = {}
131
- end
132
-
133
- end
134
-
135
- end
136
-
137
- if __FILE__ == $0
138
- require 'rubygems'
139
- require 'test/unit'
140
-
141
- TEST_CONFIG = %(
142
- - :host: 127.0.0.1
143
- :port: 6379
144
- :db: 0
145
- :buckets: 0-64
146
- - :host: 127.0.0.1
147
- :port: 6379
148
- :db: 1
149
- :buckets: 65-127
150
- )
151
- class TestIt < Test::Unit::TestCase
152
-
153
- def test_redis_pool
154
- pool = Redi::Pool.new(YAML.load(TEST_CONFIG))
155
- redis = pool.redis_by_key('me:foo:1')
156
- assert_equal "n25", redis.namespace
157
- redis.flushall
158
- redis.set("me:foo:1", "hello")
159
- assert_equal "hello", redis.get("me:foo:1")
160
- end
161
-
162
- def test_using_hash_ring_strategy
163
- node_class = Struct.new(:id, :to_s)
164
- buckets = []
165
- 128.times do|i|
166
- buckets << node_class.new(i, "n#{i}")
167
- end
168
-
169
- servers = []
170
- 2.times do|i|
171
- servers << node_class.new(i, "s#{i}")
172
- end
173
-
174
- ring1 = Redis::HashRing.new(buckets)
175
-
176
- bucket2server = {}
177
- buckets.each do|bucket|
178
- bucket2server[bucket.to_s] = servers[bucket.id % servers.size].to_s
179
- end
180
-
181
- puts bucket2server.inspect
182
-
183
- servers_used = {}
184
-
185
- buckets_used = {}
186
- 6400.times do|i|
187
- key = "me:foo#{i}"
188
- bucket = ring1.get_node(key)
189
- server = bucket2server[bucket.to_s]#ring2.get_node(name.to_s)
190
- #puts "#{key} -> #{name} -> #{server}"
191
- servers_used[ server.to_s ] ||= 0
192
- servers_used[ server.to_s ] += 1
193
- buckets_used[ bucket.to_s ] ||= 0
194
- buckets_used[ bucket.to_s ] += 1
195
- end
196
- puts servers_used.inspect
197
- puts buckets_used.inspect
198
- end
199
- end
200
46
  end
data/lib/redi/mock.rb ADDED
@@ -0,0 +1,47 @@
1
+ #
2
+ # Instead of writing/reading from redis, create a in memory Hash, making it easy to
3
+ # test without redis server running
4
+ #
5
+ class Redi
6
+
7
+ def self.mock!
8
+ pool(true).mock!
9
+ end
10
+
11
+ class Pool
12
+
13
+ def mock!
14
+ @servers.map! {|s| Mock.new }
15
+ @bucket2server.keys.each_with_index do|k,i|
16
+ @bucket2server[k] = @servers[i % @servers.size]
17
+ end
18
+ end
19
+ end
20
+
21
+ class Mock
22
+ def initialize
23
+ @store = {}
24
+ end
25
+
26
+ def get(key)
27
+ @store[key]
28
+ end
29
+
30
+ def del(key)
31
+ @store.delete(key)
32
+ end
33
+
34
+ def set(key, val)
35
+ @store[key] = val
36
+ end
37
+
38
+ def mget(*keys)
39
+ keys.map {|k| get(k) }
40
+ end
41
+
42
+ def flushall
43
+ @store = {}
44
+ end
45
+
46
+ end
47
+ end
data/lib/redi/pool.rb ADDED
@@ -0,0 +1,67 @@
1
+ require 'rubygems'
2
+
3
+ require 'redis'
4
+ require 'redis/hash_ring'
5
+ require 'redis/namespace'
6
+
7
+ # provide a key to name to host:port mapping
8
+ #
9
+ # should have a larger keyspace than servers, this allows scaling up the servers without changing the keyspace mapping
10
+ #
11
+ # sample configuration:
12
+ #
13
+ # - :host:
14
+ # :port:
15
+ # :db:
16
+ # :buckets: 0 - 64
17
+ # - :host:
18
+ # :port:
19
+ # :db:
20
+ # :buckets: 65 - 127
21
+ #
22
+ class Redi
23
+ class Pool
24
+ attr_reader :keyspace, :servers
25
+ def initialize(config,mock=false)
26
+ key_type = Struct.new(:id, :to_s)
27
+
28
+ # build server pool
29
+ @bucket2server = {}
30
+ buckets = []
31
+ @servers = config.map {|cfg|
32
+ bucket_range = cfg.delete(:buckets)
33
+ s, e = bucket_range.split('-').map {|n| n.to_i }
34
+ if mock
35
+ require 'redi/mock'
36
+ conn = Mock.new
37
+ else
38
+ conn = Redis.new(cfg)
39
+ end
40
+ (s..e).each do|i|
41
+ bucket_name = "n#{i}"
42
+ buckets << key_type.new(i, bucket_name)
43
+ @bucket2server[bucket_name] = conn
44
+ end
45
+ conn
46
+ }
47
+
48
+ # create the keyring to map redis keys to buckets
49
+ @keyring = Redis::HashRing.new(buckets)
50
+ end
51
+
52
+ def qualified_key_for(key)
53
+ bucket = @keyring.get_node(key)
54
+ "#{bucket.to_s}:#{key}"
55
+ end
56
+
57
+ def redis_by_key(key)
58
+ bucket = @keyring.get_node(key)
59
+ Redis::Namespace.new(bucket.to_s, :redis => @bucket2server[bucket.to_s])
60
+ end
61
+
62
+ def flushall
63
+ @servers.map {|s| s.flushall }
64
+ end
65
+
66
+ end
67
+ end
data/lib/redi/version.rb CHANGED
@@ -1,3 +1,3 @@
1
- module RedisRing
2
- VERSION = "0.0.5"
1
+ class Redi
2
+ VERSION = "0.0.7"
3
3
  end
data/redi.gemspec CHANGED
@@ -4,9 +4,9 @@ require "redi/version"
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "redi"
7
- s.version = RedisRing::VERSION
8
- s.authors = ["Todd Fisher"]
9
- s.email = ["todd.fisher@livingsocial.com"]
7
+ s.version = Redi::VERSION
8
+ s.authors = ["Todd Fisher", "Ben Bleything"]
9
+ s.email = ["todd.fisher@livingsocial.com", "ben@bleything.net"]
10
10
  s.homepage = "http://livingsocial.com/"
11
11
  s.summary = %q{Redi multi redis scaling "to infinity and beyond!"}
12
12
  s.description = %q{hash keys to intermediate buckets allowing you to more easily scale out to more severs later}
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
 
21
21
  # specify any dependencies here; for example:
22
- # s.add_development_dependency "rails"
22
+ s.add_development_dependency "rake"
23
23
 
24
24
  s.add_runtime_dependency "redis"
25
25
  s.add_runtime_dependency "redis-namespace"
data/test/test_redi.rb ADDED
@@ -0,0 +1,78 @@
1
+ require 'test/unit'
2
+ require 'redi'
3
+ require 'yaml'
4
+
5
+ TEST_CONFIG = %(
6
+ - :host: 127.0.0.1
7
+ :port: 6379
8
+ :db: 6
9
+ :buckets: 0-64
10
+ - :host: 127.0.0.1
11
+ :port: 6379
12
+ :db: 7
13
+ :buckets: 65-127
14
+ )
15
+
16
+ class TestRedi < Test::Unit::TestCase
17
+
18
+ def setup
19
+ Redi.config = YAML.load( TEST_CONFIG )
20
+ Redi.flushall
21
+ end
22
+
23
+ def teardown
24
+ Redi.flushall
25
+ end
26
+
27
+ def test_unimplemented_commands
28
+ assert_raises( NotImplementedError, /has not yet been implemented/ ) do
29
+ Redi.brpoplpush 'source', 'destination', 'timeout'
30
+ end
31
+ end
32
+
33
+ def test_redis_pool
34
+ redis = Redi.pool.redis_by_key('me:foo:1')
35
+ assert_equal "n25", redis.namespace
36
+ redis.set("me:foo:1", "hello")
37
+ assert_equal "hello", redis.get("me:foo:1")
38
+ end
39
+
40
+ def test_using_hash_ring_strategy
41
+ node_class = Struct.new(:id, :to_s)
42
+ buckets = []
43
+ 128.times do|i|
44
+ buckets << node_class.new(i, "n#{i}")
45
+ end
46
+
47
+ servers = []
48
+ 2.times do|i|
49
+ servers << node_class.new(i, "s#{i}")
50
+ end
51
+
52
+ ring1 = Redis::HashRing.new(buckets)
53
+
54
+ bucket2server = {}
55
+ buckets.each do|bucket|
56
+ bucket2server[bucket.to_s] = servers[bucket.id % servers.size].to_s
57
+ end
58
+
59
+ servers_used = {}
60
+
61
+ buckets_used = {}
62
+ 6400.times do|i|
63
+ key = "me:foo#{i}"
64
+ bucket = ring1.get_node(key)
65
+ index = bucket.to_s.gsub(/n/,'').to_i
66
+ assert index < 128 && index >= 0, "bucket out of range: #{bucket}"
67
+ server = bucket2server[bucket.to_s]
68
+ servers_used[ server.to_s ] ||= 0
69
+ servers_used[ server.to_s ] += 1
70
+ buckets_used[ bucket.to_s ] ||= 0
71
+ buckets_used[ bucket.to_s ] += 1
72
+ end
73
+
74
+ assert_equal 3228, servers_used["s0"], "when hashing 6400 times in sequence, s0 should have 3228 hits"
75
+ assert_equal 3172, servers_used["s1"], "when hashing 6400 times in sequence, s1 should have 3172 hits"
76
+
77
+ end
78
+ end
metadata CHANGED
@@ -1,102 +1,95 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: redi
3
- version: !ruby/object:Gem::Version
4
- hash: 21
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 0
9
- - 5
10
- version: 0.0.5
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Todd Fisher
9
+ - Ben Bleything
14
10
  autorequire:
15
11
  bindir: bin
16
12
  cert_chain: []
17
-
18
- date: 2011-10-14 00:00:00 -04:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- name: redis
13
+ date: 2011-11-02 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rake
17
+ requirement: &2151905420 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
23
24
  prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
25
+ version_requirements: *2151905420
26
+ - !ruby/object:Gem::Dependency
27
+ name: redis
28
+ requirement: &2151904760 !ruby/object:Gem::Requirement
25
29
  none: false
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 3
30
- segments:
31
- - 0
32
- version: "0"
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
33
34
  type: :runtime
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: redis-namespace
37
35
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
36
+ version_requirements: *2151904760
37
+ - !ruby/object:Gem::Dependency
38
+ name: redis-namespace
39
+ requirement: &2151904240 !ruby/object:Gem::Requirement
39
40
  none: false
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- hash: 3
44
- segments:
45
- - 0
46
- version: "0"
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
47
45
  type: :runtime
48
- version_requirements: *id002
49
- description: hash keys to intermediate buckets allowing you to more easily scale out to more severs later
50
- email:
46
+ prerelease: false
47
+ version_requirements: *2151904240
48
+ description: hash keys to intermediate buckets allowing you to more easily scale out
49
+ to more severs later
50
+ email:
51
51
  - todd.fisher@livingsocial.com
52
+ - ben@bleything.net
52
53
  executables: []
53
-
54
54
  extensions: []
55
-
56
55
  extra_rdoc_files: []
57
-
58
- files:
56
+ files:
57
+ - .rvmrc
59
58
  - ChangeLog.md
60
59
  - Gemfile
61
60
  - Gemfile.lock
62
61
  - README.md
62
+ - Rakefile
63
63
  - init.rb
64
64
  - lib/redi.rb
65
+ - lib/redi/mock.rb
66
+ - lib/redi/pool.rb
65
67
  - lib/redi/version.rb
66
68
  - redi.gemspec
67
- has_rdoc: true
69
+ - test/test_redi.rb
68
70
  homepage: http://livingsocial.com/
69
71
  licenses: []
70
-
71
72
  post_install_message:
72
73
  rdoc_options: []
73
-
74
- require_paths:
74
+ require_paths:
75
75
  - lib
76
- required_ruby_version: !ruby/object:Gem::Requirement
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
77
  none: false
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- hash: 3
82
- segments:
83
- - 0
84
- version: "0"
85
- required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
83
  none: false
87
- requirements:
88
- - - ">="
89
- - !ruby/object:Gem::Version
90
- hash: 3
91
- segments:
92
- - 0
93
- version: "0"
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
94
88
  requirements: []
95
-
96
89
  rubyforge_project: redi
97
- rubygems_version: 1.5.2
90
+ rubygems_version: 1.8.10
98
91
  signing_key:
99
92
  specification_version: 3
100
93
  summary: Redi multi redis scaling "to infinity and beyond!"
101
- test_files: []
102
-
94
+ test_files:
95
+ - test/test_redi.rb