job-iteration 1.7.0 → 1.8.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: 0c3d42266a1de05ed85c35d99e76b55a131144054db3e3b83026aa3e180488c6
4
- data.tar.gz: c272d3a7e0316cbb0a9b34326f8b1cdff5cb43c2ff58828dea1767a7f47eecb9
3
+ metadata.gz: a38399b1ad6f7c81b2dd731e39abc75b4febcbac50ea9c310c6c96ada5f802ff
4
+ data.tar.gz: 96182f4dcdd4594c9fde855b2394b96a516e7134671a18905ebd3ae6b13ddcfa
5
5
  SHA512:
6
- metadata.gz: 500cefbc1ab8e7f02a4679b13f8c1401662e63021d20b2b4b5209d979f70c208d5b2a91ed5765d5dc5739666772fbe86e6f1a3e658bd9b1dad03f4b42d97b270
7
- data.tar.gz: 37e100ba000a3ab67a07e78937ac12c58822a13e8f5641e6a3312a49a51733da3a1d4d3c8bf1a1f1928fc45c3aa026caf003fb97565a412e4c371b389513c7bb
6
+ metadata.gz: 515d1e0106c1b7c20ce3058df4df1c0c2a29dfb0ebe297bc3ac81da5365b7c8d68cf1058dc5de9c1c37331570260065e59aa86b1801f3b821a70d504aa3b25ac
7
+ data.tar.gz: 27303a6ea6910734d7d23f6ef0570cf7b2e6f011b1776731829e8366d93e2ad1032c1fce4bf44dee42e1c253f209f97cda8c761824ab7b81f8bfa51a339b0218
data/CHANGELOG.md CHANGED
@@ -1,6 +1,31 @@
1
1
  ### Main (unreleased)
2
2
 
