sidekiq-batching 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -0
- data/lib/sidekiq/batching/actor.rb +1 -2
- data/lib/sidekiq/batching/batch.rb +11 -1
- data/lib/sidekiq/batching/redis.rb +31 -12
- data/lib/sidekiq/batching/version.rb +1 -1
- data/spec/modules/batch_spec.rb +61 -11
- data/spec/modules/redis_spec.rb +47 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/support/test_workers.rb +10 -1
- data/web.png +0 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e6d0c4e5564c036527dcc48e5336efc2193bcbab
|
4
|
+
data.tar.gz: 228c68f5f5005a261c5cf1c90398575628a8a482
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1ebf0255ad1c14f6fd7b6242b934a0ef6a92ed064256991da94fb988596baf265b9f9d4b32f197326ee2d2f0781c80efca049c3fc757fda55d3d1c5c8ee535d
|
7
|
+
data.tar.gz: 6208a1a7020cfc52799051efda54a92daed302bdda620925c6668aba03d225301fab07724cf7f89b8044d1e4dbc93d61f53408d70b337105a1edf85ba79261f7
|
data/README.md
CHANGED
@@ -6,6 +6,8 @@ Useful for:
|
|
6
6
|
* Grouping asynchronous API index calls into bulks for bulk updating/indexing.
|
7
7
|
* Periodical batch updating of recently changing database counters.
|
8
8
|
|
9
|
+
Sponsored by [Evil Martians](http://evilmartians.com)
|
10
|
+
|
9
11
|
## Usage
|
10
12
|
|
11
13
|
Create a worker:
|
@@ -50,6 +52,10 @@ This jobs will be grouped into a single job which will be performed with the sin
|
|
50
52
|
|
51
53
|
This will happen for every 30 jobs in a row or every 60 seconds.
|
52
54
|
|
55
|
+
## Web UI
|
56
|
+
|
57
|
+
![Web UI](web.png)
|
58
|
+
|
53
59
|
Add this line to your `config/routes.rb` to activate web UI:
|
54
60
|
|
55
61
|
```ruby
|
@@ -12,7 +12,13 @@ module Sidekiq
|
|
12
12
|
attr_reader :name, :worker_class, :queue
|
13
13
|
|
14
14
|
def add(msg)
|
15
|
-
|
15
|
+
msg = msg.to_json
|
16
|
+
@redis.push_msg(@name, msg, enqueue_similar_once?) if should_add? msg
|
17
|
+
end
|
18
|
+
|
19
|
+
def should_add? msg
|
20
|
+
return true unless enqueue_similar_once?
|
21
|
+
!@redis.enqueued?(@name, msg)
|
16
22
|
end
|
17
23
|
|
18
24
|
def size
|
@@ -94,6 +100,10 @@ module Sidekiq
|
|
94
100
|
end
|
95
101
|
end
|
96
102
|
|
103
|
+
def enqueue_similar_once?
|
104
|
+
worker_class_options['batch_unique'] == true
|
105
|
+
end
|
106
|
+
|
97
107
|
def set_current_time_as_last
|
98
108
|
@redis.set_last_execution_time(@name, Time.now)
|
99
109
|
end
|
@@ -1,10 +1,29 @@
|
|
1
1
|
module Sidekiq
|
2
2
|
module Batching
|
3
3
|
class Redis
|
4
|
-
|
4
|
+
|
5
|
+
PLUCK_SCRIPT = <<-SCRIPT
|
6
|
+
local pluck_values = redis.call('lrange', KEYS[1], 0, ARGV[1] - 1)
|
7
|
+
redis.call('ltrim', KEYS[1], ARGV[1], -1)
|
8
|
+
for k, v in pairs(pluck_values) do
|
9
|
+
redis.call('srem', KEYS[2], v)
|
10
|
+
end
|
11
|
+
return pluck_values
|
12
|
+
SCRIPT
|
13
|
+
|
14
|
+
def push_msg(name, msg, remember_unique = false)
|
15
|
+
redis do |conn|
|
16
|
+
conn.multi do
|
17
|
+
conn.sadd(ns('batches'), name)
|
18
|
+
conn.rpush(ns(name), msg)
|
19
|
+
conn.sadd(unique_messages_key(name), msg) if remember_unique
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def enqueued?(name, msg)
|
5
25
|
redis do |conn|
|
6
|
-
conn.
|
7
|
-
conn.rpush(ns(name), msg)
|
26
|
+
conn.sismember(unique_messages_key(name), msg)
|
8
27
|
end
|
9
28
|
end
|
10
29
|
|
@@ -17,14 +36,9 @@ module Sidekiq
|
|
17
36
|
end
|
18
37
|
|
19
38
|
def pluck(name, limit)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
conn.ltrim(ns(name), limit, -1)
|
24
|
-
end
|
25
|
-
|
26
|
-
result.first
|
27
|
-
end
|
39
|
+
keys = [ns(name), unique_messages_key(name)]
|
40
|
+
args = [limit]
|
41
|
+
redis { |conn| conn.eval PLUCK_SCRIPT, keys, args }
|
28
42
|
end
|
29
43
|
|
30
44
|
def get_last_execution_time(name)
|
@@ -55,6 +69,11 @@ module Sidekiq
|
|
55
69
|
end
|
56
70
|
|
57
71
|
private
|
72
|
+
|
73
|
+
def unique_messages_key name
|
74
|
+
ns("#{name}:unique_messages")
|
75
|
+
end
|
76
|
+
|
58
77
|
def ns(key = nil)
|
59
78
|
"batching:#{key}"
|
60
79
|
end
|
@@ -64,4 +83,4 @@ module Sidekiq
|
|
64
83
|
end
|
65
84
|
end
|
66
85
|
end
|
67
|
-
end
|
86
|
+
end
|
data/spec/modules/batch_spec.rb
CHANGED
@@ -29,34 +29,34 @@ describe Sidekiq::Batching::Batch do
|
|
29
29
|
it 'must flush if limit exceeds for limit worker' do
|
30
30
|
batch = subject.new(BatchedSizeWorker.name, 'batched_size')
|
31
31
|
|
32
|
-
expect(batch.could_flush?).to
|
32
|
+
expect(batch.could_flush?).to be_falsy
|
33
33
|
BatchedSizeWorker.perform_async('bar')
|
34
|
-
expect(batch.could_flush?).to
|
34
|
+
expect(batch.could_flush?).to be_falsy
|
35
35
|
4.times { BatchedSizeWorker.perform_async('bar') }
|
36
|
-
expect(batch.could_flush?).to
|
36
|
+
expect(batch.could_flush?).to be_truthy
|
37
37
|
end
|
38
38
|
|
39
39
|
it 'must flush if limit exceeds for both worker' do
|
40
40
|
batch = subject.new(BatchedBothWorker.name, 'batched_both')
|
41
41
|
|
42
|
-
expect(batch.could_flush?).to
|
42
|
+
expect(batch.could_flush?).to be_falsy
|
43
43
|
BatchedBothWorker.perform_async('bar')
|
44
|
-
expect(batch.could_flush?).to
|
44
|
+
expect(batch.could_flush?).to be_falsy
|
45
45
|
4.times { BatchedBothWorker.perform_async('bar') }
|
46
|
-
expect(batch.could_flush?).to
|
46
|
+
expect(batch.could_flush?).to be_truthy
|
47
47
|
end
|
48
48
|
|
49
49
|
it 'must flush if limit okay but time came' do
|
50
50
|
batch = subject.new(BatchedIntervalWorker.name, 'batched_interval')
|
51
51
|
|
52
|
-
expect(batch.could_flush?).to
|
52
|
+
expect(batch.could_flush?).to be_falsy
|
53
53
|
BatchedIntervalWorker.perform_async('bar')
|
54
|
-
expect(batch.could_flush?).to
|
54
|
+
expect(batch.could_flush?).to be_falsy
|
55
55
|
expect(batch.size).to eq(1)
|
56
56
|
|
57
57
|
Timecop.travel(2.hours.since)
|
58
58
|
|
59
|
-
expect(batch.could_flush?).to
|
59
|
+
expect(batch.could_flush?).to be_truthy
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
@@ -64,7 +64,7 @@ describe Sidekiq::Batching::Batch do
|
|
64
64
|
it 'must put wokrer to queue on flush' do
|
65
65
|
batch = subject.new(BatchedSizeWorker.name, 'batched_size')
|
66
66
|
|
67
|
-
expect(batch.could_flush?).to
|
67
|
+
expect(batch.could_flush?).to be_falsy
|
68
68
|
10.times { BatchedSizeWorker.perform_async('bar') }
|
69
69
|
batch.flush
|
70
70
|
expect(BatchedSizeWorker).to have_enqueued_job([["bar"], ["bar"], ["bar"]])
|
@@ -72,6 +72,56 @@ describe Sidekiq::Batching::Batch do
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
+
context 'with similar args' do
|
76
|
+
context 'option batch_unique = true' do
|
77
|
+
it 'enqueues once' do
|
78
|
+
batch = subject.new(BatchedUniqueArgsWorker.name, 'batched_unique_args')
|
79
|
+
3.times { BatchedUniqueArgsWorker.perform_async('bar', 1) }
|
80
|
+
expect(batch.size).to eq(1)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'enqueues once each unique set of args' do
|
84
|
+
batch = subject.new(BatchedUniqueArgsWorker.name, 'batched_unique_args')
|
85
|
+
3.times { BatchedUniqueArgsWorker.perform_async('bar', 1) }
|
86
|
+
6.times { BatchedUniqueArgsWorker.perform_async('baz', 1) }
|
87
|
+
3.times { BatchedUniqueArgsWorker.perform_async('bar', 1) }
|
88
|
+
2.times { BatchedUniqueArgsWorker.perform_async('baz', 3) }
|
89
|
+
7.times { BatchedUniqueArgsWorker.perform_async('bar', 1) }
|
90
|
+
expect(batch.size).to eq(3)
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'flushing' do
|
94
|
+
|
95
|
+
it 'works' do
|
96
|
+
batch = subject.new(BatchedUniqueArgsWorker.name, 'batched_unique_args')
|
97
|
+
2.times { BatchedUniqueArgsWorker.perform_async('bar', 1) }
|
98
|
+
2.times { BatchedUniqueArgsWorker.perform_async('baz', 1) }
|
99
|
+
batch.flush
|
100
|
+
expect(batch.size).to eq(0)
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'allows to enqueue again after flush' do
|
104
|
+
batch = subject.new(BatchedUniqueArgsWorker.name, 'batched_unique_args')
|
105
|
+
2.times { BatchedUniqueArgsWorker.perform_async('bar', 1) }
|
106
|
+
2.times { BatchedUniqueArgsWorker.perform_async('baz', 1) }
|
107
|
+
batch.flush
|
108
|
+
BatchedUniqueArgsWorker.perform_async('bar', 1)
|
109
|
+
BatchedUniqueArgsWorker.perform_async('baz', 1)
|
110
|
+
expect(batch.size).to eq(2)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'batch_unique is not specified' do
|
117
|
+
it 'enqueues all' do
|
118
|
+
batch = subject.new(BatchedSizeWorker.name, 'batched_size')
|
119
|
+
3.times { BatchedSizeWorker.perform_async('bar', 1) }
|
120
|
+
expect(batch.size).to eq(3)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
75
125
|
private
|
76
126
|
def expect_batch(klass, queue)
|
77
127
|
expect(klass).to_not have_enqueued_job('bar')
|
@@ -83,4 +133,4 @@ describe Sidekiq::Batching::Batch do
|
|
83
133
|
expect(stats.first.queue).to eq(queue)
|
84
134
|
expect(batch.pluck).to eq [['bar']]
|
85
135
|
end
|
86
|
-
end
|
136
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sidekiq::Batching::Redis do
|
4
|
+
subject { Sidekiq::Batching::Redis.new }
|
5
|
+
|
6
|
+
let(:queue_name) { "my_queue" }
|
7
|
+
let(:key) { "batching:#{queue_name}" }
|
8
|
+
let(:unique_key) { "batching:#{queue_name}:unique_messages" }
|
9
|
+
|
10
|
+
describe "#push_msg" do
|
11
|
+
it "adds message to queue" do
|
12
|
+
subject.push_msg(queue_name, 'My message')
|
13
|
+
expect(redis { |c| c.llen key }).to eq 1
|
14
|
+
expect(redis { |c| c.lrange key, 0, 1 }).to eq ['My message']
|
15
|
+
expect(redis { |c| c.smembers unique_key}).to eq []
|
16
|
+
end
|
17
|
+
|
18
|
+
it "remembers unique message if specified" do
|
19
|
+
subject.push_msg(queue_name, 'My message', true)
|
20
|
+
expect(redis { |c| c.smembers unique_key}).to eq ['My message']
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#pluck" do
|
25
|
+
it "removes messages from queue" do
|
26
|
+
subject.push_msg(queue_name, "Message 1")
|
27
|
+
subject.push_msg(queue_name, "Message 2")
|
28
|
+
subject.pluck(queue_name, 2)
|
29
|
+
expect(redis { |c| c.llen key }).to eq 0
|
30
|
+
end
|
31
|
+
|
32
|
+
it "forgets unique messages" do
|
33
|
+
subject.push_msg(queue_name, "Message 1", true)
|
34
|
+
subject.push_msg(queue_name, "Message 2", true)
|
35
|
+
expect(redis { |c| c.scard unique_key }).to eq 2
|
36
|
+
subject.pluck(queue_name, 2)
|
37
|
+
expect(redis { |c| c.smembers unique_key }).to eq []
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def redis(&block)
|
44
|
+
Sidekiq.redis(&block)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -30,4 +30,13 @@ class BatchedBothWorker
|
|
30
30
|
|
31
31
|
def perform(foo)
|
32
32
|
end
|
33
|
-
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class BatchedUniqueArgsWorker
|
36
|
+
include Sidekiq::Worker
|
37
|
+
|
38
|
+
sidekiq_options queue: :batched_unique_args, batch_size: 3, batch_unique: true
|
39
|
+
|
40
|
+
def perform(foo)
|
41
|
+
end
|
42
|
+
end
|
data/web.png
ADDED
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-batching
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Victor Sokolov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-01-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -147,8 +147,10 @@ files:
|
|
147
147
|
- lib/sidekiq/batching/web.rb
|
148
148
|
- sidekiq-batching.gemspec
|
149
149
|
- spec/modules/batch_spec.rb
|
150
|
+
- spec/modules/redis_spec.rb
|
150
151
|
- spec/spec_helper.rb
|
151
152
|
- spec/support/test_workers.rb
|
153
|
+
- web.png
|
152
154
|
homepage: http://github.com/gzigzigzeo/sidekiq-batching
|
153
155
|
licenses:
|
154
156
|
- MIT
|
@@ -169,11 +171,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
169
171
|
version: '0'
|
170
172
|
requirements: []
|
171
173
|
rubyforge_project:
|
172
|
-
rubygems_version: 2.
|
174
|
+
rubygems_version: 2.4.3
|
173
175
|
signing_key:
|
174
176
|
specification_version: 4
|
175
177
|
summary: Allows identical sidekiq jobs to be processed with a single background call
|
176
178
|
test_files:
|
177
179
|
- spec/modules/batch_spec.rb
|
180
|
+
- spec/modules/redis_spec.rb
|
178
181
|
- spec/spec_helper.rb
|
179
182
|
- spec/support/test_workers.rb
|