sidekiq-grouping 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4dbe0fe595dfbe28a39a687925d5078d7c1df9d7793df783cf4ed448910136f9
4
- data.tar.gz: a2f48474c1ec7ec9a632999f17cf6159d15490dfa6d06ff4eb5b85221fec776e
3
+ metadata.gz: be68edcca66201c059ee0a9d18c163cf899a00e1d5013a64ca72206dc9b48e9e
4
+ data.tar.gz: 55bfb74ecd86ce73a4fedd115e8adfb1a8531ab3460e008a74ce6c4b9c111892
5
5
  SHA512:
6
- metadata.gz: fb1a5cc739670f30b05cd477c3797eca72d0469307ebf4e8ea13841d49ed050b2a0814f11e29b81ce5ce7d039e99f6661110b528d8ccf22cd609654fd445b4b0
7
- data.tar.gz: 31f85b457287ae7e1dee1c52e008099f9806cd520132c7bad048a42d4c67ee0550cb51610d95059b3135c7a39b2865058f2d2092623864b71c23010bebc03017
6
+ metadata.gz: 709bbcf1bf236c18e330adba5fd5c0c23a5cce73f869fcc152c3b846f306c28189c3942bb6bfde1875f1895e0a799e7367e1e4bddd4abb8c6147f16490101841
7
+ data.tar.gz: d46cb5d01ccaaae7c52f7768e8f8159a8be287e58852f6c167d4ebfbf01e37da70640b40b4476c868fa01a4874d717bbb4c0cbe9108418d1180c4a2c9d95c3ce
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: Lint
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+ branches:
9
+ - master
10
+
11
+ jobs:
12
+ rubocop:
13
+ name: Rubocop
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v3
17
+
18
+ - name: Set up Ruby ${{ matrix.ruby_version }}
19
+ uses: ruby/setup-ruby@v1
20
+ with:
21
+ bundler: 2
22
+ ruby-version: 2.7
23
+
24
+ - uses: actions/cache@v3
25
+ with:
26
+ path: vendor/bundle
27
+ key: ${{ runner.os }}-gems-${{ matrix.ruby-version }}-${{ hashFiles('./*.gemspec') }}
28
+ restore-keys: |
29
+ ${{ runner.os }}-gems-
30
+
31
+ - name: Bundle install
32
+ run: |
33
+ bundle config path vendor/bundle
34
+ bundle install --jobs 4 --retry 3
35
+
36
+ - name: Run Rubocop
37
+ run: bundle exec rubocop
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: Test
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+ branches:
9
+ - master
10
+
11
+ jobs:
12
+ rspec:
13
+ name: Rspec
14
+ runs-on: ubuntu-latest
15
+ services:
16
+ redis:
17
+ image: redis:6
18
+ options: >-
19
+ --health-cmd "redis-cli ping"
20
+ --health-interval 10s
21
+ --health-timeout 5s
22
+ --health-retries 5
23
+ ports:
24
+ - 6379:6379
25
+ strategy:
26
+ matrix:
27
+ include:
28
+ - { ruby_version: 2.7 }
29
+ - { ruby_version: 3.0 }
30
+ - { ruby_version: 3.1 }
31
+ steps:
32
+ - uses: actions/checkout@v3
33
+
34
+ - name: Set up Ruby ${{ matrix.ruby_version }}
35
+ uses: ruby/setup-ruby@v1
36
+ with:
37
+ bundler: 2
38
+ ruby-version: ${{ matrix.ruby_version }}
39
+
40
+ # Appraisal doesn't support vendored install
41
+ # See: https://github.com/thoughtbot/appraisal/issues/173
42
+ # https://github.com/thoughtbot/appraisal/pull/174
43
+ - name: Bundle install
44
+ run: |
45
+ bundle install --jobs 4 --retry 3
46
+ bundle exec appraisal install
47
+
48
+ - name: Run tests
49
+ run: bundle exec appraisal rspec
data/.rubocop.yml CHANGED
@@ -1,8 +1,15 @@
1
+ ---
1
2
  require: rubocop-rspec
2
3
 
3
4
  AllCops:
5
+ TargetRubyVersion: 2.7.0
6
+ SuggestExtensions: false
7
+ NewCops: enable
4
8
  Include:
5
9
  - ./Gemfile
10
+ - ./Rakefile
11
+ - '*.gemspec'
12
+ - '**/*.rb'
6
13
 
7
14
  Documentation:
8
15
  Enabled: false
@@ -10,8 +17,29 @@ Documentation:
10
17
  Style/StringLiterals:
