deimos-ruby 1.16.5 → 1.17.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: 021fca59477fbfa890fa77ae867cd3a006b65e2c0235d1bb47ccdd1780e51d9c
4
- data.tar.gz: 8330ba3d8f90567c7abbfd91298dd04d80f9507f702a0d4ac45300f68e5d224a
3
+ metadata.gz: 8b4270b2fc345a60377ead079f053ea92281bd606df2d24da782c583bf25e3b9
4
+ data.tar.gz: 6c824200ea66395439dfbc00a0478a30f1410d10032915fb063f94e0d7efbeb0
5
5
  SHA512:
6
- metadata.gz: ab861eb37e8590664f47e0e11082ed5aac344a730c009c5261edcbdcbfcd00a39bc8e1f44bf2be5c05b1eb587ba15ad32e77edfd80ca0789da05049c91dd20e9
7
- data.tar.gz: 851b9b844d11be5d90d3b4e652a648e7ce1e8e90e1ddd96f25a34d690cb89e2503302a80ad9cb891cbaa30949a0394e68337b34de86cf848a6815730d1548ca0
6
+ metadata.gz: db33a95a08af5e4d5a6e42ea7d8d7193432647afe1c18186d8d4dbf58dca3fa7994cf06c67abda80c841a918746fe4059e72386c6ab2d98080bd1e9aa727e25e
7
+ data.tar.gz: 8d091dfb4478e66dc898949bc87304dc82a48fc29e1c89e2b1235ea199d338a59c62cc8b29667a03eb45e45db846187feaa28f8f490db3beb17c8e70ddfb7cf0
data/.ruby-version CHANGED
@@ -1,2 +1,2 @@
1
- 3.1.0
1
+ 2.7.0
2
2
 
data/CHANGELOG.md CHANGED
@@ -7,9 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## UNRELEASED
9
9
 
10
- # 1.16.5 - 2022-09-30
11
-
10
+ # 1.17.0 - 2022-10-19
12
11
  - Fix the linting step in the CI
12
+ - CHANGE: Add retries to DB poller and bypass "bad batches".
13
+ - Add tracing spans to DB poller production.
13
14
 
14
15
  # 1.16.4 - 2022-09-09
15
16
 
data/Gemfile CHANGED
@@ -9,4 +9,3 @@ if !ENV['CI'] || ENV['CI'] == ''
9
9
  # TODO: once all PRs are merged, add this to gemspec as a development dependency
10
10
  gem 'sord', git: 'git@github.com:dorner/sord.git', ref: 'local-develop'
11
11
  end
12
-
data/README.md CHANGED
@@ -800,6 +800,9 @@ class MyProducer < Deimos::ActiveRecordProducer
800
800
  end