3
- Nil
3
+ ### Changes
4
+
5
+ nil
6
+
7
+ ### Features
8
+
9
+ nil
10
+
11
+ ### Bug fixes
12
+
13
+ nil
14
+
15
+ ## v1.8.0 (Dec 10, 2024)
16
+
17
+ ### Changes
18
+
19
+ - [513](https://github.com/Shopify/job-iteration/pull/513) Deprecate returning enumerators from `build_enumerator` that are not wrapped with `enumerator_builder.wrap`. The built-in enumerator builders now always wrap.
20
+
21
+ ### Features
22
+
23
+ - [340](https://github.com/Shopify/job-iteration/pull/340) Add `cursor.iteration` instrumentation for the query to fetch the next batch of records for the Active Record cursor.
24
+ - [523](https://github.com/Shopify/job-iteration/pull/523) Add interruption adapter for [aws-activejob-sqs](https://github.com/aws/aws-activejob-sqs-ruby).
25
+
26
+ ### Bug fixes
27
+
28
+ - [515](https://github.com/Shopify/job-iteration/pull/515) Fix size of array enumerators.
4
29
 
5
30
  ## v1.7.0 (Oct 11, 2024)
6
31
 
data/Gemfile.lock CHANGED
@@ -1,6 +1,6 @@
1
1
  GIT
2
2
  remote: https://github.com/brianmario/mysql2
3
- revision: f6a9b68b42a51d1a370403f11eb88527dcb42dc6
3
+ revision: 58f8d009a0d107776dd6d24c9906426fe0f7b856
4
4
  specs:
5
5
  mysql2 (0.5.6)
6
6
  bigdecimal
@@ -8,7 +8,7 @@ GIT
8
8
  PATH
9
9
  remote: .
10
10
  specs:
11
- job-iteration (1.7.0)
11
+ job-iteration (1.8.0)
12
12
  activejob (>= 5.2)
13
13
 
14
14
  GEM
@@ -49,7 +49,7 @@ GEM
49
49
  language_server-protocol (3.17.0.3)
50
50
  method_source (1.1.0)
51
51
  minitest (5.24.0)
52
- mocha (2.4.5)
52
+ mocha (2.7.0)
53
53
  ruby2_keywords (>= 0.0.5)
54
54
  mono_logger (1.1.2)
55
55
  multi_json (1.15.0)
@@ -60,7 +60,7 @@ GEM
60
60
  parser (3.3.3.0)
61
61
  ast (~> 2.4.1)
62
62
  racc
63
- pry (0.14.2)
63
+ pry (0.15.0)
64
64
  coderay (~> 1.1)
65
65
  method_source (~> 1.0)
66
66
  racc (1.8.0)
@@ -84,8 +84,7 @@ GEM
84
84
  multi_json (~> 1.0)
85
85
  redis-namespace (~> 1.6)
86
86
  sinatra (>= 0.9.2)
87
- rexml (3.3.6)
88
- strscan
87
+ rexml (3.3.9)
89
88
  rubocop (1.64.1)
90
89
  json (~> 2.3)
91
90
  language_server-protocol (>= 3.17.0)
@@ -115,7 +114,6 @@ GEM
115
114
  rack-session (>= 2.0.0, < 3)
116
115
  tilt (~> 2.0)
117
116
  sorbet-runtime (0.5.11460)
118
- strscan (3.1.0)
119
117
  tilt (2.4.0)
120
118
  timeout (0.4.1)
121
119
  tzinfo (2.0.6)
data/README.md CHANGED
@@ -161,7 +161,7 @@ Iteration hooks into Sidekiq and Resque out of the box to support graceful inter
161
161
  * [Writing custom enumerator](guides/custom-enumerator.md)
162
162
  * [Throttling](guides/throttling.md)
163
163
 
164
- For more detailed documentation, see [rubydoc](https://www.rubydoc.info/github/Shopify/job-iteration).
164
+ For more detailed documentation, see [rubydoc](https://www.rubydoc.info/gems/job-iteration).
165
165
 
166
166
  ## Requirements
167
167
 
@@ -78,12 +78,14 @@ class LoadRefundsForChargeJob < ActiveJob::Base
78
78
  # Use an exponential back-off strategy when Stripe's API returns errors.
79
79
 
80
80
  def build_enumerator(charge_id, cursor:)
81
- StripeListEnumerator.new(
82
- Stripe::Refund,
83
- params: { charge: charge_id}, # "charge_id" will be a prefixed Stripe ID such as "chrg_123"
84
- options: { api_key: "sk_test_123", stripe_version: "2018-01-18" },
85
- cursor: cursor
86
- ).to_enumerator
81
+ enumerator_builder.wrap(
82
+ StripeListEnumerator.new(
83
+ Stripe::Refund,
84
+ params: { charge: charge_id}, # "charge_id" will be a prefixed Stripe ID such as "chrg_123"
85
+ options: { api_key: "sk_test_123", stripe_version: "2018-01-18" },
86
+ cursor: cursor
87
+ ).to_enumerator
88
+ )
87
89
  end
88
90
 
89
91
  # Note that in this case `each_iteration` will only receive one positional argument per iteration.
@@ -114,9 +116,11 @@ class RedisPopListJob < ActiveJob::Base
114
116
  # @see https://redis.io/commands/lpop/
115
117
  def build_enumerator(*)
116
118
  @redis = Redis.new
117
- Enumerator.new do |yielder|
118
- yielder.yield @redis.lpop(key), nil
119
- end
119
+ enumerator_builder.wrap(
120
+ Enumerator.new do |yielder|
121
+ yielder.yield @redis.lpop(key), nil
122
+ end
123
+ )
120
124
  end
121
125
 
122
126
  def each_iteration(item_from_redis)
data/guides/throttling.md CHANGED
@@ -38,9 +38,31 @@ def build_enumerator(_params, cursor:)
38
38
  end
39
39
  ```
40
40
 
41
+ If you want to apply throttling on all jobs, you can subclass your own EnumeratorBuilder and override the default
42
+ enumerator builder. The builder always wraps the returned enumerators from `build_enumerator`
43
+
44
+ ```ruby
45
+ class MyOwnBuilder < JobIteration::EnumeratorBuilder
46
+ class Wrapper < Enumerator
47
+ class << self
48
+ def wrap(_builder, enum)
49
+ ThrottleEnumerator.new(
50
+ enum,
51
+ nil,
52
+ throttle_on: -> { DatabaseStatus.unhealthy? },
53
+ backoff: 30.seconds
54
+ )
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ JobIteration.enumerator_builder = MyOwnBuilder
61
+ ```
62
+
41
63
  Note that it's up to you to implement `DatabaseStatus.unhealthy?` that works for your database choice. At Shopify, a helper like `DatabaseStatus` checks the following MySQL metrics:
42
64
 
43
65
  * Replication lag across all regions
44
66
  * DB threads
45
67
  * DB is available for writes (otherwise indicates a failover happening)
46
- * [Semian](https://github.com/shopify/semian) open circuits
68
+ * [Semian](https://github.com/shopify/semian) open circuits
@@ -32,7 +32,7 @@ module JobIteration
32
32
  def batches
33
33
  cursor = finder_cursor
34
34
  Enumerator.new(method(:size)) do |yielder|
35
- while (records = cursor.next_batch(@batch_size))
35
+ while (records = instrument_next_batch(cursor))
36
36
  yielder.yield(records, cursor_value(records.last)) if records.any?
37
37
  end
38
38
  end
@@ -44,6 +44,12 @@ module JobIteration
44
44
 
45
45
  private
46
46
 
47
+ def instrument_next_batch(cursor)
48
+ ActiveSupport::Notifications.instrument("active_record_cursor.iteration") do
49
+ cursor.next_batch(@batch_size)
50
+ end
51
+ end
52
+
47
53
  def cursor_value(record)
48
54
  positions = @columns.map do |column|
49
55
  attribute_name = column.to_s.split(".").last
@@ -16,8 +16,7 @@ module JobIteration
16
16
  # `enumerator_builder` is _always_ the type that is returned from
17
17
  # `build_enumerator`. This prevents people from implementing custom
18
18
  # Enumerators without wrapping them in
19
- # `enumerator_builder.wrap(custom_enum)`. We don't do this yet for backwards
20
- # compatibility with raw calls to EnumeratorBuilder. Think of these wrappers
19
+ # `enumerator_builder.wrap(custom_enum)`. Think of these wrappers
21
20
  # the way you should a middleware.
22
21
  class Wrapper < Enumerator
23
22
  class << self
@@ -63,7 +62,7 @@ module JobIteration
63
62
  cursor + 1
64
63
  end
65
64
 
66
- wrap(self, enumerable.each_with_index.drop(drop).to_enum { enumerable.size })
65
+ wrap(self, enumerable.each_with_index.drop(drop).to_enum { enumerable.size - drop })
67
66
  end
68
67
 
69
68
  # Builds Enumerator from Active Record Relation. Each Enumerator tick moves the cursor one row forward.
@@ -131,21 +130,24 @@ module JobIteration
131
130
  enum
132
131
  end
133
132
 
134
- def build_throttle_enumerator(enum, throttle_on:, backoff:)
135
- JobIteration::ThrottleEnumerator.new(
136
- enum,
133
+ def build_throttle_enumerator(enumerable, throttle_on:, backoff:)
134
+ enum = JobIteration::ThrottleEnumerator.new(
135
+ enumerable,
137
136
  @job,
138
137
  throttle_on: throttle_on,
139
138
  backoff: backoff,
140
139
  ).to_enum
140
+ wrap(self, enum)
141
141
  end
142
142
 
143
143
  def build_csv_enumerator(enumerable, cursor:)
144
- CsvEnumerator.new(enumerable).rows(cursor: cursor)
144
+ enum = CsvEnumerator.new(enumerable).rows(cursor: cursor)
145
+ wrap(self, enum)
145
146
  end
146
147
 
147
148
  def build_csv_enumerator_on_batches(enumerable, cursor:, batch_size: 100)
148
- CsvEnumerator.new(enumerable).batches(cursor: cursor, batch_size: batch_size)
149
+ enum = CsvEnumerator.new(enumerable).batches(cursor: cursor, batch_size: batch_size)
150
+ wrap(self, enum)
149
151
  end
150
152
 
151
153
  # Builds Enumerator for nested iteration.
@@ -179,7 +181,8 @@ module JobIteration
179
181
  # end
180
182
  #
181
183
  def build_nested_enumerator(enums, cursor:)
182
- NestedEnumerator.new(enums, cursor: cursor).each
184
+ enum = NestedEnumerator.new(enums, cursor: cursor).each
185
+ wrap(self, enum)
183
186
  end
184
187
 
185
188
  alias_method :once, :build_once_enumerator
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "aws-activejob-sqs"
5
+ rescue LoadError
6
+ # Aws::ActiveJob::SQS is not available, no need to load the adapter
7
+ return
8
+ end
9
+
10
+ begin
11
+ # Aws::ActiveJob::SQS.on_worker_stop was introduced in Aws::ActiveJob::SQS 0.1.1
12
+ gem("aws-activejob-sqs", ">= 0.1.1")
13
+ rescue Gem::LoadError
14
+ warn("job-iteration's interruption adapter for SQS requires aws-activejob-sqs 0.1.1 or newer")
15
+ return
16
+ end
17
+
18
+ module JobIteration
19
+ module InterruptionAdapters
20
+ module SqsAdapter
21
+ class << self
22
+ attr_accessor :stopping
23
+
24
+ def call
25
+ stopping
26
+ end
27
+ end
28
+
29
+ Aws::ActiveJob::SQS.on_worker_stop do
30
+ SqsAdapter.stopping = true
31
+ end
32
+ end
33
+
34
+ register(:sqs, SqsAdapter)
35
+ end
36
+ end
@@ -4,7 +4,7 @@ require_relative "interruption_adapters/null_adapter"
4
4
 
5
5
  module JobIteration
6
6
  module InterruptionAdapters
7
- BUNDLED_ADAPTERS = [:good_job, :resque, :sidekiq, :solid_queue].freeze # @api private
7
+ BUNDLED_ADAPTERS = [:good_job, :resque, :sidekiq, :solid_queue, :sqs].freeze # @api private
8
8
 
9
9
  class << self
10
10
  # Returns adapter for specified name.
@@ -219,7 +219,14 @@ module JobIteration
219
219
  end
220
220
 
221
221
  def assert_enumerator!(enum)
222
- return if enum.is_a?(Enumerator)
222
+ if enum.is_a?(Enumerator)
223
+ unless enum.is_a?(JobIteration.enumerator_builder::Wrapper)
224
+ JobIteration::Deprecation.warn("Returning an unwrapped enumerator from build_enumerator is deprecated. " \
225
+ "Wrap the enumerator using enumerator_builder.wrap(my_enumerator) instead.")
226
+ end
227
+
228
+ return
229
+ end
223
230
 
224
231
  raise ArgumentError, <<~EOS
225
232
  #build_enumerator is expected to return Enumerator object, but returned #{enum.class}.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JobIteration
4
- VERSION = "1.7.0"
4
+ VERSION = "1.8.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: job-iteration
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-11 00:00:00.000000000 Z
11
+ date: 2024-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -67,6 +67,7 @@ files:
67
67
  - lib/job-iteration/interruption_adapters/resque_adapter.rb
68
68
  - lib/job-iteration/interruption_adapters/sidekiq_adapter.rb
69
69
  - lib/job-iteration/interruption_adapters/solid_queue_adapter.rb
70
+ - lib/job-iteration/interruption_adapters/sqs_adapter.rb
70
71
  - lib/job-iteration/iteration.rb
71
72
  - lib/job-iteration/log_subscriber.rb
72
73
  - lib/job-iteration/nested_enumerator.rb
@@ -95,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
96
  - !ruby/object:Gem::Version
96
97
  version: '0'
97
98
  requirements: []
98
- rubygems_version: 3.5.21
99
+ rubygems_version: 3.5.23
99
100
  signing_key:
100
101
  specification_version: 4
101
102
  summary: Makes your background jobs interruptible and resumable.