11
18
  EnforcedStyle: double_quotes
12
19
 
13
- Style/ClassAndModuleChildren:
14
- EnforcedStyle: compact
15
-
16
20
  RSpec/FilePath:
17
21
  Enabled: false
22
+
23
+ RSpec/ExampleLength:
24
+ Enabled: false
25
+
26
+ RSpec/AnyInstance:
27
+ Enabled: false
28
+
29
+ Metrics/MethodLength:
30
+ Max: 15
31
+
32
+ Metrics/ClassLength:
33
+ Max: 150
34
+
35
+ Layout/LineLength:
36
+ Max: 80
37
+
38
+ Layout/FirstArgumentIndentation:
39
+ EnforcedStyle: consistent
40
+
41
+ Layout/FirstMethodArgumentLineBreak:
42
+ Enabled: true
43
+
44
+ Layout/MultilineMethodArgumentLineBreaks:
45
+ Enabled: true
data/Appraisals CHANGED
@@ -14,6 +14,10 @@ appraise 'sidekiq-5.0' do
14
14
  gem 'sidekiq', '~> 5.0.0'
15
15
  end
16
16
 
17
- appraise 'sidekiq-master' do
18
- gem 'sidekiq', github: 'mperham/sidekiq'
17
+ appraise 'sidekiq-6.0' do
18
+ gem 'sidekiq', '~> 6.0.0'
19
+ end
20
+
21
+ appraise 'sidekiq-6.5' do
22
+ gem 'sidekiq', '~> 6.5.0'
19
23
  end
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Gem Version](https://badge.fury.io/rb/sidekiq-grouping.svg)](https://rubygems.org/gems/sidekiq-grouping)
2
+
1
3
  # Sidekiq::Grouping
2
4
 
3
5
  <a href="https://evilmartians.com/?utm_source=sidekiq-grouping-gem">
@@ -23,7 +25,7 @@ class ElasticBulkIndexWorker
23
25
  include Sidekiq::Worker
24
26
 
25
27
  sidekiq_options(
26
- queue: :elasic_bulks,
28
+ queue: :elastic_bulks,
27
29
  batch_flush_size: 30, # Jobs will be combined when queue size exceeds 30
28
30
  batch_flush_interval: 60, # Jobs will be combined every 60 seconds
29
31
  retry: 5
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sidekiq", "~> 6.0.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sidekiq", "~> 6.5.0"
6
+
7
+ gemspec path: "../"
data/lefthook.yml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ # Git hooks configuration
3
+ #
4
+ # See: github.com/evilmartians/lefthook
5
+
6
+ pre-commit:
7
+ commands:
8
+ rubocop:
9
+ glob: "*.rb"
10
+ run: bundle exec rubocop -A {staged_files} && git add {staged_files}
11
+
12
+ pre-push:
13
+ commands:
14
+ rspec:
15
+ run: bundle exec appraisal rspec
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sidekiq
2
4
  module Grouping
3
5
  class Batch
4
- def initialize(worker_class, queue, redis_pool = nil)
6
+ def initialize(worker_class, queue, _redis_pool = nil)
5
7
  @worker_class = worker_class
6
8
  @queue = queue
7
9
  @name = "#{worker_class.underscore}:#{queue}"
@@ -12,11 +14,18 @@ module Sidekiq
12
14
 
13
15
  def add(msg)
14
16
  msg = msg.to_json
15
- @redis.push_msg(@name, msg, enqueue_similar_once?) if should_add? msg
17
+ return unless should_add? msg
18
+
19
+ @redis.push_msg(
20
+ @name,
21
+ msg,
22
+ remember_unique: enqueue_similar_once?
23
+ )
16
24
  end
17
25
 
18
- def should_add? msg
26
+ def should_add?(msg)
19
27
  return true unless enqueue_similar_once?
28
+
20
29
  !@redis.enqueued?(@name, msg)
21
30
  end
22
31
 
@@ -25,19 +34,19 @@ module Sidekiq
25
34
  end
26
35
 
27
36
  def chunk_size
28
- worker_class_options['batch_size'] ||
37
+ worker_class_options["batch_size"] ||
29
38
  Sidekiq::Grouping::Config.max_batch_size
30
39
  end
31
40
 
32
41
  def pluck_size
33
- worker_class_options['batch_flush_size'] ||
42
+ worker_class_options["batch_flush_size"] ||
34
43
  chunk_size
35
44
  end
36
45
 
37
46
  def pluck
38
- if @redis.lock(@name)
39
- @redis.pluck(@name, pluck_size).map { |value| JSON.parse(value) }
40
- end
47
+ return unless @redis.lock(@name)
48
+
49
+ @redis.pluck(@name, pluck_size).map { |value| JSON.parse(value) }
41
50
  end
42
51
 
43
52
  def flush
@@ -46,9 +55,9 @@ module Sidekiq
46
55
 
47
56
  chunk.each_slice(chunk_size) do |subchunk|
48
57
  Sidekiq::Client.push(
49
- 'class' => @worker_class,
50
- 'queue' => @queue,
51
- 'args' => [true, subchunk]
58
+ "class" => @worker_class,
59
+ "queue" => @queue,
60
+ "args" => [true, subchunk]
52
61
  )
53
62
  end
54
63
  set_current_time_as_last
@@ -74,10 +83,11 @@ module Sidekiq
74
83
  end
75
84
 
76
85
  def next_execution_time
77
- if interval = worker_class_options['batch_flush_interval']
78
- last_time = last_execution_time
79
- last_time + interval.seconds if last_time
80
- end
86
+ interval = worker_class_options["batch_flush_interval"]
87
+ return unless interval
88
+
89
+ last_time = last_execution_time
90
+ last_time + interval.seconds if last_time
81
91
  end
82
92
 
83
93
  def delete
@@ -99,13 +109,13 @@ module Sidekiq
99
109
  if last_time.blank?
100
110
  set_current_time_as_last
101
111
  false
102
- else
103
- next_time < Time.now if next_time
112
+ elsif next_time
113
+ next_time < Time.now
104
114
  end
105
115
  end
106
116
 
107
117
  def enqueue_similar_once?
108
- worker_class_options['batch_unique'] == true
118
+ worker_class_options["batch_unique"] == true
109
119
  end
110
120
 
111
121
  def set_current_time_as_last
@@ -122,7 +132,7 @@ module Sidekiq
122
132
  end
123
133
 
124
134
  def extract_worker_klass_and_queue(name)
125
- klass, queue = name.split(':')
135
+ klass, queue = name.split(":")
126
136
  [klass.camelize, queue]
127
137
  end
128
138
  end
@@ -1,29 +1,39 @@
1
- module Sidekiq::Grouping::Config
2
- include ActiveSupport::Configurable
1
+ # frozen_string_literal: true
3
2
 
4
- def self.options
5
- Sidekiq.options[:grouping] || Sidekiq.options["grouping"] || {} # sidekiq 5.x use symbol in keys
6
- end
3
+ module Sidekiq
4
+ module Grouping
5
+ module Config
6
+ include ActiveSupport::Configurable
7
7
 
8
- # Queue size overflow check polling interval
9
- config_accessor :poll_interval do
10
- options[:poll_interval] || 3
11
- end
8
+ def self.options
9
+ if Sidekiq.respond_to?(:[])
10
+ Sidekiq[:grouping] || Sidekiq["grouping"] || {}
11
+ else
12
+ Sidekiq.options[:grouping] || Sidekiq.options["grouping"] || {}
13
+ end
14
+ end
12
15
 
13
- # Maximum batch size
14
- config_accessor :max_batch_size do
15
- options[:max_batch_size] || 1000
16
- end
16
+ # Queue size overflow check polling interval
17
+ config_accessor :poll_interval do
18
+ options[:poll_interval] || 3
19
+ end
17
20
 
18
- # Batch queue flush lock timeout
19
- config_accessor :lock_ttl do
20
- options[:lock_ttl] || 1
21
- end
21
+ # Maximum batch size
22
+ config_accessor :max_batch_size do
23
+ options[:max_batch_size] || 1000
24
+ end
25
+
26
+ # Batch queue flush lock timeout
27
+ config_accessor :lock_ttl do
28
+ options[:lock_ttl] || 1
29
+ end
22
30
 
23
- # Option to override how Sidekiq::Grouping know about tests env
24
- config_accessor :tests_env do
25
- options[:tests_env] || (
26
- defined?(::Rails) && Rails.respond_to?(:env) && Rails.env.test?
27
- )
31
+ # Option to override how Sidekiq::Grouping know about tests env
32
+ config_accessor :tests_env do
33
+ options[:tests_env] || (
34
+ defined?(::Rails) && Rails.respond_to?(:env) && Rails.env.test?
35
+ )
36
+ end
37
+ end
28
38
  end
29
39
  end
@@ -1,42 +1,54 @@
1
- class Sidekiq::Grouping::Flusher
2
- def flush
3
- batches = Sidekiq::Grouping::Batch.all.map do |batch|
4
- batch if batch.could_flush?
5
- end
6
- flush_batches(batches)
7
- end
1
+ # frozen_string_literal: true
8
2
 
9
- def force_flush_for_test!
10
- unless Sidekiq::Grouping::Config.tests_env
11
- Sidekiq::Grouping.logger.warn(
12
- "**************************************************"
13
- )
14
- Sidekiq::Grouping.logger.warn([
15
- "⛔️ force_flush_for_test! for testing API, ",
16
- "but this is not the test environment. ",
17
- "Please check your environment or ",
18
- "change 'tests_env' to cover this one"
19
- ].join)
20
- Sidekiq::Grouping.logger.warn(
21
- "**************************************************"
22
- )
23
- end
24
- flush_batches(Sidekiq::Grouping::Batch.all)
25
- end
3
+ module Sidekiq
4
+ module Grouping
5
+ class Flusher
6
+ def flush
7
+ batches = Sidekiq::Grouping::Batch.all.map do |batch|
8
+ batch if batch.could_flush?
9
+ end
10
+ flush_batches(batches)
11
+ end
26
12
 
27
- private
13
+ def force_flush_for_test!
14
+ unless Sidekiq::Grouping::Config.tests_env
15
+ Sidekiq::Grouping.logger.warn(
16
+ "**************************************************"
17
+ )
18
+ Sidekiq::Grouping.logger.warn(
19
+ "⛔️ force_flush_for_test! for testing API, " \
20
+ "but this is not the test environment. " \
21
+ "Please check your environment or " \
22
+ "change 'tests_env' to cover this one"
23
+ )
24
+ Sidekiq::Grouping.logger.warn(
25
+ "**************************************************"
26
+ )
27
+ end
28
+ flush_batches(Sidekiq::Grouping::Batch.all)
29
+ end
28
30
 
29
- def flush_batches(batches)
30
- batches.compact!
31
- flush_concrete(batches)
32
- end
31
+ private
32
+
33
+ def flush_batches(batches)
34
+ batches.compact!
35
+ flush_concrete(batches)
36
+ end
33
37
 
34
- def flush_concrete(batches)
35
- return if batches.empty?
36
- names = batches.map { |batch| "#{batch.worker_class} in #{batch.queue}" }
37
- Sidekiq::Grouping.logger.info(
38
- "[Sidekiq::Grouping] Trying to flush batched queues: #{names.join(',')}"
39
- ) unless Sidekiq::Grouping::Config.tests_env
40
- batches.each(&:flush)
38
+ def flush_concrete(batches)
39
+ return if batches.empty?
40
+
41
+ names = batches.map do |batch|
42
+ "#{batch.worker_class} in #{batch.queue}"
43
+ end
44
+ unless Sidekiq::Grouping::Config.tests_env
45
+ Sidekiq::Grouping.logger.info(
46
+ "[Sidekiq::Grouping] Trying to flush batched queues: " \
47
+ "#{names.join(',')}"
48
+ )
49
+ end
50
+ batches.each(&:flush)
51
+ end
52
+ end
41
53
  end
42
54
  end
@@ -1,13 +1,19 @@
1
- class Sidekiq::Grouping::FlusherObserver
2
- def update(time, _result, ex)
3
- if ex.is_a?(Concurrent::TimeoutError)
4
- Sidekiq::Grouping.logger.error(
5
- "[Sidekiq::Grouping] (#{time}) Execution timed out\n"
6
- )
7
- elsif ex.present?
8
- Sidekiq::Grouping.logger.error(
9
- "[Sidekiq::Grouping] Execution failed with error #{ex}\n"
10
- )
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ module Grouping
5
+ class FlusherObserver
6
+ def update(time, _result, exception)
7
+ if exception.is_a?(Concurrent::TimeoutError)
8
+ Sidekiq::Grouping.logger.error(
9
+ "[Sidekiq::Grouping] (#{time}) Execution timed out\n"
10
+ )
11
+ elsif exception.present?
12
+ Sidekiq::Grouping.logger.error(
13
+ "[Sidekiq::Grouping] Execution failed with error #{exception}\n"
14
+ )
15
+ end
16
+ end
11
17
  end
12
18
  end
13
19
  end
@@ -1,43 +1,56 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sidekiq
2
4
  module Grouping
3
5
  class Middleware
6
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
4
7
  def call(worker_class, msg, queue, redis_pool = nil)
5
- return yield if (defined?(Sidekiq::Testing) && Sidekiq::Testing.inline?)
6
-
7
- worker_class = worker_class.camelize.constantize if worker_class.is_a?(String)
8
+ if worker_class.is_a?(String)
9
+ worker_class = worker_class.camelize.constantize
10
+ end
8
11
  options = worker_class.get_sidekiq_options
9
12
 
10
13
  batch =
11
- options.key?('batch_flush_size') ||
12
- options.key?('batch_flush_interval') ||
13
- options.key?('batch_size')
14
+ options.key?("batch_flush_size") ||
15
+ options.key?("batch_flush_interval") ||
16
+ options.key?("batch_size")
14
17
 
15
18
  passthrough =
16
- msg['args'] &&
17
- msg['args'].is_a?(Array) &&
18
- msg['args'].try(:first) == true
19
+ msg["args"].is_a?(Array) &&
20
+ msg["args"].try(:first) == true
19
21
 
20
22
  retrying = msg["failed_at"].present?
21
23
 
22
24
  return yield unless batch
23
25
 
24
- if !(passthrough || retrying)
25
- add_to_batch(worker_class, queue, msg, redis_pool)
26
- else
27
- msg['args'].shift if passthrough
26
+ if inline_mode?
27
+ wrapped_args = [[msg["args"]]]
28
+ msg["args"] = wrapped_args
29
+ return yield
30
+ end
31
+
32
+ if passthrough || retrying
33
+ msg["args"].shift if passthrough
28
34
  yield
35
+ else
36
+ add_to_batch(worker_class, queue, msg, redis_pool)
29
37
  end
30
38
  end
39
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
31
40
 
32
41
  private
33
42
 
34
43
  def add_to_batch(worker_class, queue, msg, redis_pool = nil)
35
44
  Sidekiq::Grouping::Batch
36
45
  .new(worker_class.name, queue, redis_pool)
37
- .add(msg['args'])
46
+ .add(msg["args"])
38
47
 
39
48
  nil
40
49
  end
50
+
51
+ def inline_mode?
52
+ defined?(Sidekiq::Testing) && Sidekiq::Testing.inline?
53
+ end
41
54
  end
42
55
  end
43
56
  end
@@ -1,22 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sidekiq
2
4
  module Grouping
3
5
  class Redis
4
-
5
6
  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)
7
+ local pluck_values = redis.call('lpop', KEYS[1], ARGV[1]) or {}
8
+ if #pluck_values > 0 then
9
+ redis.call('srem', KEYS[2], unpack(pluck_values))
10
10
  end
11
11
  return pluck_values
12
12
  SCRIPT
13
13
 
14
- def push_msg(name, msg, remember_unique = false)
14
+ def push_msg(name, msg, remember_unique: false)
15
15
  redis do |conn|
16
16
  conn.multi do |pipeline|
17
- pipeline.sadd(ns("batches"), name)
17
+ sadd = pipeline.respond_to?(:sadd?) ? :sadd? : :sadd
18
+ pipeline.public_send(sadd, ns("batches"), name)
18
19
  pipeline.rpush(ns(name), msg)
19
- pipeline.sadd(unique_messages_key(name), msg) if remember_unique
20
+ if remember_unique
21
+ pipeline.public_send(
22
+ sadd,
23
+ unique_messages_key(name),
24
+ msg
25
+ )
26
+ end
20
27
  end
21
28
  end
22
29
  end
@@ -46,7 +53,9 @@ module Sidekiq
46
53
  end
47
54
 
48
55
  def set_last_execution_time(name, time)
49
- redis { |conn| conn.set(ns("last_execution_time:#{name}"), time.to_json) }
56
+ redis do |conn|
57
+ conn.set(ns("last_execution_time:#{name}"), time.to_json)
58
+ end
50
59
  end
51
60
 
52
61
  def lock(name)
@@ -60,13 +69,13 @@ module Sidekiq
60
69
  redis do |conn|
61
70
  conn.del(ns("last_execution_time:#{name}"))
62
71
  conn.del(ns(name))
63
- conn.srem(ns('batches'), name)
72
+ conn.srem(ns("batches"), name)
64
73
  end
65
74
  end
66
75
 
67
76
  private
68
77
 
69
- def unique_messages_key name
78
+ def unique_messages_key(name)
70
79
  ns("#{name}:unique_messages")
71
80
  end
72
81
 
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sidekiq
2
4
  module Grouping
3
- VERSION = "1.1.0"
5
+ VERSION = "1.2.0"
4
6
  end
5
7
  end