deimos-ruby 1.16.5 → 1.17.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: 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.