sidekiq-grouping 1.1.0 → 1.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4dbe0fe595dfbe28a39a687925d5078d7c1df9d7793df783cf4ed448910136f9
4
- data.tar.gz: a2f48474c1ec7ec9a632999f17cf6159d15490dfa6d06ff4eb5b85221fec776e
3
+ metadata.gz: 1626ba86eaf1cb747b25ca6770aa5fa982fbc19f4b8f55bdb49983331ffa8761
4
+ data.tar.gz: bdd1ee7ec9d60582fdf2db69edacbb5a3592f1caa77d60ea60e09f259a0dbca4
5
5
  SHA512:
6
- metadata.gz: fb1a5cc739670f30b05cd477c3797eca72d0469307ebf4e8ea13841d49ed050b2a0814f11e29b81ce5ce7d039e99f6661110b528d8ccf22cd609654fd445b4b0
7
- data.tar.gz: 31f85b457287ae7e1dee1c52e008099f9806cd520132c7bad048a42d4c67ee0550cb51610d95059b3135c7a39b2865058f2d2092623864b71c23010bebc03017
6
+ metadata.gz: 99cfd0e831a581d1f38764f1803e81fcdd4ae5b2fd7a75fa8b19a936af601abc415031213f8419d16f3108d8ba8a980439b148e55ae2b64ebc89e674d4c2b780
7
+ data.tar.gz: 9b98e1272e011f68fc60766918f16cb80ce867eb351d05b65ac8a760dbfe05fe363b74a276794b80a6971e5b4593d2c423d35ad835ad7bbdb00d6ca911490e94
@@ -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/.gitignore CHANGED
@@ -16,3 +16,4 @@ spec/reports
16
16
  test/tmp
17
17
  test/version_tmp
18
18
  tmp
19
+ .lefthook-local.yml
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,18 @@ appraise 'sidekiq-5.0' do
14
14
  gem 'sidekiq', '~> 5.0.0'
15
15
  end
16
16
 
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'
23
+ end
24
+
25
+ appraise 'sidekiq-7.0' do
26
+ gem 'sidekiq', '~> 7.0.0'
27
+ end
28
+
17
29
  appraise 'sidekiq-master' do
18
30
  gem 'sidekiq', github: 'mperham/sidekiq'
19
31
  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
data/bin/console ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "sidekiq/grouping"
6
+
7
+ require "pry"
8
+ Pry.start
@@ -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: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sidekiq", "~> 7.0.0"
6
+
7
+ gemspec path: "../"
data/lefthook.yml ADDED
@@ -0,0 +1,20 @@
1
+ ---
2
+ # Git hooks configuration
3
+ #
4
+ # See: github.com/evilmartians/lefthook
5
+
6
+ pre-commit:
7
+ parallel: true
8
+ commands:
9
+ appraisal:
10
+ glob: "{Appraisals,*.gemfile}"
11
+ run: echo {staged_files} > /dev/null; bundle exec appraisal install && git add gemfiles/*.gemfile
12
+ rubocop:
13
+ glob: "{*.rb,*.gemspec,Gemfile,Rakefile}"
14
+ run: bundle exec rubocop -A {staged_files} && git add {staged_files}
15
+
16
+ pre-push:
17
+ commands:
18
+ rspec:
19
+ glob: "*.rb"
20
+ run: echo {push_files} > /dev/null; 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,41 @@
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?(:[]) # Sidekiq 6.x
10
+ Sidekiq[:grouping] || {}
11
+ elsif Sidekiq.respond_to?(:options) # Sidekiq <= 5.x
12
+ Sidekiq.options[:grouping] || Sidekiq.options["grouping"] || {}
13
+ else # Sidekiq 7.x
14
+ Sidekiq.default_configuration[:grouping] || {}
15
+ end
16
+ end
12
17
 
13
- # Maximum batch size
14
- config_accessor :max_batch_size do
15
- options[:max_batch_size] || 1000
16
- end
18
+ # Queue size overflow check polling interval
19
+ config_accessor :poll_interval do
20
+ options[:poll_interval] || 3
21
+ end
17
22
 
18
- # Batch queue flush lock timeout
19
- config_accessor :lock_ttl do
20
- options[:lock_ttl] || 1
21
- end
23
+ # Maximum batch size
24
+ config_accessor :max_batch_size do
25
+ options[:max_batch_size] || 1000
26
+ end
27
+
28
+ # Batch queue flush lock timeout
29
+ config_accessor :lock_ttl do
30
+ options[:lock_ttl] || 1
31
+ end
22
32
 
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
- )
33
+ # Option to override how Sidekiq::Grouping know about tests env
34
+ config_accessor :tests_env do
35
+ options[:tests_env] || (
36
+ defined?(::Rails) && Rails.respond_to?(:env) && Rails.env.test?
37
+ )
38
+ end
39
+ end
28
40
  end
29
41
  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