karafka 2.0.40 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci.yml +2 -2
- data/CHANGELOG.md +30 -1
- data/Gemfile +3 -2
- data/Gemfile.lock +13 -1
- data/bin/integrations +17 -2
- data/config/locales/errors.yml +10 -0
- data/config/locales/pro_errors.yml +0 -2
- data/lib/karafka/active_job/consumer.rb +16 -11
- data/lib/karafka/active_job/current_attributes/loading.rb +36 -0
- data/lib/karafka/active_job/current_attributes/persistence.rb +28 -0
- data/lib/karafka/active_job/current_attributes.rb +42 -0
- data/lib/karafka/active_job/dispatcher.rb +8 -2
- data/lib/karafka/admin.rb +17 -13
- data/lib/karafka/connection/client.rb +6 -3
- data/lib/karafka/errors.rb +3 -0
- data/lib/karafka/instrumentation/callbacks/statistics.rb +12 -0
- data/lib/karafka/instrumentation/logger_listener.rb +16 -5
- data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +166 -0
- data/lib/karafka/pro/active_job/consumer.rb +1 -10
- data/lib/karafka/pro/active_job/dispatcher.rb +2 -2
- data/lib/karafka/pro/iterator.rb +253 -0
- data/lib/karafka/pro/processing/coordinator.rb +20 -1
- data/lib/karafka/pro/processing/filters/virtual_limiter.rb +52 -0
- data/lib/karafka/pro/processing/filters_applier.rb +4 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +3 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +2 -2
- data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +2 -0
- data/lib/karafka/pro/processing/strategies/aj/mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/dlq/ftr.rb +1 -1
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +3 -6
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom_vp.rb +43 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_vp.rb +1 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +3 -7
- data/lib/karafka/pro/processing/strategies/dlq/ftr_mom_vp.rb +41 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_vp.rb +1 -0
- data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +3 -6
- data/lib/karafka/pro/processing/strategies/dlq/lrj_mom_vp.rb +36 -0
- data/lib/karafka/pro/processing/strategies/dlq/lrj_vp.rb +1 -0
- data/lib/karafka/pro/processing/strategies/dlq/mom.rb +8 -7
- data/lib/karafka/pro/processing/strategies/dlq/mom_vp.rb +37 -0
- data/lib/karafka/pro/processing/strategies/lrj/default.rb +2 -0
- data/lib/karafka/pro/processing/strategies/lrj/ftr_mom_vp.rb +40 -0
- data/lib/karafka/pro/processing/strategies/lrj/mom.rb +2 -0
- data/lib/karafka/pro/processing/strategies/lrj/mom_vp.rb +38 -0
- data/lib/karafka/pro/processing/strategies/mom/ftr_vp.rb +37 -0
- data/lib/karafka/pro/{base_consumer.rb → processing/strategies/mom/vp.rb} +17 -7
- data/lib/karafka/pro/processing/strategies/vp/default.rb +51 -0
- data/lib/karafka/pro/processing/virtual_offset_manager.rb +147 -0
- data/lib/karafka/pro/routing/features/virtual_partitions/contract.rb +0 -17
- data/lib/karafka/processing/strategies/default.rb +2 -0
- data/lib/karafka/processing/strategies/dlq_mom.rb +9 -7
- data/lib/karafka/routing/router.rb +15 -0
- data/lib/karafka/setup/config.rb +7 -1
- data/lib/karafka/version.rb +1 -1
- data/lib/karafka.rb +5 -0
- data.tar.gz.sig +0 -0
- metadata +17 -4
- metadata.gz.sig +0 -0
- data/lib/karafka/instrumentation/vendors/datadog/listener.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3d0a2f78b4bf7fa8f49527d48d2e877b95597566e07beabf0166a02259a936b
|
4
|
+
data.tar.gz: fc6054ad5f99bfe8a678c337167f93fc612dddfe88494f8891158dbd8610fb7f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0fb1fa88ef76ce81e145797a1364ac36bea2b94c47e733856cfd5ec9b37d0d9e2e984a3e4ef7fc36d2ac34c448e490cfdea4e10fae886cd80fb289798e55d308
|
7
|
+
data.tar.gz: 68df2bc1edb9acccd45d32428b43fd5dee12b9333c6059c801aa4ac03b3b89c01e93e8ea4ebbb8021618447c1122c8c5df64afc86747d6f0deec0cf992237e82
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/.github/workflows/ci.yml
CHANGED
@@ -62,7 +62,7 @@ jobs:
|
|
62
62
|
run: \curl -sSL https://api.coditsu.io/run/ci | bash
|
63
63
|
|
64
64
|
specs:
|
65
|
-
timeout-minutes:
|
65
|
+
timeout-minutes: 30
|
66
66
|
runs-on: ubuntu-latest
|
67
67
|
needs: diffend
|
68
68
|
strategy:
|
@@ -102,7 +102,7 @@ jobs:
|
|
102
102
|
run: bin/rspecs
|
103
103
|
|
104
104
|
integrations:
|
105
|
-
timeout-minutes:
|
105
|
+
timeout-minutes: 45
|
106
106
|
runs-on: ubuntu-latest
|
107
107
|
needs: diffend
|
108
108
|
strategy:
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,34 @@
|
|
1
1
|
# Karafka framework changelog
|
2
2
|
|
3
|
+
## 2.1.0 (2023-05-22)
|
4
|
+
- **[Feature]** Provide ability to use CurrentAttributes with ActiveJob's Karafka adapter.
|
5
|
+
- **[Feature]** Introduce collective Virtual Partitions offset management.
|
6
|
+
- **[Feature]** Use virtual offsets to filter out messages that would be re-processed upon retries.
|
7
|
+
- [Improvement] No longer break processing on failing parallel virtual partitions in ActiveJob because it is compensated by virtual marking.
|
8
|
+
- [Improvement] Always use Virtual offset management for Pro ActiveJobs.
|
9
|
+
- [Improvement] Do not attempt to mark offsets on already revoked partitions.
|
10
|
+
- [Improvement] Make sure, that VP components are not injected into non VP strategies.
|
11
|
+
- [Improvement] Improve complex strategies inheritance flow.
|
12
|
+
- [Improvement] Optimize offset management for DLQ + MoM feature combinations.
|
13
|
+
- [Change] Removed `Karafka::Pro::BaseConsumer` in favor of `Karafka::BaseConsumer`. (#1345)
|
14
|
+
- [Fix] Fix for `max_messages` and `max_wait_time` not having reference in errors.yml (#1443)
|
15
|
+
|
16
|
+
### Upgrade notes
|
17
|
+
|
18
|
+
1. Upgrade to Karafka `2.0.41` prior to upgrading to `2.1.0`.
|
19
|
+
2. Replace `Karafka::Pro::BaseConsumer` references to `Karafka::BaseConsumer`.
|
20
|
+
3. Replace `Karafka::Instrumentation::Vendors::Datadog:Listener` with `Karafka::Instrumentation::Vendors::Datadog::MetricsListener`.
|
21
|
+
|
22
|
+
## 2.0.41 (2023-14-19)
|
23
|
+
- **[Feature]** Provide `Karafka::Pro::Iterator` for anonymous topic/partitions iterations and messages lookups (#1389 and #1427).
|
24
|
+
- [Improvement] Optimize topic lookup for `read_topic` admin method usage.
|
25
|
+
- [Improvement] Report via `LoggerListener` information about the partition on which a given job has started and finished.
|
26
|
+
- [Improvement] Slightly normalize the `LoggerListener` format. Always report partition related operations as followed: `TOPIC_NAME/PARTITION`.
|
27
|
+
- [Improvement] Do not retry recovery from `unknown_topic_or_part` when Karafka is shutting down as there is no point and no risk of any data losses.
|
28
|
+
- [Improvement] Report `client.software.name` and `client.software.version` according to `librdkafka` recommendation.
|
29
|
+
- [Improvement] Report ten longest integration specs after the suite execution.
|
30
|
+
- [Improvement] Prevent user originating errors related to statistics processing after listener loop crash from potentially crashing the listener loop and hanging Karafka process.
|
31
|
+
|
3
32
|
## 2.0.40 (2023-04-13)
|
4
33
|
- [Improvement] Introduce `Karafka::Messages::Messages#empty?` method to handle Idle related cases where shutdown or revocation would be called on an empty messages set. This method allows for checking if there are any messages in the messages batch.
|
5
34
|
- [Refactor] Require messages builder to accept partition and do not fetch it from messages.
|
@@ -50,7 +79,7 @@
|
|
50
79
|
|
51
80
|
## 2.0.35 (2023-03-13)
|
52
81
|
- **[Feature]** Allow for defining topics config via the DSL and its automatic creation via CLI command.
|
53
|
-
- **[Feature]** Allow for full topics reset and topics repartitioning via the CLI.
|
82
|
+
- **[Feature]** Allow for full topics reset and topics repartitioning via the CLI.
|
54
83
|
|
55
84
|
## 2.0.34 (2023-03-04)
|
56
85
|
- [Improvement] Attach an `embedded` tag to Karafka processes started using the embedded API.
|
data/Gemfile
CHANGED
@@ -6,10 +6,11 @@ plugin 'diffend'
|
|
6
6
|
|
7
7
|
gemspec
|
8
8
|
|
9
|
-
# Karafka gem does not require
|
10
|
-
#
|
9
|
+
# Karafka gem does not require activejob nor karafka-web to work
|
10
|
+
# They are added here because they are part of the integration suite
|
11
11
|
group :integrations do
|
12
12
|
gem 'activejob'
|
13
|
+
gem 'karafka-web'
|
13
14
|
end
|
14
15
|
|
15
16
|
group :test do
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
karafka (2.0
|
4
|
+
karafka (2.1.0)
|
5
5
|
karafka-core (>= 2.0.12, < 3.0.0)
|
6
6
|
thor (>= 0.20)
|
7
7
|
waterdrop (>= 2.4.10, < 3.0.0)
|
@@ -22,6 +22,7 @@ GEM
|
|
22
22
|
concurrent-ruby (1.2.2)
|
23
23
|
diff-lcs (1.5.0)
|
24
24
|
docile (1.4.0)
|
25
|
+
erubi (1.12.0)
|
25
26
|
factory_bot (6.2.1)
|
26
27
|
activesupport (>= 5.0.0)
|
27
28
|
ffi (1.15.5)
|
@@ -36,9 +37,18 @@ GEM
|
|
36
37
|
ffi (~> 1.15)
|
37
38
|
mini_portile2 (~> 2.6)
|
38
39
|
rake (> 12)
|
40
|
+
karafka-web (0.5.1)
|
41
|
+
erubi (~> 1.4)
|
42
|
+
karafka (>= 2.0.40, < 3.0.0)
|
43
|
+
karafka-core (>= 2.0.12, < 3.0.0)
|
44
|
+
roda (~> 3.63)
|
45
|
+
tilt (~> 2.0)
|
39
46
|
mini_portile2 (2.8.1)
|
40
47
|
minitest (5.18.0)
|
48
|
+
rack (3.0.7)
|
41
49
|
rake (13.0.6)
|
50
|
+
roda (3.67.0)
|
51
|
+
rack
|
42
52
|
rspec (3.12.0)
|
43
53
|
rspec-core (~> 3.12.0)
|
44
54
|
rspec-expectations (~> 3.12.0)
|
@@ -59,6 +69,7 @@ GEM
|
|
59
69
|
simplecov-html (0.12.3)
|
60
70
|
simplecov_json_formatter (0.1.4)
|
61
71
|
thor (1.2.1)
|
72
|
+
tilt (2.1.0)
|
62
73
|
tzinfo (2.0.6)
|
63
74
|
concurrent-ruby (~> 1.0)
|
64
75
|
waterdrop (2.5.1)
|
@@ -75,6 +86,7 @@ DEPENDENCIES
|
|
75
86
|
byebug
|
76
87
|
factory_bot
|
77
88
|
karafka!
|
89
|
+
karafka-web
|
78
90
|
rspec
|
79
91
|
simplecov
|
80
92
|
|
data/bin/integrations
CHANGED
@@ -152,8 +152,14 @@ class Scenario
|
|
152
152
|
end
|
153
153
|
end
|
154
154
|
|
155
|
+
# @return [Float] number of seconds that a given spec took to run
|
156
|
+
def time_taken
|
157
|
+
@finished_at - @started_at
|
158
|
+
end
|
159
|
+
|
155
160
|
# Close all the files that are open, so they do not pile up
|
156
161
|
def close
|
162
|
+
@finished_at = current_time
|
157
163
|
@stdin.close
|
158
164
|
@stdout.close
|
159
165
|
@stderr.close
|
@@ -262,13 +268,22 @@ while finished_scenarios.size < scenarios.size
|
|
262
268
|
sleep(0.1)
|
263
269
|
end
|
264
270
|
|
271
|
+
# Report longest scenarios
|
272
|
+
puts
|
273
|
+
puts "\nLongest scenarios:\n\n"
|
274
|
+
|
275
|
+
finished_scenarios.sort_by(&:time_taken).reverse.first(10).each do |long_scenario|
|
276
|
+
puts "[#{'%6.2f' % long_scenario.time_taken}] #{long_scenario.name}"
|
277
|
+
end
|
278
|
+
|
265
279
|
failed_scenarios = finished_scenarios.reject(&:success?)
|
266
280
|
|
267
|
-
# Report once more on the failed jobs
|
268
|
-
# This will only list scenarios that failed without printing their stdout here.
|
269
281
|
if failed_scenarios.empty?
|
270
282
|
puts
|
271
283
|
else
|
284
|
+
# Report once more on the failed jobs
|
285
|
+
# This will only list scenarios that failed without printing their stdout here.
|
286
|
+
puts
|
272
287
|
puts "\nFailed scenarios:\n\n"
|
273
288
|
|
274
289
|
failed_scenarios.each do |scenario|
|
data/config/locales/errors.yml
CHANGED
@@ -15,6 +15,13 @@ en:
|
|
15
15
|
shutdown_timeout_format: needs to be an integer bigger than 0
|
16
16
|
max_wait_time_format: needs to be an integer bigger than 0
|
17
17
|
kafka_format: needs to be a filled hash
|
18
|
+
internal.processing.jobs_builder_format: cannot be nil
|
19
|
+
internal.processing.scheduler: cannot be nil
|
20
|
+
internal.processing.coordinator_class: cannot be nil
|
21
|
+
internal.processing.partitioner_class: cannot be nil
|
22
|
+
internal.active_job.dispatcher: cannot be nil
|
23
|
+
internal.active_job.job_options_contract: cannot be nil
|
24
|
+
internal.active_job.consumer_class: cannot be nil
|
18
25
|
internal.status_format: needs to be present
|
19
26
|
internal.process_format: needs to be present
|
20
27
|
internal.routing.builder_format: needs to be present
|
@@ -31,7 +38,10 @@ en:
|
|
31
38
|
topics_missing: No topics to subscribe to
|
32
39
|
|
33
40
|
topic:
|
41
|
+
kafka: needs to be a hash with kafka scope settings details
|
34
42
|
missing: needs to be present
|
43
|
+
max_messages_format: 'needs to be an integer bigger than 0'
|
44
|
+
max_wait_time_format: 'needs to be an integer bigger than 0'
|
35
45
|
name_format: 'needs to be a string with a Kafka accepted format'
|
36
46
|
deserializer_format: needs to be present
|
37
47
|
consumer_format: needs to be present
|
@@ -4,8 +4,6 @@ en:
|
|
4
4
|
virtual_partitions.partitioner_respond_to_call: needs to be defined and needs to respond to `#call`
|
5
5
|
virtual_partitions.max_partitions_format: needs to be equal or more than 1
|
6
6
|
|
7
|
-
manual_offset_management_not_with_virtual_partitions: cannot be used together with Virtual Partitions
|
8
|
-
|
9
7
|
long_running_job.active_format: needs to be either true or false
|
10
8
|
|
11
9
|
dead_letter_queue_with_virtual_partitions: when using Dead Letter Queue with Virtual Partitions, at least one retry is required.
|
@@ -24,21 +24,26 @@ module Karafka
|
|
24
24
|
#
|
25
25
|
# @param job_message [Karafka::Messages::Message] message with active job
|
26
26
|
def consume_job(job_message)
|
27
|
-
|
28
|
-
|
29
|
-
# the ActiveJob setup here
|
30
|
-
job = ::ActiveSupport::JSON.decode(job_message.raw_payload)
|
27
|
+
with_deserialized_job(job_message) do |job|
|
28
|
+
tags.add(:job_class, job['job_class'])
|
31
29
|
|
32
|
-
|
30
|
+
payload = { caller: self, job: job, message: job_message }
|
33
31
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
::ActiveJob::Base.execute(job)
|
32
|
+
# We publish both to make it consistent with `consumer.x` events
|
33
|
+
Karafka.monitor.instrument('active_job.consume', payload)
|
34
|
+
Karafka.monitor.instrument('active_job.consumed', payload) do
|
35
|
+
::ActiveJob::Base.execute(job)
|
36
|
+
end
|
40
37
|
end
|
41
38
|
end
|
39
|
+
|
40
|
+
# @param job_message [Karafka::Messages::Message] message with active job
|
41
|
+
def with_deserialized_job(job_message)
|
42
|
+
# We technically speaking could set this as deserializer and reference it from the
|
43
|
+
# message instead of using the `#raw_payload`. This is not done on purpose to simplify
|
44
|
+
# the ActiveJob setup here
|
45
|
+
yield ::ActiveSupport::JSON.decode(job_message.raw_payload)
|
46
|
+
end
|
42
47
|
end
|
43
48
|
end
|
44
49
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module ActiveJob
|
5
|
+
module CurrentAttributes
|
6
|
+
# Module expanding the job deserialization to extract current attributes and load them
|
7
|
+
# for the time of the job execution
|
8
|
+
module Loading
|
9
|
+
# @param job_message [Karafka::Messages::Message] message with active job
|
10
|
+
def with_deserialized_job(job_message)
|
11
|
+
super(job_message) do |job|
|
12
|
+
resetable = []
|
13
|
+
|
14
|
+
_cattr_klasses.each do |key, cattr_klass_str|
|
15
|
+
next unless job.key?(key)
|
16
|
+
|
17
|
+
attributes = job.delete(key)
|
18
|
+
|
19
|
+
cattr_klass = cattr_klass_str.constantize
|
20
|
+
|
21
|
+
attributes.each do |name, value|
|
22
|
+
cattr_klass.public_send("#{name}=", value)
|
23
|
+
end
|
24
|
+
|
25
|
+
resetable << cattr_klass
|
26
|
+
end
|
27
|
+
|
28
|
+
yield(job)
|
29
|
+
|
30
|
+
resetable.each(&:reset)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module ActiveJob
|
5
|
+
module CurrentAttributes
|
6
|
+
# Module adding the current attributes persistence into the ActiveJob jobs
|
7
|
+
module Persistence
|
8
|
+
# Alters the job serialization to inject the current attributes into the json before we
|
9
|
+
# send it to Kafka
|
10
|
+
#
|
11
|
+
# @param job [ActiveJob::Base] job
|
12
|
+
def serialize_job(job)
|
13
|
+
json = super(job)
|
14
|
+
|
15
|
+
_cattr_klasses.each do |key, cattr_klass_str|
|
16
|
+
next if json.key?(key)
|
17
|
+
|
18
|
+
attrs = cattr_klass_str.constantize.attributes
|
19
|
+
|
20
|
+
json[key] = attrs unless attrs.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
json
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/current_attributes'
|
4
|
+
require_relative 'current_attributes/loading'
|
5
|
+
require_relative 'current_attributes/persistence'
|
6
|
+
|
7
|
+
# This code is based on Sidekiqs approach to persisting current attributes
|
8
|
+
# @see https://github.com/sidekiq/sidekiq/blob/main/lib/sidekiq/middleware/current_attributes.rb
|
9
|
+
module Karafka
|
10
|
+
module ActiveJob
|
11
|
+
# Module that allows to persist current attributes on Karafka jobs
|
12
|
+
module CurrentAttributes
|
13
|
+
# Allows for persistence of given current attributes via AJ + Karafka
|
14
|
+
#
|
15
|
+
# @param klasses [Array<String, Class>] classes or names of the current attributes classes
|
16
|
+
def persist(*klasses)
|
17
|
+
# Support for providing multiple classes
|
18
|
+
klasses = Array(klasses).flatten
|
19
|
+
|
20
|
+
[Dispatcher, Consumer]
|
21
|
+
.reject { |expandable| expandable.respond_to?(:_cattr_klasses) }
|
22
|
+
.each { |expandable| expandable.class_attribute :_cattr_klasses, default: {} }
|
23
|
+
|
24
|
+
# Do not double inject in case of running persist multiple times
|
25
|
+
Dispatcher.prepend(Persistence) unless Dispatcher.ancestors.include?(Persistence)
|
26
|
+
Consumer.prepend(Loading) unless Consumer.ancestors.include?(Loading)
|
27
|
+
|
28
|
+
klasses.map(&:to_s).each do |stringified_klass|
|
29
|
+
# Prevent registering same klass multiple times
|
30
|
+
next if Dispatcher._cattr_klasses.value?(stringified_klass)
|
31
|
+
|
32
|
+
key = "cattr_#{Dispatcher._cattr_klasses.count}"
|
33
|
+
|
34
|
+
Dispatcher._cattr_klasses[key] = stringified_klass
|
35
|
+
Consumer._cattr_klasses[key] = stringified_klass
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module_function :persist
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -18,7 +18,7 @@ module Karafka
|
|
18
18
|
::Karafka.producer.public_send(
|
19
19
|
fetch_option(job, :dispatch_method, DEFAULTS),
|
20
20
|
topic: job.queue_name,
|
21
|
-
payload: ::ActiveSupport::JSON.encode(job
|
21
|
+
payload: ::ActiveSupport::JSON.encode(serialize_job(job))
|
22
22
|
)
|
23
23
|
end
|
24
24
|
|
@@ -34,7 +34,7 @@ module Karafka
|
|
34
34
|
|
35
35
|
dispatches[d_method] << {
|
36
36
|
topic: job.queue_name,
|
37
|
-
payload: ::ActiveSupport::JSON.encode(job
|
37
|
+
payload: ::ActiveSupport::JSON.encode(serialize_job(job))
|
38
38
|
}
|
39
39
|
end
|
40
40
|
|
@@ -58,6 +58,12 @@ module Karafka
|
|
58
58
|
.karafka_options
|
59
59
|
.fetch(key, defaults.fetch(key))
|
60
60
|
end
|
61
|
+
|
62
|
+
# @param job [ActiveJob::Base] job
|
63
|
+
# @return [Hash] json representation of the job
|
64
|
+
def serialize_job(job)
|
65
|
+
job.serialize
|
66
|
+
end
|
61
67
|
end
|
62
68
|
end
|
63
69
|
end
|
data/lib/karafka/admin.rb
CHANGED
@@ -96,13 +96,15 @@ module Karafka
|
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
99
|
+
# Use topic from routes if we can match it or create a dummy one
|
100
|
+
# Dummy one is used in case we cannot match the topic with routes. This can happen
|
101
|
+
# when admin API is used to read topics that are not part of the routing
|
102
|
+
topic = ::Karafka::Routing::Router.find_or_initialize_by_name(name)
|
103
|
+
|
99
104
|
messages.map! do |message|
|
100
105
|
Messages::Builders::Message.call(
|
101
106
|
message,
|
102
|
-
|
103
|
-
# Dummy one is used in case we cannot match the topic with routes. This can happen
|
104
|
-
# when admin API is used to read topics that are not part of the routing
|
105
|
-
Routing::Router.find_by(name: name) || Topic.new(name, App.config.deserializer),
|
107
|
+
topic,
|
106
108
|
Time.now
|
107
109
|
)
|
108
110
|
end
|
@@ -173,6 +175,17 @@ module Karafka
|
|
173
175
|
end
|
174
176
|
end
|
175
177
|
|
178
|
+
# Creates consumer instance and yields it. After usage it closes the consumer instance
|
179
|
+
# This API can be used in other pieces of code and allows for low-level consumer usage
|
180
|
+
#
|
181
|
+
# @param settings [Hash] extra settings to customize consumer
|
182
|
+
def with_consumer(settings = {})
|
183
|
+
consumer = config(:consumer, settings).consumer
|
184
|
+
yield(consumer)
|
185
|
+
ensure
|
186
|
+
consumer&.close
|
187
|
+
end
|
188
|
+
|
176
189
|
private
|
177
190
|
|
178
191
|
# @return [Array<String>] topics names
|
@@ -195,15 +208,6 @@ module Karafka
|
|
195
208
|
admin&.close
|
196
209
|
end
|
197
210
|
|
198
|
-
# Creates consumer instance and yields it. After usage it closes the consumer instance
|
199
|
-
# @param settings [Hash] extra settings to customize consumer
|
200
|
-
def with_consumer(settings = {})
|
201
|
-
consumer = config(:consumer, settings).consumer
|
202
|
-
yield(consumer)
|
203
|
-
ensure
|
204
|
-
consumer&.close
|
205
|
-
end
|
206
|
-
|
207
211
|
# There are some cases where rdkafka admin operations finish successfully but without the
|
208
212
|
# callback being triggered to materialize the post-promise object. Until this is fixed we
|
209
213
|
# can figure out, that operation we wanted to do finished successfully by checking that the
|
@@ -30,7 +30,7 @@ module Karafka
|
|
30
30
|
#
|
31
31
|
# @param subscription_group [Karafka::Routing::SubscriptionGroup] subscription group
|
32
32
|
# with all the configuration details needed for us to create a client
|
33
|
-
# @return [Karafka::Connection::
|
33
|
+
# @return [Karafka::Connection::Client]
|
34
34
|
def initialize(subscription_group)
|
35
35
|
@id = SecureRandom.hex(6)
|
36
36
|
# Name is set when we build consumer
|
@@ -369,6 +369,8 @@ module Karafka
|
|
369
369
|
rescue ::Rdkafka::RdkafkaError => e
|
370
370
|
early_report = false
|
371
371
|
|
372
|
+
retryable = time_poll.attempts <= MAX_POLL_RETRIES && time_poll.retryable?
|
373
|
+
|
372
374
|
# There are retryable issues on which we want to report fast as they are source of
|
373
375
|
# problems and can mean some bigger system instabilities
|
374
376
|
# Those are mainly network issues and exceeding the max poll interval
|
@@ -389,9 +391,10 @@ module Karafka
|
|
389
391
|
return nil if @subscription_group.kafka[:'allow.auto.create.topics']
|
390
392
|
|
391
393
|
early_report = true
|
392
|
-
end
|
393
394
|
|
394
|
-
|
395
|
+
# No sense in retrying when no topic/partition and we're no longer running
|
396
|
+
retryable = false unless Karafka::App.running?
|
397
|
+
end
|
395
398
|
|
396
399
|
if early_report || !retryable
|
397
400
|
Karafka.monitor.instrument(
|
data/lib/karafka/errors.rb
CHANGED
@@ -32,6 +32,18 @@ module Karafka
|
|
32
32
|
consumer_group_id: @consumer_group_id,
|
33
33
|
statistics: @statistics_decorator.call(statistics)
|
34
34
|
)
|
35
|
+
# We need to catch and handle any potential errors coming from the instrumentation pipeline
|
36
|
+
# as otherwise, in case of statistics which run in the main librdkafka thread, any crash
|
37
|
+
# will hang the whole process.
|
38
|
+
rescue StandardError => e
|
39
|
+
::Karafka.monitor.instrument(
|
40
|
+
'error.occurred',
|
41
|
+
caller: self,
|
42
|
+
subscription_group_id: @subscription_group_id,
|
43
|
+
consumer_group_id: @consumer_group_id,
|
44
|
+
type: 'statistics.emitted.error',
|
45
|
+
error: e
|
46
|
+
)
|
35
47
|
end
|
36
48
|
end
|
37
49
|
end
|
@@ -48,7 +48,8 @@ module Karafka
|
|
48
48
|
job_type = job.class.to_s.split('::').last
|
49
49
|
consumer = job.executor.topic.consumer
|
50
50
|
topic = job.executor.topic.name
|
51
|
-
|
51
|
+
partition = job.executor.partition
|
52
|
+
info "[#{job.id}] #{job_type} job for #{consumer} on #{topic}/#{partition} started"
|
52
53
|
end
|
53
54
|
|
54
55
|
# Prints info about the fact that a given job has finished
|
@@ -60,7 +61,11 @@ module Karafka
|
|
60
61
|
job_type = job.class.to_s.split('::').last
|
61
62
|
consumer = job.executor.topic.consumer
|
62
63
|
topic = job.executor.topic.name
|
63
|
-
|
64
|
+
partition = job.executor.partition
|
65
|
+
info <<~MSG.tr("\n", ' ').strip!
|
66
|
+
[#{job.id}] #{job_type} job for #{consumer}
|
67
|
+
on #{topic}/#{partition} finished in #{time}ms
|
68
|
+
MSG
|
64
69
|
end
|
65
70
|
|
66
71
|
# Prints info about a consumer pause occurrence. Irrelevant if user or system initiated.
|
@@ -73,7 +78,7 @@ module Karafka
|
|
73
78
|
client = event[:caller]
|
74
79
|
|
75
80
|
info <<~MSG.tr("\n", ' ').strip!
|
76
|
-
[#{client.id}] Pausing
|
81
|
+
[#{client.id}] Pausing on topic #{topic}/#{partition} on offset #{offset}
|
77
82
|
MSG
|
78
83
|
end
|
79
84
|
|
@@ -86,7 +91,7 @@ module Karafka
|
|
86
91
|
client = event[:caller]
|
87
92
|
|
88
93
|
info <<~MSG.tr("\n", ' ').strip!
|
89
|
-
[#{client.id}] Resuming
|
94
|
+
[#{client.id}] Resuming on topic #{topic}/#{partition}
|
90
95
|
MSG
|
91
96
|
end
|
92
97
|
|
@@ -102,7 +107,7 @@ module Karafka
|
|
102
107
|
|
103
108
|
info <<~MSG.tr("\n", ' ').strip!
|
104
109
|
[#{consumer.id}] Retrying of #{consumer.class} after #{timeout} ms
|
105
|
-
on
|
110
|
+
on topic #{topic}/#{partition} from offset #{offset}
|
106
111
|
MSG
|
107
112
|
end
|
108
113
|
|
@@ -261,6 +266,12 @@ module Karafka
|
|
261
266
|
when 'librdkafka.error'
|
262
267
|
error "librdkafka internal error occurred: #{error}"
|
263
268
|
error details
|
269
|
+
# Those can occur when emitted statistics are consumed by the end user and the processing
|
270
|
+
# of statistics fails. The statistics are emitted from librdkafka main loop thread and
|
271
|
+
# any errors there crash the whole thread
|
272
|
+
when 'statistics.emitted.error'
|
273
|
+
error "statistics.emitted processing failed due to an error: #{error}"
|
274
|
+
error details
|
264
275
|
# Those will only occur when retries in the client fail and when they did not stop after
|
265
276
|
# back-offs
|
266
277
|
when 'connection.client.poll.error'
|