redi 0.0.5 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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