redis_pipeliner 0.0.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.
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/Gemfile +5 -0
- data/README.md +49 -0
- data/Rakefile +1 -0
- data/lib/redis_pipeliner/pipeliner.rb +44 -0
- data/lib/redis_pipeliner/version.rb +3 -0
- data/lib/redis_pipeliner.rb +17 -0
- data/redis_pipeliner.gemspec +24 -0
- data/spec/pipeliner_spec.rb +61 -0
- data/spec/spec_helper.rb +20 -0
- metadata +106 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Ruby gem for easily pipelining REDIS commands
|
2
|
+
https://github.com/mrdanadams/redis_pipeliner
|
3
|
+
|
4
|
+
(Inspired by this blog post on [5-10x Speed Ups by Pipeling Multiple REDIS Commands in Ruby](http://mrdanadams.com/2012/pipeline-redis-commands-ruby/) by [Dan Adams](http://mrdanadams.com).)
|
5
|
+
|
6
|
+
[Pipelining in REDIS](https://github.com/redis/redis-rb#pipelining) is a great way to stay performant when executing multiple commands. It should also be easy to use.
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
|
10
|
+
Basic usage involves:
|
11
|
+
|
12
|
+
1. Enqueueing a number of REDIS commands inside a `pipelined` block
|
13
|
+
2. Doing something with the results either afterwards or inside blocks specific to each command.
|
14
|
+
|
15
|
+
Ex: (a bit contrived...)
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
# Put a bunch of values in a few different hashes
|
19
|
+
redis = Redis.connect
|
20
|
+
redis.hset "h1", "foo", 1
|
21
|
+
redis.hset "h2", "bar", 2
|
22
|
+
redis.hest "h3", "baz", 3
|
23
|
+
|
24
|
+
# Get the values pipelined and sum them up
|
25
|
+
values = RedisPipeliner.pipeline redis do |p|
|
26
|
+
# This would normally be 3 round-trips
|
27
|
+
p.enqueue redis.hget("h1", "foo")
|
28
|
+
p.enqueue redis.hget("h2", "bar")
|
29
|
+
p.enqueue redis.hget("h3", "baz")
|
30
|
+
end
|
31
|
+
values.map(&:to_i).inject(&:+).should == 6
|
32
|
+
```
|
33
|
+
|
34
|
+
You can also pass in a block to be called for each value rather than operating on the values afterwards:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
results = []
|
38
|
+
RedisPipeliner.pipeline redis do |p|
|
39
|
+
[%w(h1 foo), %w(h2 bar), %w(h3 baz)].each do |pair|
|
40
|
+
p.enqueue redis.hget(pair[0], pair[1]) do |value|
|
41
|
+
# referencing pair inside the block
|
42
|
+
results << pair[1] + value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
results.first.should == "foo1"
|
47
|
+
```
|
48
|
+
|
49
|
+
See the specs for executable examples.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RedisPipeliner
|
2
|
+
# Enqueues commands in a pipeline and waits until they are finished.
|
3
|
+
# Usage pattern is to call #enqueue with each REDIS future and a block to process it,
|
4
|
+
# then call #wait outside the Redis.pipelined call.
|
5
|
+
class Pipeliner
|
6
|
+
def initialize(redis)
|
7
|
+
@redis = redis
|
8
|
+
@commands = []
|
9
|
+
end
|
10
|
+
|
11
|
+
# Adds a command (a Future, actually) with an option block to call when the Future has been realized.
|
12
|
+
def enqueue(future, &proc)
|
13
|
+
@commands << { future: future, callback: proc }
|
14
|
+
end
|
15
|
+
|
16
|
+
# Blocks until all Futures have been realized and returns the values.
|
17
|
+
# This should be called _outside_ the Redis.pipelined call.
|
18
|
+
# Note that the value enqueue is the REDIS return value, not the value returned by any passed block.
|
19
|
+
# Nil values will be included in the return values (if that's what REDIS gives us).
|
20
|
+
def values
|
21
|
+
return @values unless @values.nil?
|
22
|
+
|
23
|
+
@values = []
|
24
|
+
@commands.each do |cmd|
|
25
|
+
while cmd[:future].value.is_a?(Redis::FutureNotReady)
|
26
|
+
sleep(1.0 / 100.0)
|
27
|
+
end
|
28
|
+
|
29
|
+
v = cmd[:future].value
|
30
|
+
cmd[:callback].call(v) unless cmd[:callback].nil?
|
31
|
+
@values << v
|
32
|
+
end
|
33
|
+
|
34
|
+
@values
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the enqueue REDIS commands
|
38
|
+
def commands
|
39
|
+
@commands.map {|h| h[:future] }
|
40
|
+
end
|
41
|
+
|
42
|
+
alias_method :wait, :values
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "redis_pipeliner/version"
|
2
|
+
require "redis"
|
3
|
+
require "redis_pipeliner/pipeliner"
|
4
|
+
|
5
|
+
module RedisPipeliner
|
6
|
+
class << self
|
7
|
+
# Convenience for creating a pipeline, enqueueing, and blocking until the results are processed.
|
8
|
+
def pipeline(redis, &proc)
|
9
|
+
pipeliner = RedisPipeliner::Pipeliner.new(redis)
|
10
|
+
redis.pipelined do
|
11
|
+
proc.call pipeliner
|
12
|
+
end
|
13
|
+
|
14
|
+
pipeliner.values
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "redis_pipeliner/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "redis_pipeliner"
|
7
|
+
s.version = RedisPipeliner::VERSION
|
8
|
+
s.authors = ["Dan Adams"]
|
9
|
+
s.email = ["mr.danadams@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/mrdanadams/redis_pipeliner"
|
11
|
+
s.summary = %q{Easy pipelining of REDIS commands}
|
12
|
+
s.description = %q{Easy pipelining of REDIS commands}
|
13
|
+
|
14
|
+
s.rubyforge_project = "redis_pipeliner"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency "redis"
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
s.add_development_dependency "pry"
|
24
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RedisPipeliner do
|
4
|
+
let :redis do
|
5
|
+
Redis.connect
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'supports basic usage' do
|
9
|
+
redis.del "testhash"
|
10
|
+
redis.hset "testhash", "bar", 1
|
11
|
+
redis.hset "testhash", "foo", 2
|
12
|
+
|
13
|
+
redis.hmget("testhash", "foo", "bar").map(&:to_i).inject(&:+).should == 3
|
14
|
+
|
15
|
+
total = 0
|
16
|
+
values = RedisPipeliner.pipeline redis do |pipe|
|
17
|
+
%w(bar foo).each do |key|
|
18
|
+
pipe.enqueue redis.hget("testhash", key) do |result|
|
19
|
+
total += result.to_i
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
values.map(&:to_i).inject(&:+).should == 3
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'can reference variables outside the proce' do
|
27
|
+
redis.del "testhash"
|
28
|
+
redis.hset "testhash", "bar", 1
|
29
|
+
redis.hset "testhash", "foo", 2
|
30
|
+
|
31
|
+
results = []
|
32
|
+
RedisPipeliner.pipeline redis do |pipe|
|
33
|
+
%w(bar foo).each do |key|
|
34
|
+
pipe.enqueue redis.hget("testhash", key) do |result|
|
35
|
+
results << key+result
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
results.should == %w(bar1 foo2)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should pipelines commands and return values' do
|
44
|
+
redis.del "testhash"
|
45
|
+
redis.hset "testhash", "bar", 1
|
46
|
+
redis.hset "testhash", "foo", 2
|
47
|
+
|
48
|
+
pipeliner = RedisPipeliner::Pipeliner.new(redis)
|
49
|
+
redis.pipelined do
|
50
|
+
%w(bar foo).each do |key|
|
51
|
+
# allows not having a block
|
52
|
+
pipeliner.enqueue redis.hget("testhash", key)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
pipeliner.commands.first.value.should_not be_nil # you can't test this class for type...
|
57
|
+
|
58
|
+
pipeliner.values.map(&:to_i).inject(&:+).should == 3
|
59
|
+
pipeliner.values.should === pipeliner.values
|
60
|
+
end
|
61
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'redis_pipeliner'
|
2
|
+
require 'pry'
|
3
|
+
|
4
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
5
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
6
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
7
|
+
# loaded once.
|
8
|
+
#
|
9
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
12
|
+
config.run_all_when_everything_filtered = true
|
13
|
+
config.filter_run :focus
|
14
|
+
|
15
|
+
# Run specs in random order to surface order dependencies. If you find an
|
16
|
+
# order dependency and want to debug it, you can fix the order by providing
|
17
|
+
# the seed, which is printed after each run.
|
18
|
+
# --seed 1234
|
19
|
+
config.order = 'random'
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redis_pipeliner
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dan Adams
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: redis
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: pry
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Easy pipelining of REDIS commands
|
63
|
+
email:
|
64
|
+
- mr.danadams@gmail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- .rspec
|
71
|
+
- Gemfile
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- lib/redis_pipeliner.rb
|
75
|
+
- lib/redis_pipeliner/pipeliner.rb
|
76
|
+
- lib/redis_pipeliner/version.rb
|
77
|
+
- redis_pipeliner.gemspec
|
78
|
+
- spec/pipeliner_spec.rb
|
79
|
+
- spec/spec_helper.rb
|
80
|
+
homepage: https://github.com/mrdanadams/redis_pipeliner
|
81
|
+
licenses: []
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ! '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubyforge_project: redis_pipeliner
|
100
|
+
rubygems_version: 1.8.24
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: Easy pipelining of REDIS commands
|
104
|
+
test_files:
|
105
|
+
- spec/pipeliner_spec.rb
|
106
|
+
- spec/spec_helper.rb
|