801
801
  ```
802
802
 
803
+ Note that the poller will retry infinitely if it encounters a Kafka-related error such
804
+ as a communication failure. For all other errors, it will retry once by default.
805
+
803
806
  ## Running consumers
804
807
 
805
808
  Deimos includes a rake task. Once it's in your gemfile, just run
@@ -1207,7 +1210,7 @@ You can/should re-generate RBS types when methods or classes change by running t
1207
1210
  ### Linting
1208
1211
 
1209
1212
  Deimos uses Rubocop to lint the code. Please run Rubocop on your code
1210
- before submitting a PR. The GitHub CI will also run rubocop on your pull request.
1213
+ before submitting a PR. The GitHub CI will also run rubocop on your pull request.
1211
1214
 
1212
1215
  ---
1213
1216
  <p align="center">
data/Steepfile CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  target :app do
2
- check "lib"
3
- signature "sig"
4
+ check 'lib'
5
+ signature 'sig'
4
6
 
5
- library "set", "pathname"
7
+ library 'set', 'pathname'
6
8
  end
@@ -15,7 +15,7 @@ Config name|Default|Description
15
15
  logger|`Logger.new(STDOUT)`|The logger that Deimos will use.
16
16
  phobos_logger|`Deimos.config.logger`|The logger passed to Phobos.
17
17
  metrics|`Deimos::Metrics::Mock.new`|The metrics backend use for reporting.
18
- tracer|`Deimos::Tracer::Mock.new`|The tracer backend used for debugging.
18
+ tracer|`Deimos::Tracing::Mock.new`|The tracer backend used for debugging.
19
19
 
20
20
  ## Defining Producers
21
21
 
@@ -119,6 +119,7 @@ timestamp_column|`:updated_at`|Name of the column to query. Remember to add an i
119
119
  delay_time|2|Amount of time in seconds to wait before picking up records, to allow for transactions to finish.
120
120
  full_table|false|If set to true, do a full table dump to Kafka each run. Good for very small tables.
121
121
  start_from_beginning|true|If false, start from the current time instead of the beginning of time if this is the first time running the poller.
122
+ retries|1|The number of times to retry for a *non-Kafka* error.
122
123
 
123
124
  ## Kafka Configuration
124
125
 
@@ -461,6 +461,8 @@ module Deimos
461
461
  # If false, start from the current time instead of the beginning of time
462
462
  # if this is the first time running the poller.
463
463
  setting :start_from_beginning, true
464
+ # The number of times to retry production when encountering a *non-Kafka* error.
465
+ setting :retries, 1
464
466
  end
465
467
 
466
468
  deprecate 'kafka_logger', 'kafka.logger'
@@ -7,7 +7,7 @@ module Deimos
7
7
  # A mock Metrics wrapper which just logs the metrics
8
8
  class Mock < Provider
9
9
  # @param logger [Logger,nil]
10
- def initialize(logger=nil)
10
+ def initialize(logger=nil) # rubocop:disable Lint/MissingSuper
11
11
  @logger = logger || Logger.new(STDOUT)
12
12
  @logger.info('MockMetricsProvider initialized')
13
13
  end
@@ -24,7 +24,7 @@ module Deimos
24
24
  end
25
25
 
26
26
  # @override
27
- def coerce_field(field, value)
27
+ def coerce_field(_field, value)
28
28
  value
29
29
  end
30
30
 
@@ -14,6 +14,9 @@ module Deimos
14
14
  # @return [Integer]
15
15
  attr_reader :id
16
16
 
17
+ # @return [Hash]
18
+ attr_reader :config
19
+
17
20
  # Begin the DB Poller process.
18
21
  # @return [void]
19
22
  def self.start!
@@ -110,6 +113,7 @@ module Deimos
110
113
  Deimos.config.logger.info("Polling #{@producer.topic} from #{time_from} to #{time_to}")
111
114
  message_count = 0
112
115
  batch_count = 0
116
+ error_count = 0
113
117
 
114
118
  # poll_query gets all the relevant data from the database, as defined
115
119
  # by the producer itself.
@@ -118,12 +122,15 @@ module Deimos
118
122
  batch = fetch_results(time_from, time_to).to_a
119
123
  break if batch.empty?
120
124
 
121
- batch_count += 1
122
- process_batch(batch)
125
+ if process_batch_with_span(batch)
126
+ batch_count += 1
127
+ else
128
+ error_count += 1
129
+ end
123
130
  message_count += batch.size
124
131
  time_from = last_updated(batch.last)
125
132
  end
126
- Deimos.config.logger.info("Poll #{@producer.topic} complete at #{time_to} (#{message_count} messages, #{batch_count} batches}")
133
+ Deimos.config.logger.info("Poll #{@producer.topic} complete at #{time_to} (#{message_count} messages, #{batch_count} successful batches, #{error_count} batches errored}")
127
134
  end
128
135
 
129
136
  # @param time_from [ActiveSupport::TimeWithZone]
@@ -143,15 +150,52 @@ module Deimos
143
150
 
144
151
  # @param batch [Array<ActiveRecord::Base>]
145
152
  # @return [void]
146
- def process_batch(batch)
153
+ def process_batch_with_span(batch)
154
+ retries = 0
155
+ begin
156
+ span = Deimos.config.tracer&.start(
157
+ 'deimos-db-poller',
158
+ resource: @producer.class.name.gsub('::', '-')
159
+ )
160
+ process_batch(batch)
161
+ Deimos.config.tracer&.finish(span)
162
+ rescue Kafka::Error => e # keep trying till it fixes itself
163
+ Deimos.config.logger.error("Error publishing through DB Poller: #{e.message}")
164
+ sleep(0.5)
165
+ retry
166
+ rescue StandardError => e
167
+ Deimos.config.logger.error("Error publishing through DB poller: #{e.message}}")
168
+ if retries < @config.retries
169
+ retries += 1
170
+ sleep(0.5)
171
+ retry
172
+ else
173
+ Deimos.config.logger.error('Retries exceeded, moving on to next batch')
174
+ Deimos.config.tracer&.set_error(span, e)
175
+ self.touch_info(batch)
176
+ return false
177
+ end
178
+ end
179
+ true
180
+ end
181
+
182
+ # @param batch [Array<ActiveRecord::Base>]
183
+ # @return [void]
184
+ def touch_info(batch)
147
185
  record = batch.last
148
186
  id_method = record.class.primary_key
149
187
  last_id = record.public_send(id_method)
150
188
  last_updated_at = last_updated(record)
151
- @producer.send_events(batch)
152
189
  @info.attributes = { last_sent: last_updated_at, last_sent_id: last_id }
153
190
  @info.save!
154
191
  end
192
+
193
+ # @param batch [Array<ActiveRecord::Base>]
194
+ # @return [void]
195
+ def process_batch(batch)
196
+ @producer.send_events(batch)
197
+ self.touch_info(batch)
198
+ end
155
199
  end
156
200
  end
157
201
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Deimos
4
- VERSION = '1.16.5'
4
+ VERSION = '1.17.0'
5
5
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Layout/LineLength
4
+
3
5
  # @param seconds [Integer]
4
6
  # @return [Time]
5
7
  def time_value(secs: 0, mins: 0)
@@ -156,16 +158,56 @@ each_db_config(Deimos::Utils::DbPoller) do
156
158
  expect(poller.should_run?).to eq(false)
157
159
  end
158
160
 
159
- specify '#process_batch' do
160
- travel_to time_value
161
- widgets = (1..3).map { Widget.create!(test_id: 'some_id', some_int: 4) }
162
- widgets.last.update_attribute(:updated_at, time_value(mins: -30))
163
- expect(MyProducer).to receive(:send_events).with(widgets)
164
- poller.retrieve_poll_info
165
- poller.process_batch(widgets)
166
- info = Deimos::PollInfo.last
167
- expect(info.last_sent.in_time_zone).to eq(time_value(mins: -30))
168
- expect(info.last_sent_id).to eq(widgets.last.id)
161
+ describe '#process_batch' do
162
+ let(:widgets) { (1..3).map { Widget.create!(test_id: 'some_id', some_int: 4) } }
163
+
164
+ before(:each) do
165
+ allow(Deimos.config.tracer).to receive(:start).and_return('a span')
166
+ allow(Deimos.config.tracer).to receive(:set_error)
167
+ allow(Deimos.config.tracer).to receive(:finish)
168
+ end
169
+
170
+ it 'should process the batch' do
171
+ travel_to time_value
172
+ widgets.last.update_attribute(:updated_at, time_value(mins: -30))
173
+ expect(MyProducer).to receive(:send_events).with(widgets)
174
+ poller.retrieve_poll_info
175
+ poller.process_batch(widgets)
176
+ info = Deimos::PollInfo.last
177
+ expect(info.last_sent.in_time_zone).to eq(time_value(mins: -30))
178
+ expect(info.last_sent_id).to eq(widgets.last.id)
179
+ end
180
+
181
+ it 'should create a span' do
182
+ poller.retrieve_poll_info
183
+ poller.process_batch_with_span(widgets)
184
+ expect(Deimos.config.tracer).to have_received(:finish).with('a span')
185
+ end
186
+
187
+ it 'should retry on Kafka error' do
188
+ called_once = false
189
+ allow(poller).to receive(:sleep)
190
+ allow(poller).to receive(:process_batch) do
191
+ unless called_once
192
+ called_once = true
193
+ raise Kafka::Error, 'OH NOES'
194
+ end
195
+ end
196
+ poller.retrieve_poll_info
197
+ poller.process_batch_with_span(widgets)
198
+ expect(poller).to have_received(:sleep).once.with(0.5)
199
+ expect(Deimos.config.tracer).to have_received(:finish).with('a span')
200
+ end
201
+
202
+ it 'should retry only once on other errors' do
203
+ error = RuntimeError.new('OH NOES')
204
+ allow(poller).to receive(:sleep)
205
+ allow(poller).to receive(:process_batch).and_raise(error)
206
+ poller.retrieve_poll_info
207
+ poller.process_batch_with_span(widgets)
208
+ expect(poller).to have_received(:sleep).once.with(0.5)
209
+ expect(Deimos.config.tracer).to have_received(:set_error).with('a span', error)
210
+ end
169
211
  end
170
212
 
171
213
  describe '#process_updates' do
@@ -234,6 +276,7 @@ each_db_config(Deimos::Utils::DbPoller) do
234
276
  end
235
277
 
236
278
  it 'should send events across multiple batches' do
279
+ allow(Deimos.config.logger).to receive(:info)
237
280
  allow(MyProducer).to receive(:poll_query).and_call_original
238
281
  expect(poller).to receive(:process_batch).ordered.
239
282
  with([widgets[0], widgets[1], widgets[2]]).and_call_original
@@ -273,48 +316,48 @@ each_db_config(Deimos::Utils::DbPoller) do
273
316
  time_to: time_value(secs: 120), # plus 122 seconds minus 2 seconds
274
317
  column_name: :updated_at,
275
318
  min_id: last_widget.id)
319
+ expect(Deimos.config.logger).to have_received(:info).
320
+ with('Poll my-topic-with-id complete at 2015-05-05 00:59:58 -0400 (7 messages, 3 successful batches, 0 batches errored}')
276
321
  end
277
322
 
278
- it 'should recover correctly with errors and save the right ID' do
279
- widgets.each do |w|
280
- w.update_attribute(:updated_at, time_value(mins: -61, secs: 30))
323
+ describe 'errors' do
324
+ before(:each) do
325
+ poller.config.retries = 0
326
+ allow(Deimos.config.logger).to receive(:info)
281
327
  end
282
- allow(MyProducer).to receive(:poll_query).and_call_original
283
- expect(poller).to receive(:process_batch).ordered.
284
- with([widgets[0], widgets[1], widgets[2]]).and_call_original
285
- expect(poller).to receive(:process_batch).ordered.
286
- with([widgets[3], widgets[4], widgets[5]]).and_raise('OH NOES')
287
-
288
- expect { poller.process_updates }.to raise_exception('OH NOES')
289
-
290
- expect(MyProducer).to have_received(:poll_query).
291
- with(time_from: time_value(mins: -61),
292
- time_to: time_value(secs: -2),
293
- column_name: :updated_at,
294
- min_id: 0)
295
-
296
- info = Deimos::PollInfo.last
297
- expect(info.last_sent.in_time_zone).to eq(time_value(mins: -61, secs: 30))
298
- expect(info.last_sent_id).to eq(widgets[2].id)
299
328
 
300
- travel 61.seconds
301
- # process the last widget which came in during the delay
302
- expect(poller).to receive(:process_batch).ordered.
303
- with([widgets[3], widgets[4], widgets[5]]).and_call_original
304
- expect(poller).to receive(:process_batch).with([widgets[6], last_widget]).
305
- and_call_original
306
- poller.process_updates
307
- expect(MyProducer).to have_received(:poll_query).
308
- with(time_from: time_value(mins: -61, secs: 30),
309
- time_to: time_value(secs: 59),
310
- column_name: :updated_at,
311
- min_id: widgets[2].id)
329
+ after(:each) do
330
+ poller.config.retries = 1
331
+ end
312
332
 
313
- expect(info.reload.last_sent.in_time_zone).to eq(time_value(secs: -1))
314
- expect(info.last_sent_id).to eq(last_widget.id)
333
+ it 'should recover correctly with errors and save the right ID' do
334
+ widgets.each do |w|
335
+ w.update_attribute(:updated_at, time_value(mins: -61, secs: 30))
336
+ end
337
+ allow(MyProducer).to receive(:poll_query).and_call_original
338
+ expect(poller).to receive(:process_batch).ordered.
339
+ with([widgets[0], widgets[1], widgets[2]]).and_call_original
340
+ expect(poller).to receive(:process_batch).ordered.
341
+ with([widgets[3], widgets[4], widgets[5]]).and_raise('OH NOES')
342
+ expect(poller).to receive(:process_batch).ordered.
343
+ with([widgets[6]]).and_call_original
344
+
345
+ poller.process_updates
346
+
347
+ expect(MyProducer).to have_received(:poll_query).
348
+ with(time_from: time_value(mins: -61),
349
+ time_to: time_value(secs: -2),
350
+ column_name: :updated_at,
351
+ min_id: 0)
352
+
353
+ info = Deimos::PollInfo.last
354
+ expect(info.last_sent.in_time_zone).to eq(time_value(mins: -61, secs: 30))
355
+ expect(info.last_sent_id).to eq(widgets[6].id)
356
+ expect(Deimos.config.logger).to have_received(:info).
357
+ with('Poll my-topic-with-id complete at 2015-05-05 00:59:58 -0400 (7 messages, 2 successful batches, 1 batches errored}')
358
+ end
315
359
  end
316
-
317
360
  end
318
-
319
361
  end
320
362
  end
363
+ # rubocop:enable Layout/LineLength
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deimos-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.16.5
4
+ version: 1.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Orner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-30 00:00:00.000000000 Z
11
+ date: 2022-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: avro_turf
@@ -607,7 +607,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
607
607
  - !ruby/object:Gem::Version
608
608
  version: '0'
609
609
  requirements: []
610
- rubygems_version: 3.3.21
610
+ rubygems_version: 3.3.20
611
611
  signing_key:
612
612
  specification_version: 4
613
613
  summary: Kafka libraries for Ruby.