modesty 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +13 -0
- data/Gemfile.lock +18 -0
- data/LICENSE +21 -0
- data/README.md +121 -0
- data/Rakefile +29 -0
- data/VERSION +1 -0
- data/init.rb +1 -0
- data/lib/modesty.rb +26 -0
- data/lib/modesty/api.rb +14 -0
- data/lib/modesty/core_ext.rb +5 -0
- data/lib/modesty/core_ext/array.rb +21 -0
- data/lib/modesty/core_ext/fixnum.rb +5 -0
- data/lib/modesty/core_ext/hash.rb +39 -0
- data/lib/modesty/core_ext/string.rb +9 -0
- data/lib/modesty/core_ext/symbol.rb +33 -0
- data/lib/modesty/datastore.rb +51 -0
- data/lib/modesty/datastore/redis.rb +180 -0
- data/lib/modesty/experiment.rb +87 -0
- data/lib/modesty/experiment/base.rb +47 -0
- data/lib/modesty/experiment/builder.rb +48 -0
- data/lib/modesty/experiment/console.rb +4 -0
- data/lib/modesty/experiment/data.rb +75 -0
- data/lib/modesty/experiment/interface.rb +29 -0
- data/lib/modesty/experiment/significance.rb +376 -0
- data/lib/modesty/experiment/stats.rb +163 -0
- data/lib/modesty/frameworks/rails.rb +27 -0
- data/lib/modesty/identity.rb +32 -0
- data/lib/modesty/load.rb +80 -0
- data/lib/modesty/load/load_experiments.rb +14 -0
- data/lib/modesty/load/load_metrics.rb +17 -0
- data/lib/modesty/metric.rb +56 -0
- data/lib/modesty/metric/base.rb +38 -0
- data/lib/modesty/metric/builder.rb +23 -0
- data/lib/modesty/metric/data.rb +133 -0
- data/modesty.gemspec +192 -0
- data/spec/core_ext_spec.rb +17 -0
- data/spec/experiment_spec.rb +239 -0
- data/spec/identity_spec.rb +161 -0
- data/spec/load_spec.rb +87 -0
- data/spec/metric_spec.rb +176 -0
- data/spec/rails_spec.rb +48 -0
- data/spec/redis_spec.rb +29 -0
- data/spec/significance_spec.rb +147 -0
- data/spec/spec.opts +1 -0
- data/test/myapp/config/modesty.yml +9 -0
- data/test/myapp/modesty/experiments/cookbook.rb +4 -0
- data/test/myapp/modesty/metrics/kitchen_metrics.rb +9 -0
- data/test/myapp/modesty/metrics/stove/burner_metrics.rb +2 -0
- data/vendor/.piston.yml +8 -0
- data/vendor/mock_redis/.gitignore +2 -0
- data/vendor/mock_redis/README +8 -0
- data/vendor/mock_redis/lib/mock_redis.rb +10 -0
- data/vendor/mock_redis/lib/mock_redis/hash.rb +61 -0
- data/vendor/mock_redis/lib/mock_redis/list.rb +6 -0
- data/vendor/mock_redis/lib/mock_redis/misc.rb +69 -0
- data/vendor/mock_redis/lib/mock_redis/set.rb +108 -0
- data/vendor/mock_redis/lib/mock_redis/string.rb +32 -0
- data/vendor/redis-rb/.gitignore +8 -0
- data/vendor/redis-rb/LICENSE +20 -0
- data/vendor/redis-rb/README.markdown +129 -0
- data/vendor/redis-rb/Rakefile +155 -0
- data/vendor/redis-rb/benchmarking/logging.rb +62 -0
- data/vendor/redis-rb/benchmarking/pipeline.rb +51 -0
- data/vendor/redis-rb/benchmarking/speed.rb +21 -0
- data/vendor/redis-rb/benchmarking/suite.rb +24 -0
- data/vendor/redis-rb/benchmarking/thread_safety.rb +38 -0
- data/vendor/redis-rb/benchmarking/worker.rb +71 -0
- data/vendor/redis-rb/examples/basic.rb +15 -0
- data/vendor/redis-rb/examples/dist_redis.rb +43 -0
- data/vendor/redis-rb/examples/incr-decr.rb +17 -0
- data/vendor/redis-rb/examples/list.rb +26 -0
- data/vendor/redis-rb/examples/pubsub.rb +31 -0
- data/vendor/redis-rb/examples/sets.rb +36 -0
- data/vendor/redis-rb/examples/unicorn/config.ru +3 -0
- data/vendor/redis-rb/examples/unicorn/unicorn.rb +20 -0
- data/vendor/redis-rb/lib/redis.rb +676 -0
- data/vendor/redis-rb/lib/redis/client.rb +201 -0
- data/vendor/redis-rb/lib/redis/compat.rb +21 -0
- data/vendor/redis-rb/lib/redis/connection.rb +134 -0
- data/vendor/redis-rb/lib/redis/distributed.rb +526 -0
- data/vendor/redis-rb/lib/redis/hash_ring.rb +131 -0
- data/vendor/redis-rb/lib/redis/pipeline.rb +13 -0
- data/vendor/redis-rb/lib/redis/subscribe.rb +79 -0
- data/vendor/redis-rb/redis.gemspec +29 -0
- data/vendor/redis-rb/test/commands_on_hashes_test.rb +46 -0
- data/vendor/redis-rb/test/commands_on_lists_test.rb +50 -0
- data/vendor/redis-rb/test/commands_on_sets_test.rb +78 -0
- data/vendor/redis-rb/test/commands_on_sorted_sets_test.rb +109 -0
- data/vendor/redis-rb/test/commands_on_strings_test.rb +70 -0
- data/vendor/redis-rb/test/commands_on_value_types_test.rb +88 -0
- data/vendor/redis-rb/test/connection_handling_test.rb +87 -0
- data/vendor/redis-rb/test/db/.gitignore +1 -0
- data/vendor/redis-rb/test/distributd_key_tags_test.rb +53 -0
- data/vendor/redis-rb/test/distributed_blocking_commands_test.rb +54 -0
- data/vendor/redis-rb/test/distributed_commands_on_hashes_test.rb +12 -0
- data/vendor/redis-rb/test/distributed_commands_on_lists_test.rb +18 -0
- data/vendor/redis-rb/test/distributed_commands_on_sets_test.rb +85 -0
- data/vendor/redis-rb/test/distributed_commands_on_strings_test.rb +50 -0
- data/vendor/redis-rb/test/distributed_commands_on_value_types_test.rb +73 -0
- data/vendor/redis-rb/test/distributed_commands_requiring_clustering_test.rb +141 -0
- data/vendor/redis-rb/test/distributed_connection_handling_test.rb +25 -0
- data/vendor/redis-rb/test/distributed_internals_test.rb +18 -0
- data/vendor/redis-rb/test/distributed_persistence_control_commands_test.rb +24 -0
- data/vendor/redis-rb/test/distributed_publish_subscribe_test.rb +90 -0
- data/vendor/redis-rb/test/distributed_remote_server_control_commands_test.rb +31 -0
- data/vendor/redis-rb/test/distributed_sorting_test.rb +21 -0
- data/vendor/redis-rb/test/distributed_test.rb +60 -0
- data/vendor/redis-rb/test/distributed_transactions_test.rb +34 -0
- data/vendor/redis-rb/test/encoding_test.rb +16 -0
- data/vendor/redis-rb/test/helper.rb +86 -0
- data/vendor/redis-rb/test/internals_test.rb +27 -0
- data/vendor/redis-rb/test/lint/hashes.rb +90 -0
- data/vendor/redis-rb/test/lint/internals.rb +53 -0
- data/vendor/redis-rb/test/lint/lists.rb +93 -0
- data/vendor/redis-rb/test/lint/sets.rb +66 -0
- data/vendor/redis-rb/test/lint/sorted_sets.rb +132 -0
- data/vendor/redis-rb/test/lint/strings.rb +98 -0
- data/vendor/redis-rb/test/lint/value_types.rb +84 -0
- data/vendor/redis-rb/test/persistence_control_commands_test.rb +22 -0
- data/vendor/redis-rb/test/pipelining_commands_test.rb +78 -0
- data/vendor/redis-rb/test/publish_subscribe_test.rb +151 -0
- data/vendor/redis-rb/test/redis_mock.rb +64 -0
- data/vendor/redis-rb/test/remote_server_control_commands_test.rb +56 -0
- data/vendor/redis-rb/test/sorting_test.rb +44 -0
- data/vendor/redis-rb/test/test.conf +8 -0
- data/vendor/redis-rb/test/thread_safety_test.rb +34 -0
- data/vendor/redis-rb/test/transactions_test.rb +91 -0
- data/vendor/redis-rb/test/unknown_commands_test.rb +14 -0
- data/vendor/redis-rb/test/url_param_test.rb +52 -0
- metadata +277 -0
@@ -0,0 +1,201 @@
|
|
1
|
+
class Redis
|
2
|
+
class Client
|
3
|
+
attr_accessor :db, :host, :port, :password, :logger
|
4
|
+
attr :timeout
|
5
|
+
attr :connection
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@host = options[:host] || "127.0.0.1"
|
9
|
+
@port = (options[:port] || 6379).to_i
|
10
|
+
@db = (options[:db] || 0).to_i
|
11
|
+
@timeout = (options[:timeout] || 5).to_i
|
12
|
+
@password = options[:password]
|
13
|
+
@logger = options[:logger]
|
14
|
+
@connection = Connection.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def connect
|
18
|
+
connect_to(@host, @port)
|
19
|
+
call(:auth, @password) if @password
|
20
|
+
call(:select, @db) if @db != 0
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def id
|
25
|
+
"redis://#{host}:#{port}/#{db}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def call(*args)
|
29
|
+
process(args) do
|
30
|
+
read
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def call_loop(*args)
|
35
|
+
without_socket_timeout do
|
36
|
+
process(args) do
|
37
|
+
loop { yield(read) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def call_pipelined(commands)
|
43
|
+
process(*commands) do
|
44
|
+
Array.new(commands.size) { read }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def call_without_timeout(*args)
|
49
|
+
without_socket_timeout do
|
50
|
+
call(*args)
|
51
|
+
end
|
52
|
+
rescue Errno::ECONNRESET
|
53
|
+
retry
|
54
|
+
end
|
55
|
+
|
56
|
+
def process(*commands)
|
57
|
+
logging(commands) do
|
58
|
+
ensure_connected do
|
59
|
+
commands.each do |command|
|
60
|
+
connection.write(command)
|
61
|
+
end
|
62
|
+
|
63
|
+
yield if block_given?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def connected?
|
69
|
+
connection.connected?
|
70
|
+
end
|
71
|
+
|
72
|
+
def disconnect
|
73
|
+
connection.disconnect if connection.connected?
|
74
|
+
end
|
75
|
+
|
76
|
+
def reconnect
|
77
|
+
disconnect
|
78
|
+
connect
|
79
|
+
end
|
80
|
+
|
81
|
+
def read
|
82
|
+
begin
|
83
|
+
connection.read
|
84
|
+
|
85
|
+
rescue Errno::EAGAIN
|
86
|
+
# We want to make sure it reconnects on the next command after the
|
87
|
+
# timeout. Otherwise the server may reply in the meantime leaving
|
88
|
+
# the protocol in a desync status.
|
89
|
+
disconnect
|
90
|
+
|
91
|
+
raise Errno::EAGAIN, "Timeout reading from the socket"
|
92
|
+
|
93
|
+
rescue Errno::ECONNRESET
|
94
|
+
raise Errno::ECONNRESET, "Connection lost"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def without_socket_timeout
|
99
|
+
connect unless connected?
|
100
|
+
|
101
|
+
begin
|
102
|
+
self.timeout = 0
|
103
|
+
yield
|
104
|
+
ensure
|
105
|
+
self.timeout = @timeout if connected?
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
|
111
|
+
def deprecated(old, new = nil, trace = caller[0])
|
112
|
+
message = "The method #{old} is deprecated and will be removed in 2.0"
|
113
|
+
message << " - use #{new} instead" if new
|
114
|
+
Redis.deprecate(message, trace)
|
115
|
+
end
|
116
|
+
|
117
|
+
def logging(commands)
|
118
|
+
return yield unless @logger && @logger.debug?
|
119
|
+
|
120
|
+
begin
|
121
|
+
commands.each do |name, *args|
|
122
|
+
@logger.debug("Redis >> #{name.to_s.upcase} #{args.join(" ")}")
|
123
|
+
end
|
124
|
+
|
125
|
+
t1 = Time.now
|
126
|
+
yield
|
127
|
+
ensure
|
128
|
+
@logger.debug("Redis >> %0.2fms" % ((Time.now - t1) * 1000))
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def connect_to(host, port)
|
133
|
+
with_timeout(@timeout) do
|
134
|
+
connection.connect(host, port)
|
135
|
+
end
|
136
|
+
|
137
|
+
# If the timeout is set we set the low level socket options in order
|
138
|
+
# to make sure a blocking read will return after the specified number
|
139
|
+
# of seconds. This hack is from memcached ruby client.
|
140
|
+
self.timeout = @timeout
|
141
|
+
|
142
|
+
rescue Errno::ECONNREFUSED
|
143
|
+
raise Errno::ECONNREFUSED, "Unable to connect to Redis on #{host}:#{port}"
|
144
|
+
end
|
145
|
+
|
146
|
+
def timeout=(timeout)
|
147
|
+
connection.timeout = Integer(timeout * 1_000_000)
|
148
|
+
end
|
149
|
+
|
150
|
+
def ensure_connected
|
151
|
+
connect unless connected?
|
152
|
+
|
153
|
+
begin
|
154
|
+
yield
|
155
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF
|
156
|
+
if reconnect
|
157
|
+
yield
|
158
|
+
else
|
159
|
+
raise Errno::ECONNRESET
|
160
|
+
end
|
161
|
+
rescue Exception
|
162
|
+
disconnect
|
163
|
+
raise
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class ThreadSafe < self
|
168
|
+
def initialize(*args)
|
169
|
+
require "monitor"
|
170
|
+
|
171
|
+
super(*args)
|
172
|
+
@mutex = ::Monitor.new
|
173
|
+
end
|
174
|
+
|
175
|
+
def synchronize(&block)
|
176
|
+
@mutex.synchronize(&block)
|
177
|
+
end
|
178
|
+
|
179
|
+
def ensure_connected(&block)
|
180
|
+
synchronize { super }
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
begin
|
185
|
+
require "system_timer"
|
186
|
+
|
187
|
+
def with_timeout(seconds, &block)
|
188
|
+
SystemTimer.timeout_after(seconds, &block)
|
189
|
+
end
|
190
|
+
|
191
|
+
rescue LoadError
|
192
|
+
warn "WARNING: using the built-in Timeout class which is known to have issues when used for opening connections. Install the SystemTimer gem if you want to make sure the Redis client will not hang." unless RUBY_VERSION >= "1.9" || RUBY_PLATFORM =~ /java/
|
193
|
+
|
194
|
+
require "timeout"
|
195
|
+
|
196
|
+
def with_timeout(seconds, &block)
|
197
|
+
Timeout.timeout(seconds, &block)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# This file contains core methods that are present in
|
2
|
+
# Ruby 1.9 and not in earlier versions.
|
3
|
+
|
4
|
+
unless [].respond_to?(:product)
|
5
|
+
class Array
|
6
|
+
def product(*enums)
|
7
|
+
enums.unshift self
|
8
|
+
result = [[]]
|
9
|
+
while [] != enums
|
10
|
+
t, result = result, []
|
11
|
+
b, *enums = enums
|
12
|
+
t.each do |a|
|
13
|
+
b.each do |n|
|
14
|
+
result << a + [n]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
result
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
class Redis
|
2
|
+
class Connection
|
3
|
+
MINUS = "-".freeze
|
4
|
+
PLUS = "+".freeze
|
5
|
+
COLON = ":".freeze
|
6
|
+
DOLLAR = "$".freeze
|
7
|
+
ASTERISK = "*".freeze
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@sock = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def connected?
|
14
|
+
!! @sock
|
15
|
+
end
|
16
|
+
|
17
|
+
def connect(host, port)
|
18
|
+
@sock = TCPSocket.new(host, port)
|
19
|
+
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def disconnect
|
23
|
+
@sock.close
|
24
|
+
rescue
|
25
|
+
ensure
|
26
|
+
@sock = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def timeout=(usecs)
|
30
|
+
secs = Integer(usecs / 1_000_000)
|
31
|
+
usecs = Integer(usecs - (secs * 1_000_000)) # 0 - 999_999
|
32
|
+
|
33
|
+
optval = [secs, usecs].pack("l_2")
|
34
|
+
|
35
|
+
begin
|
36
|
+
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
37
|
+
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
38
|
+
rescue Errno::ENOPROTOOPT
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
COMMAND_DELIMITER = "\r\n"
|
43
|
+
|
44
|
+
def write(command)
|
45
|
+
@sock.write(build_command(*command).join(COMMAND_DELIMITER))
|
46
|
+
@sock.write(COMMAND_DELIMITER)
|
47
|
+
end
|
48
|
+
|
49
|
+
def build_command(name, *args)
|
50
|
+
command = []
|
51
|
+
command << "*#{args.size + 1}"
|
52
|
+
command << "$#{string_size name}"
|
53
|
+
command << name
|
54
|
+
|
55
|
+
args.each do |arg|
|
56
|
+
arg = arg.to_s
|
57
|
+
command << "$#{string_size arg}"
|
58
|
+
command << arg
|
59
|
+
end
|
60
|
+
|
61
|
+
command
|
62
|
+
end
|
63
|
+
|
64
|
+
def read
|
65
|
+
# We read the first byte using read() mainly because gets() is
|
66
|
+
# immune to raw socket timeouts.
|
67
|
+
reply_type = @sock.read(1)
|
68
|
+
|
69
|
+
raise Errno::ECONNRESET unless reply_type
|
70
|
+
|
71
|
+
format_reply(reply_type, @sock.gets)
|
72
|
+
end
|
73
|
+
|
74
|
+
def format_reply(reply_type, line)
|
75
|
+
case reply_type
|
76
|
+
when MINUS then format_error_reply(line)
|
77
|
+
when PLUS then format_status_reply(line)
|
78
|
+
when COLON then format_integer_reply(line)
|
79
|
+
when DOLLAR then format_bulk_reply(line)
|
80
|
+
when ASTERISK then format_multi_bulk_reply(line)
|
81
|
+
else raise ProtocolError.new(reply_type)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def format_error_reply(line)
|
86
|
+
raise "-" + line.strip
|
87
|
+
end
|
88
|
+
|
89
|
+
def format_status_reply(line)
|
90
|
+
line.strip
|
91
|
+
end
|
92
|
+
|
93
|
+
def format_integer_reply(line)
|
94
|
+
line.to_i
|
95
|
+
end
|
96
|
+
|
97
|
+
def format_bulk_reply(line)
|
98
|
+
bulklen = line.to_i
|
99
|
+
return if bulklen == -1
|
100
|
+
reply = encode(@sock.read(bulklen))
|
101
|
+
@sock.read(2) # Discard CRLF.
|
102
|
+
reply
|
103
|
+
end
|
104
|
+
|
105
|
+
def format_multi_bulk_reply(line)
|
106
|
+
n = line.to_i
|
107
|
+
return if n == -1
|
108
|
+
|
109
|
+
Array.new(n) { read }
|
110
|
+
end
|
111
|
+
|
112
|
+
protected
|
113
|
+
|
114
|
+
if "".respond_to?(:bytesize)
|
115
|
+
def string_size(string)
|
116
|
+
string.to_s.bytesize
|
117
|
+
end
|
118
|
+
else
|
119
|
+
def string_size(string)
|
120
|
+
string.to_s.size
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
if defined?(Encoding::default_external)
|
125
|
+
def encode(string)
|
126
|
+
string.force_encoding(Encoding::default_external)
|
127
|
+
end
|
128
|
+
else
|
129
|
+
def encode(string)
|
130
|
+
string
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,526 @@
|
|
1
|
+
require "redis/hash_ring"
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
class Distributed
|
5
|
+
|
6
|
+
class CannotDistribute < RuntimeError
|
7
|
+
def initialize(command)
|
8
|
+
@command = command
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
"#{@command.to_s.upcase} cannot be used in Redis::Distributed because the keys involved need to be on the same server or because we cannot guarantee that the operation will be atomic."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :ring
|
17
|
+
|
18
|
+
def initialize(urls, options = {})
|
19
|
+
@tag = options.delete(:tag) || /^\{(.+?)\}/
|
20
|
+
@default_options = options
|
21
|
+
@ring = HashRing.new urls.map { |url| Redis.connect(options.merge(:url => url)) }
|
22
|
+
@subscribed_node = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def node_for(key)
|
26
|
+
@ring.get_node(key_tag(key) || key)
|
27
|
+
end
|
28
|
+
|
29
|
+
def nodes
|
30
|
+
@ring.nodes
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_node(url)
|
34
|
+
@ring.add_node Redis.connect(@default_options.merge(:url => url))
|
35
|
+
end
|
36
|
+
|
37
|
+
def quit
|
38
|
+
on_each_node :quit
|
39
|
+
end
|
40
|
+
|
41
|
+
def select(db)
|
42
|
+
on_each_node :select, db
|
43
|
+
end
|
44
|
+
|
45
|
+
def ping
|
46
|
+
on_each_node :ping
|
47
|
+
end
|
48
|
+
|
49
|
+
def flushall
|
50
|
+
on_each_node :flushall
|
51
|
+
end
|
52
|
+
|
53
|
+
def exists(key)
|
54
|
+
node_for(key).exists(key)
|
55
|
+
end
|
56
|
+
|
57
|
+
def del(*keys)
|
58
|
+
on_each_node(:del, *keys)
|
59
|
+
end
|
60
|
+
|
61
|
+
def type(key)
|
62
|
+
node_for(key).type(key)
|
63
|
+
end
|
64
|
+
|
65
|
+
def keys(glob = "*")
|
66
|
+
on_each_node(:keys, glob).flatten
|
67
|
+
end
|
68
|
+
|
69
|
+
def randomkey
|
70
|
+
raise CannotDistribute, :randomkey
|
71
|
+
end
|
72
|
+
|
73
|
+
def rename(old_name, new_name)
|
74
|
+
ensure_same_node(:rename, old_name, new_name) do |node|
|
75
|
+
node.rename(old_name, new_name)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def renamenx(old_name, new_name)
|
80
|
+
ensure_same_node(:renamenx, old_name, new_name) do |node|
|
81
|
+
node.renamenx(old_name, new_name)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def dbsize
|
86
|
+
on_each_node :dbsize
|
87
|
+
end
|
88
|
+
|
89
|
+
def expire(key, seconds)
|
90
|
+
node_for(key).expire(key, seconds)
|
91
|
+
end
|
92
|
+
|
93
|
+
def expireat(key, unix_time)
|
94
|
+
node_for(key).expireat(key, unix_time)
|
95
|
+
end
|
96
|
+
|
97
|
+
def persist(key)
|
98
|
+
node_for(key).persist(key)
|
99
|
+
end
|
100
|
+
|
101
|
+
def ttl(key)
|
102
|
+
node_for(key).ttl(key)
|
103
|
+
end
|
104
|
+
|
105
|
+
def move(key, db)
|
106
|
+
node_for(key).move(key, db)
|
107
|
+
end
|
108
|
+
|
109
|
+
def flushdb
|
110
|
+
on_each_node :flushdb
|
111
|
+
end
|
112
|
+
|
113
|
+
def set(key, value)
|
114
|
+
node_for(key).set(key, value)
|
115
|
+
end
|
116
|
+
|
117
|
+
def setex(key, ttl, value)
|
118
|
+
node_for(key).setex(key, ttl, value)
|
119
|
+
end
|
120
|
+
|
121
|
+
def get(key)
|
122
|
+
node_for(key).get(key)
|
123
|
+
end
|
124
|
+
|
125
|
+
def getset(key, value)
|
126
|
+
node_for(key).getset(key, value)
|
127
|
+
end
|
128
|
+
|
129
|
+
def [](key)
|
130
|
+
get(key)
|
131
|
+
end
|
132
|
+
|
133
|
+
def append(key, value)
|
134
|
+
node_for(key).append(key, value)
|
135
|
+
end
|
136
|
+
|
137
|
+
def substr(key, start, stop)
|
138
|
+
node_for(key).substr(key, start, stop)
|
139
|
+
end
|
140
|
+
|
141
|
+
def []=(key,value)
|
142
|
+
set(key, value)
|
143
|
+
end
|
144
|
+
|
145
|
+
def mget(*keys)
|
146
|
+
raise CannotDistribute, :mget
|
147
|
+
end
|
148
|
+
|
149
|
+
def mapped_mget(*keys)
|
150
|
+
raise CannotDistribute, :mapped_mget
|
151
|
+
end
|
152
|
+
|
153
|
+
def setnx(key, value)
|
154
|
+
node_for(key).setnx(key, value)
|
155
|
+
end
|
156
|
+
|
157
|
+
def mset(*args)
|
158
|
+
raise CannotDistribute, :mset
|
159
|
+
end
|
160
|
+
|
161
|
+
def mapped_mset(hash)
|
162
|
+
mset(*hash.to_a.flatten)
|
163
|
+
end
|
164
|
+
|
165
|
+
def msetnx(*args)
|
166
|
+
raise CannotDistribute, :msetnx
|
167
|
+
end
|
168
|
+
|
169
|
+
def mapped_msetnx(hash)
|
170
|
+
raise CannotDistribute, :mapped_msetnx
|
171
|
+
end
|
172
|
+
|
173
|
+
def incr(key)
|
174
|
+
node_for(key).incr(key)
|
175
|
+
end
|
176
|
+
|
177
|
+
def incrby(key, increment)
|
178
|
+
node_for(key).incrby(key, increment)
|
179
|
+
end
|
180
|
+
|
181
|
+
def decr(key)
|
182
|
+
node_for(key).decr(key)
|
183
|
+
end
|
184
|
+
|
185
|
+
def decrby(key, decrement)
|
186
|
+
node_for(key).decrby(key, decrement)
|
187
|
+
end
|
188
|
+
|
189
|
+
def rpush(key, value)
|
190
|
+
node_for(key).rpush(key, value)
|
191
|
+
end
|
192
|
+
|
193
|
+
def lpush(key, value)
|
194
|
+
node_for(key).lpush(key, value)
|
195
|
+
end
|
196
|
+
|
197
|
+
def llen(key)
|
198
|
+
node_for(key).llen(key)
|
199
|
+
end
|
200
|
+
|
201
|
+
def lrange(key, start, stop)
|
202
|
+
node_for(key).lrange(key, start, stop)
|
203
|
+
end
|
204
|
+
|
205
|
+
def ltrim(key, start, stop)
|
206
|
+
node_for(key).ltrim(key, start, stop)
|
207
|
+
end
|
208
|
+
|
209
|
+
def lindex(key, index)
|
210
|
+
node_for(key).lindex(key, index)
|
211
|
+
end
|
212
|
+
|
213
|
+
def lset(key, index, value)
|
214
|
+
node_for(key).lset(key, index, value)
|
215
|
+
end
|
216
|
+
|
217
|
+
def lrem(key, count, value)
|
218
|
+
node_for(key).lrem(key, count, value)
|
219
|
+
end
|
220
|
+
|
221
|
+
def lpop(key)
|
222
|
+
node_for(key).lpop(key)
|
223
|
+
end
|
224
|
+
|
225
|
+
def rpop(key)
|
226
|
+
node_for(key).rpop(key)
|
227
|
+
end
|
228
|
+
|
229
|
+
def rpoplpush(source, destination)
|
230
|
+
ensure_same_node(:rpoplpush, source, destination) do |node|
|
231
|
+
node.rpoplpush(source, destination)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def blpop(key, timeout)
|
236
|
+
node_for(key).blpop(key, timeout)
|
237
|
+
end
|
238
|
+
|
239
|
+
def brpop(key, timeout)
|
240
|
+
node_for(key).brpop(key, timeout)
|
241
|
+
end
|
242
|
+
|
243
|
+
def sadd(key, value)
|
244
|
+
node_for(key).sadd(key, value)
|
245
|
+
end
|
246
|
+
|
247
|
+
def srem(key, value)
|
248
|
+
node_for(key).srem(key, value)
|
249
|
+
end
|
250
|
+
|
251
|
+
def spop(key)
|
252
|
+
node_for(key).spop(key)
|
253
|
+
end
|
254
|
+
|
255
|
+
def smove(source, destination, member)
|
256
|
+
ensure_same_node(:smove, source, destination) do |node|
|
257
|
+
node.smove(source, destination, member)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def scard(key)
|
262
|
+
node_for(key).scard(key)
|
263
|
+
end
|
264
|
+
|
265
|
+
def sismember(key, member)
|
266
|
+
node_for(key).sismember(key, member)
|
267
|
+
end
|
268
|
+
|
269
|
+
def sinter(*keys)
|
270
|
+
ensure_same_node(:sinter, *keys) do |node|
|
271
|
+
node.sinter(*keys)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def sinterstore(destination, *keys)
|
276
|
+
ensure_same_node(:sinterstore, destination, *keys) do |node|
|
277
|
+
node.sinterstore(destination, *keys)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def sunion(*keys)
|
282
|
+
ensure_same_node(:sunion, *keys) do |node|
|
283
|
+
node.sunion(*keys)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def sunionstore(destination, *keys)
|
288
|
+
ensure_same_node(:sunionstore, destination, *keys) do |node|
|
289
|
+
node.sunionstore(destination, *keys)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def sdiff(*keys)
|
294
|
+
ensure_same_node(:sdiff, *keys) do |node|
|
295
|
+
node.sdiff(*keys)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def sdiffstore(destination, *keys)
|
300
|
+
ensure_same_node(:sdiffstore, destination, *keys) do |node|
|
301
|
+
node.sdiffstore(destination, *keys)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def smembers(key)
|
306
|
+
node_for(key).smembers(key)
|
307
|
+
end
|
308
|
+
|
309
|
+
def srandmember(key)
|
310
|
+
node_for(key).srandmember(key)
|
311
|
+
end
|
312
|
+
|
313
|
+
def zadd(key, score, member)
|
314
|
+
node_for(key).zadd(key, score, member)
|
315
|
+
end
|
316
|
+
|
317
|
+
def zrem(key, member)
|
318
|
+
node_for(key).zrem(key, member)
|
319
|
+
end
|
320
|
+
|
321
|
+
def zincrby(key, increment, member)
|
322
|
+
node_for(key).zincrby(key, increment, member)
|
323
|
+
end
|
324
|
+
|
325
|
+
def zrange(key, start, stop, options = {})
|
326
|
+
node_for(key).zrange(key, start, stop, options)
|
327
|
+
end
|
328
|
+
|
329
|
+
def zrank(key, member)
|
330
|
+
node_for(key).zrank(key, member)
|
331
|
+
end
|
332
|
+
|
333
|
+
def zrevrank(key, member)
|
334
|
+
node_for(key).zrevrank(key, member)
|
335
|
+
end
|
336
|
+
|
337
|
+
def zrevrange(key, start, stop, options = {})
|
338
|
+
node_for(key).zrevrange(key, start, stop, options)
|
339
|
+
end
|
340
|
+
|
341
|
+
def zremrangebyscore(key, min, max)
|
342
|
+
node_for(key).zremrangebyscore(key, min, max)
|
343
|
+
end
|
344
|
+
|
345
|
+
def zremrangebyrank(key, start, stop)
|
346
|
+
node_for(key).zremrangebyrank(key, start, stop)
|
347
|
+
end
|
348
|
+
|
349
|
+
def zrangebyscore(key, min, max, options = {})
|
350
|
+
node_for(key).zrangebyscore(key, min, max, options)
|
351
|
+
end
|
352
|
+
|
353
|
+
def zcard(key)
|
354
|
+
node_for(key).zcard(key)
|
355
|
+
end
|
356
|
+
|
357
|
+
def zscore(key, member)
|
358
|
+
node_for(key).zscore(key, member)
|
359
|
+
end
|
360
|
+
|
361
|
+
def zinterstore(destination, keys, options = {})
|
362
|
+
ensure_same_node(:zinterstore, destination, *keys) do |node|
|
363
|
+
node.zinterstore(destination, keys, options)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def zunionstore(destination, keys, options = {})
|
368
|
+
ensure_same_node(:zunionstore, destination, *keys) do |node|
|
369
|
+
node.zunionstore(destination, keys, options)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def hset(key, field, value)
|
374
|
+
node_for(key).hset(key, field, value)
|
375
|
+
end
|
376
|
+
|
377
|
+
def hget(key, field)
|
378
|
+
node_for(key).hget(key, field)
|
379
|
+
end
|
380
|
+
|
381
|
+
def hdel(key, field)
|
382
|
+
node_for(key).hdel(key, field)
|
383
|
+
end
|
384
|
+
|
385
|
+
def hexists(key, field)
|
386
|
+
node_for(key).hexists(key, field)
|
387
|
+
end
|
388
|
+
|
389
|
+
def hlen(key)
|
390
|
+
node_for(key).hlen(key)
|
391
|
+
end
|
392
|
+
|
393
|
+
def hkeys(key)
|
394
|
+
node_for(key).hkeys(key)
|
395
|
+
end
|
396
|
+
|
397
|
+
def hvals(key)
|
398
|
+
node_for(key).hvals(key)
|
399
|
+
end
|
400
|
+
|
401
|
+
def hgetall(key)
|
402
|
+
node_for(key).hgetall(key)
|
403
|
+
end
|
404
|
+
|
405
|
+
def hmset(key, *attrs)
|
406
|
+
node_for(key).hmset(key, *attrs)
|
407
|
+
end
|
408
|
+
|
409
|
+
def hincrby(key, field, increment)
|
410
|
+
node_for(key).hincrby(key, field, increment)
|
411
|
+
end
|
412
|
+
|
413
|
+
def sort(key, options = {})
|
414
|
+
keys = [key, options[:by], options[:store], *Array(options[:get])].compact
|
415
|
+
|
416
|
+
ensure_same_node(:sort, *keys) do |node|
|
417
|
+
node.sort(key, options)
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
def multi(&block)
|
422
|
+
raise CannotDistribute, :multi
|
423
|
+
end
|
424
|
+
|
425
|
+
def watch(*keys)
|
426
|
+
raise CannotDistribute, :watch
|
427
|
+
end
|
428
|
+
|
429
|
+
def unwatch
|
430
|
+
raise CannotDistribute, :unwatch
|
431
|
+
end
|
432
|
+
|
433
|
+
def exec
|
434
|
+
raise CannotDistribute, :exec
|
435
|
+
end
|
436
|
+
|
437
|
+
def discard
|
438
|
+
raise CannotDistribute, :discard
|
439
|
+
end
|
440
|
+
|
441
|
+
def publish(channel, message)
|
442
|
+
node_for(channel).publish(channel, message)
|
443
|
+
end
|
444
|
+
|
445
|
+
def subscribed?
|
446
|
+
!! @subscribed_node
|
447
|
+
end
|
448
|
+
|
449
|
+
def unsubscribe(*channels)
|
450
|
+
raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
|
451
|
+
@subscribed_node.unsubscribe(*channels)
|
452
|
+
end
|
453
|
+
|
454
|
+
def subscribe(channel, *channels, &block)
|
455
|
+
if channels.empty?
|
456
|
+
@subscribed_node = node_for(channel)
|
457
|
+
@subscribed_node.subscribe(channel, &block)
|
458
|
+
else
|
459
|
+
ensure_same_node(:subscribe, channel, *channels) do |node|
|
460
|
+
@subscribed_node = node
|
461
|
+
node.subscribe(channel, *channels, &block)
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
def punsubscribe(*channels)
|
467
|
+
raise NotImplementedError
|
468
|
+
end
|
469
|
+
|
470
|
+
def psubscribe(*channels, &block)
|
471
|
+
raise NotImplementedError
|
472
|
+
end
|
473
|
+
|
474
|
+
def save
|
475
|
+
on_each_node :save
|
476
|
+
end
|
477
|
+
|
478
|
+
def bgsave
|
479
|
+
on_each_node :bgsave
|
480
|
+
end
|
481
|
+
|
482
|
+
def lastsave
|
483
|
+
on_each_node :lastsave
|
484
|
+
end
|
485
|
+
|
486
|
+
def info
|
487
|
+
on_each_node :info
|
488
|
+
end
|
489
|
+
|
490
|
+
def monitor
|
491
|
+
raise NotImplementedError
|
492
|
+
end
|
493
|
+
|
494
|
+
def echo(value)
|
495
|
+
on_each_node :echo, value
|
496
|
+
end
|
497
|
+
|
498
|
+
def pipelined
|
499
|
+
raise CannotDistribute, :pipelined
|
500
|
+
end
|
501
|
+
|
502
|
+
protected
|
503
|
+
|
504
|
+
def on_each_node(command, *args)
|
505
|
+
nodes.map do |node|
|
506
|
+
node.send(command, *args)
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
def node_index_for(key)
|
511
|
+
nodes.index(node_for(key))
|
512
|
+
end
|
513
|
+
|
514
|
+
def key_tag(key)
|
515
|
+
key[@tag, 1] if @tag
|
516
|
+
end
|
517
|
+
|
518
|
+
def ensure_same_node(command, *keys)
|
519
|
+
tags = keys.map { |key| key_tag(key) }
|
520
|
+
|
521
|
+
raise CannotDistribute, command if !tags.all? || tags.uniq.size != 1
|
522
|
+
|
523
|
+
yield(node_for(keys.first))
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|