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 +4 -4
- data/.ruby-version +1 -1
- data/CHANGELOG.md +3 -2
- data/Gemfile +0 -1
- data/README.md +4 -1
- data/Steepfile +5 -3
- data/docs/CONFIGURATION.md +2 -1
- data/lib/deimos/config/configuration.rb +2 -0
- data/lib/deimos/metrics/mock.rb +1 -1
- data/lib/deimos/schema_backends/mock.rb +1 -1
- data/lib/deimos/utils/db_poller.rb +49 -5
- data/lib/deimos/version.rb +1 -1
- data/spec/utils/db_poller_spec.rb +89 -46
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b4270b2fc345a60377ead079f053ea92281bd606df2d24da782c583bf25e3b9
|
4
|
+
data.tar.gz: 6c824200ea66395439dfbc00a0478a30f1410d10032915fb063f94e0d7efbeb0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db33a95a08af5e4d5a6e42ea7d8d7193432647afe1c18186d8d4dbf58dca3fa7994cf06c67abda80c841a918746fe4059e72386c6ab2d98080bd1e9aa727e25e
|
7
|
+
data.tar.gz: 8d091dfb4478e66dc898949bc87304dc82a48fc29e1c89e2b1235ea199d338a59c62cc8b29667a03eb45e45db846187feaa28f8f490db3beb17c8e70ddfb7cf0
|
data/.ruby-version
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
|
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.
|
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
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
data/docs/CONFIGURATION.md
CHANGED
@@ -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::
|
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'
|
data/lib/deimos/metrics/mock.rb
CHANGED
@@ -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
|
@@ -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
|
-
|
122
|
-
|
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
|
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
|
data/lib/deimos/version.rb
CHANGED
@@ -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
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
279
|
-
|
280
|
-
|
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
|
-
|
301
|
-
|
302
|
-
|
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
|
-
|
314
|
-
|
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.
|
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-
|
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.
|
610
|
+
rubygems_version: 3.3.20
|
611
611
|
signing_key:
|
612
612
|
specification_version: 4
|
613
613
|
summary: Kafka libraries for Ruby.
|