redis 2.1.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/CHANGELOG.md +34 -0
- data/README.md +190 -0
- data/Rakefile +194 -79
- data/benchmarking/logging.rb +62 -0
- data/benchmarking/pipeline.rb +51 -0
- data/benchmarking/speed.rb +21 -0
- data/benchmarking/suite.rb +24 -0
- data/benchmarking/thread_safety.rb +38 -0
- data/benchmarking/worker.rb +71 -0
- data/examples/basic.rb +15 -0
- data/examples/dist_redis.rb +43 -0
- data/examples/incr-decr.rb +17 -0
- data/examples/list.rb +26 -0
- data/examples/pubsub.rb +31 -0
- data/examples/sets.rb +36 -0
- data/examples/unicorn/config.ru +3 -0
- data/examples/unicorn/unicorn.rb +20 -0
- data/lib/redis.rb +612 -156
- data/lib/redis/client.rb +98 -57
- data/lib/redis/connection.rb +9 -134
- data/lib/redis/connection/command_helper.rb +45 -0
- data/lib/redis/connection/hiredis.rb +49 -0
- data/lib/redis/connection/registry.rb +12 -0
- data/lib/redis/connection/ruby.rb +131 -0
- data/lib/redis/connection/synchrony.rb +125 -0
- data/lib/redis/distributed.rb +161 -5
- data/lib/redis/pipeline.rb +6 -0
- data/lib/redis/version.rb +3 -0
- data/redis.gemspec +24 -0
- data/test/commands_on_hashes_test.rb +32 -0
- data/test/commands_on_lists_test.rb +60 -0
- data/test/commands_on_sets_test.rb +78 -0
- data/test/commands_on_sorted_sets_test.rb +109 -0
- data/test/commands_on_strings_test.rb +80 -0
- data/test/commands_on_value_types_test.rb +88 -0
- data/test/connection_handling_test.rb +87 -0
- data/test/db/.gitignore +1 -0
- data/test/distributed_blocking_commands_test.rb +53 -0
- data/test/distributed_commands_on_hashes_test.rb +12 -0
- data/test/distributed_commands_on_lists_test.rb +24 -0
- data/test/distributed_commands_on_sets_test.rb +85 -0
- data/test/distributed_commands_on_strings_test.rb +50 -0
- data/test/distributed_commands_on_value_types_test.rb +73 -0
- data/test/distributed_commands_requiring_clustering_test.rb +148 -0
- data/test/distributed_connection_handling_test.rb +25 -0
- data/test/distributed_internals_test.rb +18 -0
- data/test/distributed_key_tags_test.rb +53 -0
- data/test/distributed_persistence_control_commands_test.rb +24 -0
- data/test/distributed_publish_subscribe_test.rb +101 -0
- data/test/distributed_remote_server_control_commands_test.rb +31 -0
- data/test/distributed_sorting_test.rb +21 -0
- data/test/distributed_test.rb +60 -0
- data/test/distributed_transactions_test.rb +34 -0
- data/test/encoding_test.rb +16 -0
- data/test/error_replies_test.rb +53 -0
- data/test/helper.rb +145 -0
- data/test/internals_test.rb +157 -0
- data/test/lint/hashes.rb +114 -0
- data/test/lint/internals.rb +41 -0
- data/test/lint/lists.rb +93 -0
- data/test/lint/sets.rb +66 -0
- data/test/lint/sorted_sets.rb +167 -0
- data/test/lint/strings.rb +137 -0
- data/test/lint/value_types.rb +84 -0
- data/test/persistence_control_commands_test.rb +22 -0
- data/test/pipelining_commands_test.rb +123 -0
- data/test/publish_subscribe_test.rb +158 -0
- data/test/redis_mock.rb +80 -0
- data/test/remote_server_control_commands_test.rb +63 -0
- data/test/sorting_test.rb +44 -0
- data/test/synchrony_driver.rb +57 -0
- data/test/test.conf +8 -0
- data/test/thread_safety_test.rb +30 -0
- data/test/transactions_test.rb +100 -0
- data/test/unknown_commands_test.rb +14 -0
- data/test/url_param_test.rb +60 -0
- metadata +128 -19
- data/README.markdown +0 -129
@@ -0,0 +1,62 @@
|
|
1
|
+
# Run with
|
2
|
+
#
|
3
|
+
# $ ruby -Ilib benchmarking/logging.rb
|
4
|
+
#
|
5
|
+
|
6
|
+
begin
|
7
|
+
require "bench"
|
8
|
+
rescue LoadError
|
9
|
+
$stderr.puts "`gem install bench` and try again."
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
require "redis"
|
14
|
+
require "logger"
|
15
|
+
|
16
|
+
def log(level, namespace = nil)
|
17
|
+
logger = (namespace || Kernel).const_get(:Logger).new("/dev/null")
|
18
|
+
logger.level = (namespace || Logger).const_get(level)
|
19
|
+
logger
|
20
|
+
end
|
21
|
+
|
22
|
+
def stress(redis)
|
23
|
+
redis.flushdb
|
24
|
+
|
25
|
+
n = (ARGV.shift || 2000).to_i
|
26
|
+
|
27
|
+
n.times do |i|
|
28
|
+
key = "foo:#{i}"
|
29
|
+
redis.set key, i
|
30
|
+
redis.get key
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
default = Redis.new
|
35
|
+
|
36
|
+
logging_redises = [
|
37
|
+
Redis.new(:logger => log(:DEBUG)),
|
38
|
+
Redis.new(:logger => log(:INFO)),
|
39
|
+
]
|
40
|
+
|
41
|
+
begin
|
42
|
+
require "log4r"
|
43
|
+
|
44
|
+
logging_redises += [
|
45
|
+
Redis.new(:logger => log(:DEBUG, Log4r)),
|
46
|
+
Redis.new(:logger => log(:INFO, Log4r)),
|
47
|
+
]
|
48
|
+
rescue LoadError
|
49
|
+
$stderr.puts "Log4r not installed. `gem install log4r` if you want to compare it against Ruby's Logger (spoiler: it's much faster)."
|
50
|
+
end
|
51
|
+
|
52
|
+
benchmark "Default options (no logger)" do
|
53
|
+
stress(default)
|
54
|
+
end
|
55
|
+
|
56
|
+
logging_redises.each do |redis|
|
57
|
+
benchmark "#{redis.client.logger.class} on #{Logger::SEV_LABEL[redis.client.logger.level]}" do
|
58
|
+
stress(redis)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
run 10
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "benchmark"
|
2
|
+
|
3
|
+
$:.push File.join(File.dirname(__FILE__), 'lib')
|
4
|
+
|
5
|
+
require 'redis'
|
6
|
+
|
7
|
+
ITERATIONS = 10000
|
8
|
+
|
9
|
+
@r = Redis.new
|
10
|
+
|
11
|
+
Benchmark.bmbm do |benchmark|
|
12
|
+
benchmark.report("set") do
|
13
|
+
@r.flushdb
|
14
|
+
|
15
|
+
ITERATIONS.times do |i|
|
16
|
+
@r.set("foo#{i}", "Hello world!")
|
17
|
+
@r.get("foo#{i}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
benchmark.report("set (pipelined)") do
|
22
|
+
@r.flushdb
|
23
|
+
|
24
|
+
@r.pipelined do
|
25
|
+
ITERATIONS.times do |i|
|
26
|
+
@r.set("foo#{i}", "Hello world!")
|
27
|
+
@r.get("foo#{i}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
benchmark.report("lpush+ltrim") do
|
33
|
+
@r.flushdb
|
34
|
+
|
35
|
+
ITERATIONS.times do |i|
|
36
|
+
@r.lpush "lpush#{i}", i
|
37
|
+
@r.ltrim "ltrim#{i}", 0, 30
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
benchmark.report("lpush+ltrim (pipelined)") do
|
42
|
+
@r.flushdb
|
43
|
+
|
44
|
+
@r.pipelined do
|
45
|
+
ITERATIONS.times do |i|
|
46
|
+
@r.lpush "lpush#{i}", i
|
47
|
+
@r.ltrim "ltrim#{i}", 0, 30
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Run with
|
2
|
+
#
|
3
|
+
# $ ruby -Ilib benchmarking/speed.rb
|
4
|
+
#
|
5
|
+
|
6
|
+
require "benchmark"
|
7
|
+
require "redis"
|
8
|
+
|
9
|
+
r = Redis.new
|
10
|
+
n = (ARGV.shift || 20000).to_i
|
11
|
+
|
12
|
+
elapsed = Benchmark.realtime do
|
13
|
+
# n sets, n gets
|
14
|
+
n.times do |i|
|
15
|
+
key = "foo#{i}"
|
16
|
+
r[key] = key * 10
|
17
|
+
r[key]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
puts '%.2f Kops' % (2 * n / 1000 / elapsed)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
def run_in_background(command)
|
4
|
+
fork { system command }
|
5
|
+
end
|
6
|
+
|
7
|
+
def with_all_segments(&block)
|
8
|
+
0.upto(9) do |segment_number|
|
9
|
+
block_size = 100000
|
10
|
+
start_index = segment_number * block_size
|
11
|
+
end_index = start_index + block_size - 1
|
12
|
+
block.call(start_index, end_index)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
#with_all_segments do |start_index, end_index|
|
17
|
+
# puts "Initializing keys from #{start_index} to #{end_index}"
|
18
|
+
# system "ruby worker.rb initialize #{start_index} #{end_index} 0"
|
19
|
+
#end
|
20
|
+
|
21
|
+
with_all_segments do |start_index, end_index|
|
22
|
+
run_in_background "ruby worker.rb write #{start_index} #{end_index} 10"
|
23
|
+
run_in_background "ruby worker.rb read #{start_index} #{end_index} 1"
|
24
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# Run with
|
2
|
+
#
|
3
|
+
# $ ruby -Ilib benchmarking/thread_safety.rb
|
4
|
+
#
|
5
|
+
|
6
|
+
begin
|
7
|
+
require "bench"
|
8
|
+
rescue LoadError
|
9
|
+
$stderr.puts "`gem install bench` and try again."
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
require "redis"
|
14
|
+
|
15
|
+
def stress(redis)
|
16
|
+
redis.flushdb
|
17
|
+
|
18
|
+
n = (ARGV.shift || 2000).to_i
|
19
|
+
|
20
|
+
n.times do |i|
|
21
|
+
key = "foo:#{i}"
|
22
|
+
redis.set key, i
|
23
|
+
redis.get key
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
thread_unsafe = Redis.new(:thread_safe => false)
|
28
|
+
thread_safe = Redis.new(:thread_safe => true)
|
29
|
+
|
30
|
+
benchmark "Thread-unsafe" do
|
31
|
+
stress(thread_unsafe)
|
32
|
+
end
|
33
|
+
|
34
|
+
benchmark "Thread-safe" do
|
35
|
+
stress(thread_safe)
|
36
|
+
end
|
37
|
+
|
38
|
+
run 10
|
@@ -0,0 +1,71 @@
|
|
1
|
+
BENCHMARK_ROOT = File.dirname(__FILE__)
|
2
|
+
REDIS_ROOT = File.join(BENCHMARK_ROOT, "..", "lib")
|
3
|
+
|
4
|
+
$: << REDIS_ROOT
|
5
|
+
require 'redis'
|
6
|
+
require 'benchmark'
|
7
|
+
|
8
|
+
def show_usage
|
9
|
+
puts <<-EOL
|
10
|
+
Usage: worker.rb [read:write] <start_index> <end_index> <sleep_msec>
|
11
|
+
EOL
|
12
|
+
end
|
13
|
+
|
14
|
+
def shift_from_argv
|
15
|
+
value = ARGV.shift
|
16
|
+
unless value
|
17
|
+
show_usage
|
18
|
+
exit -1
|
19
|
+
end
|
20
|
+
value
|
21
|
+
end
|
22
|
+
|
23
|
+
operation = shift_from_argv.to_sym
|
24
|
+
start_index = shift_from_argv.to_i
|
25
|
+
end_index = shift_from_argv.to_i
|
26
|
+
sleep_msec = shift_from_argv.to_i
|
27
|
+
sleep_duration = sleep_msec/1000.0
|
28
|
+
|
29
|
+
redis = Redis.new
|
30
|
+
|
31
|
+
case operation
|
32
|
+
when :initialize
|
33
|
+
|
34
|
+
start_index.upto(end_index) do |i|
|
35
|
+
redis[i] = 0
|
36
|
+
end
|
37
|
+
|
38
|
+
when :clear
|
39
|
+
|
40
|
+
start_index.upto(end_index) do |i|
|
41
|
+
redis.delete(i)
|
42
|
+
end
|
43
|
+
|
44
|
+
when :read, :write
|
45
|
+
|
46
|
+
puts "Starting to #{operation} at segment #{end_index + 1}"
|
47
|
+
|
48
|
+
loop do
|
49
|
+
t1 = Time.now
|
50
|
+
start_index.upto(end_index) do |i|
|
51
|
+
case operation
|
52
|
+
when :read
|
53
|
+
redis.get(i)
|
54
|
+
when :write
|
55
|
+
redis.incr(i)
|
56
|
+
else
|
57
|
+
raise "Unknown operation: #{operation}"
|
58
|
+
end
|
59
|
+
sleep sleep_duration
|
60
|
+
end
|
61
|
+
t2 = Time.now
|
62
|
+
|
63
|
+
requests_processed = end_index - start_index
|
64
|
+
time = t2 - t1
|
65
|
+
puts "#{t2.strftime("%H:%M")} [segment #{end_index + 1}] : Processed #{requests_processed} requests in #{time} seconds - #{(requests_processed/time).round} requests/sec"
|
66
|
+
end
|
67
|
+
|
68
|
+
else
|
69
|
+
raise "Unknown operation: #{operation}"
|
70
|
+
end
|
71
|
+
|
data/examples/basic.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require "redis"
|
2
|
+
require "redis/distributed"
|
3
|
+
|
4
|
+
r = Redis::Distributed.new %w[redis://localhost:6379 redis://localhost:6380 redis://localhost:6381 redis://localhost:6382]
|
5
|
+
|
6
|
+
r.flushdb
|
7
|
+
|
8
|
+
r['urmom'] = 'urmom'
|
9
|
+
r['urdad'] = 'urdad'
|
10
|
+
r['urmom1'] = 'urmom1'
|
11
|
+
r['urdad1'] = 'urdad1'
|
12
|
+
r['urmom2'] = 'urmom2'
|
13
|
+
r['urdad2'] = 'urdad2'
|
14
|
+
r['urmom3'] = 'urmom3'
|
15
|
+
r['urdad3'] = 'urdad3'
|
16
|
+
p r['urmom']
|
17
|
+
p r['urdad']
|
18
|
+
p r['urmom1']
|
19
|
+
p r['urdad1']
|
20
|
+
p r['urmom2']
|
21
|
+
p r['urdad2']
|
22
|
+
p r['urmom3']
|
23
|
+
p r['urdad3']
|
24
|
+
|
25
|
+
r.rpush 'listor', 'foo1'
|
26
|
+
r.rpush 'listor', 'foo2'
|
27
|
+
r.rpush 'listor', 'foo3'
|
28
|
+
r.rpush 'listor', 'foo4'
|
29
|
+
r.rpush 'listor', 'foo5'
|
30
|
+
|
31
|
+
p r.rpop('listor')
|
32
|
+
p r.rpop('listor')
|
33
|
+
p r.rpop('listor')
|
34
|
+
p r.rpop('listor')
|
35
|
+
p r.rpop('listor')
|
36
|
+
|
37
|
+
puts "key distribution:"
|
38
|
+
|
39
|
+
r.ring.nodes.each do |node|
|
40
|
+
p [node.client, node.keys("*")]
|
41
|
+
end
|
42
|
+
r.flushdb
|
43
|
+
p r.keys('*')
|
data/examples/list.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'redis'
|
3
|
+
|
4
|
+
r = Redis.new
|
5
|
+
|
6
|
+
r.del 'logs'
|
7
|
+
|
8
|
+
puts
|
9
|
+
|
10
|
+
p "pushing log messages into a LIST"
|
11
|
+
r.rpush 'logs', 'some log message'
|
12
|
+
r.rpush 'logs', 'another log message'
|
13
|
+
r.rpush 'logs', 'yet another log message'
|
14
|
+
r.rpush 'logs', 'also another log message'
|
15
|
+
|
16
|
+
puts
|
17
|
+
p 'contents of logs LIST'
|
18
|
+
|
19
|
+
p r.lrange('logs', 0, -1)
|
20
|
+
|
21
|
+
puts
|
22
|
+
p 'Trim logs LIST to last 2 elements(easy circular buffer)'
|
23
|
+
|
24
|
+
r.ltrim('logs', -2, -1)
|
25
|
+
|
26
|
+
p r.lrange('logs', 0, -1)
|
data/examples/pubsub.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "redis"
|
2
|
+
|
3
|
+
puts <<-EOS
|
4
|
+
To play with this example use redis-cli from another terminal, like this:
|
5
|
+
|
6
|
+
$ redis-cli publish one hello
|
7
|
+
|
8
|
+
Finally force the example to exit sending the 'exit' message with:
|
9
|
+
|
10
|
+
$ redis-cli publish two exit
|
11
|
+
|
12
|
+
EOS
|
13
|
+
|
14
|
+
redis = Redis.connect
|
15
|
+
|
16
|
+
trap(:INT) { puts; exit }
|
17
|
+
|
18
|
+
redis.subscribe(:one, :two) do |on|
|
19
|
+
on.subscribe do |channel, subscriptions|
|
20
|
+
puts "Subscribed to ##{channel} (#{subscriptions} subscriptions)"
|
21
|
+
end
|
22
|
+
|
23
|
+
on.message do |channel, message|
|
24
|
+
puts "##{channel}: #{message}"
|
25
|
+
redis.unsubscribe if message == "exit"
|
26
|
+
end
|
27
|
+
|
28
|
+
on.unsubscribe do |channel, subscriptions|
|
29
|
+
puts "Unsubscribed from ##{channel} (#{subscriptions} subscriptions)"
|
30
|
+
end
|
31
|
+
end
|
data/examples/sets.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'redis'
|
3
|
+
|
4
|
+
r = Redis.new
|
5
|
+
|
6
|
+
r.del 'foo-tags'
|
7
|
+
r.del 'bar-tags'
|
8
|
+
|
9
|
+
puts
|
10
|
+
p "create a set of tags on foo-tags"
|
11
|
+
|
12
|
+
r.sadd 'foo-tags', 'one'
|
13
|
+
r.sadd 'foo-tags', 'two'
|
14
|
+
r.sadd 'foo-tags', 'three'
|
15
|
+
|
16
|
+
puts
|
17
|
+
p "create a set of tags on bar-tags"
|
18
|
+
|
19
|
+
r.sadd 'bar-tags', 'three'
|
20
|
+
r.sadd 'bar-tags', 'four'
|
21
|
+
r.sadd 'bar-tags', 'five'
|
22
|
+
|
23
|
+
puts
|
24
|
+
p 'foo-tags'
|
25
|
+
|
26
|
+
p r.smembers('foo-tags')
|
27
|
+
|
28
|
+
puts
|
29
|
+
p 'bar-tags'
|
30
|
+
|
31
|
+
p r.smembers('bar-tags')
|
32
|
+
|
33
|
+
puts
|
34
|
+
p 'intersection of foo-tags and bar-tags'
|
35
|
+
|
36
|
+
p r.sinter('foo-tags', 'bar-tags')
|