sidekiq-batching 0.0.3 → 0.0.4
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.
- 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
|
+

|
|
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
|