karafka 2.4.18 → 2.5.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/.github/CODEOWNERS +3 -0
- data/.github/workflows/ci.yml +59 -15
- data/.github/workflows/push.yml +35 -0
- data/.github/workflows/verify-action-pins.yml +16 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +75 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +72 -53
- data/LICENSE-COMM +2 -2
- data/README.md +1 -1
- data/Rakefile +4 -0
- data/bin/clean_kafka +43 -0
- data/bin/integrations +20 -6
- data/bin/rspecs +15 -3
- data/bin/verify_kafka_warnings +35 -0
- data/bin/verify_topics_naming +27 -0
- data/config/locales/errors.yml +5 -1
- data/config/locales/pro_errors.yml +13 -2
- data/docker-compose.yml +1 -1
- data/examples/payloads/avro/.gitkeep +0 -0
- data/examples/payloads/json/sample_set_01/enrollment_event.json +579 -0
- data/examples/payloads/json/sample_set_01/ingestion_event.json +30 -0
- data/examples/payloads/json/sample_set_01/transaction_event.json +17 -0
- data/examples/payloads/json/sample_set_01/user_event.json +11 -0
- data/karafka.gemspec +3 -8
- data/lib/karafka/active_job/current_attributes.rb +1 -1
- data/lib/karafka/active_job/job_extensions.rb +4 -1
- data/lib/karafka/admin/acl.rb +5 -1
- data/lib/karafka/admin/configs.rb +5 -1
- data/lib/karafka/admin.rb +89 -42
- data/lib/karafka/base_consumer.rb +17 -8
- data/lib/karafka/cli/base.rb +8 -2
- data/lib/karafka/cli/topics/align.rb +7 -4
- data/lib/karafka/cli/topics/base.rb +17 -0
- data/lib/karafka/cli/topics/create.rb +9 -7
- data/lib/karafka/cli/topics/delete.rb +4 -2
- data/lib/karafka/cli/topics/help.rb +39 -0
- data/lib/karafka/cli/topics/repartition.rb +4 -2
- data/lib/karafka/cli/topics.rb +10 -3
- data/lib/karafka/cli.rb +2 -0
- data/lib/karafka/connection/client.rb +39 -9
- data/lib/karafka/connection/listener.rb +24 -12
- data/lib/karafka/connection/messages_buffer.rb +1 -1
- data/lib/karafka/connection/proxy.rb +4 -1
- data/lib/karafka/constraints.rb +3 -3
- data/lib/karafka/contracts/base.rb +3 -2
- data/lib/karafka/contracts/config.rb +5 -1
- data/lib/karafka/contracts/topic.rb +1 -1
- data/lib/karafka/errors.rb +46 -2
- data/lib/karafka/helpers/async.rb +3 -1
- data/lib/karafka/helpers/interval_runner.rb +8 -0
- data/lib/karafka/instrumentation/callbacks/rebalance.rb +5 -1
- data/lib/karafka/instrumentation/logger_listener.rb +95 -32
- data/lib/karafka/instrumentation/proctitle_listener.rb +5 -1
- data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +2 -2
- data/lib/karafka/instrumentation/vendors/kubernetes/base_listener.rb +17 -2
- data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +29 -6
- data/lib/karafka/instrumentation/vendors/kubernetes/swarm_liveness_listener.rb +9 -0
- data/lib/karafka/messages/builders/batch_metadata.rb +1 -1
- data/lib/karafka/pro/cleaner.rb +8 -0
- data/lib/karafka/pro/cli/parallel_segments/base.rb +89 -0
- data/lib/karafka/pro/cli/parallel_segments/collapse.rb +164 -0
- data/lib/karafka/pro/cli/parallel_segments/distribute.rb +164 -0
- data/lib/karafka/pro/cli/parallel_segments.rb +60 -0
- data/lib/karafka/pro/connection/manager.rb +5 -8
- data/lib/karafka/pro/encryption.rb +12 -1
- data/lib/karafka/pro/instrumentation/performance_tracker.rb +1 -1
- data/lib/karafka/pro/iterator/expander.rb +5 -3
- data/lib/karafka/pro/iterator/tpl_builder.rb +23 -0
- data/lib/karafka/pro/loader.rb +10 -0
- data/lib/karafka/pro/processing/coordinator.rb +4 -1
- data/lib/karafka/pro/processing/coordinators/errors_tracker.rb +32 -3
- data/lib/karafka/pro/processing/coordinators/filters_applier.rb +11 -0
- data/lib/karafka/pro/processing/filters/base.rb +10 -2
- data/lib/karafka/pro/processing/filters/expirer.rb +5 -0
- data/lib/karafka/pro/processing/filters/inline_insights_delayer.rb +2 -2
- data/lib/karafka/pro/processing/filters/virtual_limiter.rb +5 -0
- data/lib/karafka/pro/processing/parallel_segments/filters/base.rb +73 -0
- data/lib/karafka/pro/processing/parallel_segments/filters/default.rb +85 -0
- data/lib/karafka/pro/processing/parallel_segments/filters/mom.rb +66 -0
- data/lib/karafka/pro/processing/partitioner.rb +1 -13
- data/lib/karafka/pro/processing/piping/consumer.rb +13 -13
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/default.rb +36 -8
- data/lib/karafka/pro/processing/strategies/dlq/default.rb +15 -10
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj.rb +1 -1
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/dlq/lrj.rb +3 -1
- data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/ftr/default.rb +1 -1
- data/lib/karafka/pro/processing/strategies/lrj/default.rb +4 -1
- data/lib/karafka/pro/processing/strategies/lrj/ftr.rb +1 -1
- data/lib/karafka/pro/processing/strategies/lrj/ftr_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/lrj/mom.rb +1 -1
- data/lib/karafka/pro/processing/virtual_partitions/distributors/balanced.rb +50 -0
- data/lib/karafka/pro/processing/virtual_partitions/distributors/base.rb +29 -0
- data/lib/karafka/pro/processing/virtual_partitions/distributors/consistent.rb +27 -0
- data/lib/karafka/pro/recurring_tasks/contracts/config.rb +8 -4
- data/lib/karafka/pro/recurring_tasks/dispatcher.rb +3 -3
- data/lib/karafka/pro/recurring_tasks/setup/config.rb +7 -2
- data/lib/karafka/pro/recurring_tasks.rb +21 -2
- data/lib/karafka/pro/routing/features/dead_letter_queue/topic.rb +1 -1
- data/lib/karafka/pro/routing/features/multiplexing/config.rb +1 -0
- data/lib/karafka/pro/routing/features/multiplexing/contracts/topic.rb +17 -0
- data/lib/karafka/pro/routing/features/multiplexing/proxy.rb +5 -2
- data/lib/karafka/pro/routing/features/multiplexing/subscription_group.rb +8 -1
- data/lib/karafka/pro/routing/features/parallel_segments/builder.rb +47 -0
- data/lib/karafka/pro/routing/features/parallel_segments/config.rb +27 -0
- data/lib/karafka/pro/routing/features/parallel_segments/consumer_group.rb +83 -0
- data/lib/karafka/pro/routing/features/parallel_segments/contracts/consumer_group.rb +49 -0
- data/lib/karafka/pro/routing/features/parallel_segments/topic.rb +43 -0
- data/lib/karafka/pro/routing/features/parallel_segments.rb +24 -0
- data/lib/karafka/pro/routing/features/patterns/pattern.rb +1 -1
- data/lib/karafka/pro/routing/features/recurring_tasks/builder.rb +2 -2
- data/lib/karafka/pro/routing/features/scheduled_messages/builder.rb +10 -6
- data/lib/karafka/pro/routing/features/swarm/contracts/routing.rb +3 -2
- data/lib/karafka/pro/routing/features/swarm.rb +4 -1
- data/lib/karafka/pro/routing/features/virtual_partitions/config.rb +20 -2
- data/lib/karafka/pro/routing/features/virtual_partitions/contracts/topic.rb +1 -0
- data/lib/karafka/pro/routing/features/virtual_partitions/topic.rb +8 -2
- data/lib/karafka/pro/scheduled_messages/consumer.rb +61 -26
- data/lib/karafka/pro/scheduled_messages/daily_buffer.rb +9 -6
- data/lib/karafka/pro/scheduled_messages/deserializers/headers.rb +7 -1
- data/lib/karafka/pro/scheduled_messages/dispatcher.rb +2 -1
- data/lib/karafka/pro/scheduled_messages/max_epoch.rb +15 -6
- data/lib/karafka/pro/scheduled_messages/proxy.rb +15 -3
- data/lib/karafka/pro/scheduled_messages/serializer.rb +2 -4
- data/lib/karafka/pro/scheduled_messages/state.rb +20 -23
- data/lib/karafka/pro/scheduled_messages/tracker.rb +34 -8
- data/lib/karafka/pro/scheduled_messages.rb +17 -1
- data/lib/karafka/processing/coordinators_buffer.rb +1 -0
- data/lib/karafka/processing/strategies/default.rb +4 -4
- data/lib/karafka/routing/builder.rb +12 -3
- data/lib/karafka/routing/features/base/expander.rb +8 -2
- data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +1 -0
- data/lib/karafka/routing/subscription_group.rb +1 -1
- data/lib/karafka/runner.rb +7 -1
- data/lib/karafka/server.rb +21 -18
- data/lib/karafka/setup/attributes_map.rb +2 -0
- data/lib/karafka/setup/config.rb +40 -7
- data/lib/karafka/setup/defaults_injector.rb +26 -1
- data/lib/karafka/status.rb +6 -1
- data/lib/karafka/swarm/node.rb +31 -0
- data/lib/karafka/swarm/supervisor.rb +9 -2
- data/lib/karafka/templates/karafka.rb.erb +14 -1
- data/lib/karafka/version.rb +1 -1
- data/lib/karafka.rb +17 -9
- data/renovate.json +14 -2
- metadata +41 -40
- checksums.yaml.gz.sig +0 -0
- data/certs/cert.pem +0 -26
- data.tar.gz.sig +0 -0
- metadata.gz.sig +0 -0
@@ -12,6 +12,13 @@ module Karafka
|
|
12
12
|
class Listener
|
13
13
|
include Helpers::Async
|
14
14
|
|
15
|
+
include Helpers::ConfigImporter.new(
|
16
|
+
jobs_builder: %i[internal processing jobs_builder],
|
17
|
+
partitioner_class: %i[internal processing partitioner_class],
|
18
|
+
reset_backoff: %i[internal connection reset_backoff],
|
19
|
+
listener_thread_priority: %i[internal connection listener_thread_priority]
|
20
|
+
)
|
21
|
+
|
15
22
|
# Can be useful for logging
|
16
23
|
# @return [String] id of this listener
|
17
24
|
attr_reader :id
|
@@ -19,6 +26,11 @@ module Karafka
|
|
19
26
|
# @return [Karafka::Routing::SubscriptionGroup] subscription group that this listener handles
|
20
27
|
attr_reader :subscription_group
|
21
28
|
|
29
|
+
# @return [Processing::CoordinatorsBuffer] coordinator buffers that can be used directly in
|
30
|
+
# advanced cases of changes to the polling flow (like triggered seek back without messages
|
31
|
+
# ahead in the topic)
|
32
|
+
attr_reader :coordinators
|
33
|
+
|
22
34
|
# How long to wait in the initial events poll. Increases chances of having the initial events
|
23
35
|
# immediately available
|
24
36
|
INITIAL_EVENTS_POLL_TIMEOUT = 100
|
@@ -30,16 +42,13 @@ module Karafka
|
|
30
42
|
# @param scheduler [Karafka::Processing::Scheduler] scheduler we want to use
|
31
43
|
# @return [Karafka::Connection::Listener] listener instance
|
32
44
|
def initialize(subscription_group, jobs_queue, scheduler)
|
33
|
-
proc_config = ::Karafka::App.config.internal.processing
|
34
|
-
|
35
45
|
@id = SecureRandom.hex(6)
|
36
46
|
@subscription_group = subscription_group
|
37
47
|
@jobs_queue = jobs_queue
|
38
48
|
@coordinators = Processing::CoordinatorsBuffer.new(subscription_group.topics)
|
39
49
|
@client = Client.new(@subscription_group, -> { running? })
|
40
50
|
@executors = Processing::ExecutorsBuffer.new(@client, subscription_group)
|
41
|
-
@
|
42
|
-
@partitioner = proc_config.partitioner_class.new(subscription_group)
|
51
|
+
@partitioner = partitioner_class.new(subscription_group)
|
43
52
|
@scheduler = scheduler
|
44
53
|
@events_poller = Helpers::IntervalRunner.new { @client.events_poll }
|
45
54
|
# We keep one buffer for messages to preserve memory and not allocate extra objects
|
@@ -111,7 +120,10 @@ module Karafka
|
|
111
120
|
|
112
121
|
@status.start!
|
113
122
|
|
114
|
-
async_call(
|
123
|
+
async_call(
|
124
|
+
"karafka.listener##{@subscription_group.id}",
|
125
|
+
listener_thread_priority
|
126
|
+
)
|
115
127
|
end
|
116
128
|
|
117
129
|
# Stops the jobs queue, triggers shutdown on all the executors (sync), commits offsets and
|
@@ -254,7 +266,7 @@ module Karafka
|
|
254
266
|
reset
|
255
267
|
|
256
268
|
# Ruby sleep is in seconds
|
257
|
-
sleep_time =
|
269
|
+
sleep_time = reset_backoff / 10_000.0
|
258
270
|
sleep(sleep_time) && retry
|
259
271
|
end
|
260
272
|
|
@@ -294,7 +306,7 @@ module Karafka
|
|
294
306
|
# here. In cases like this, we do not run a revocation job
|
295
307
|
@executors.find_all(topic, partition).each do |executor|
|
296
308
|
executor.coordinator.increment(:revoked)
|
297
|
-
jobs <<
|
309
|
+
jobs << jobs_builder.revoked(executor)
|
298
310
|
end
|
299
311
|
|
300
312
|
# We need to remove all the executors of a given topic partition that we have lost, so
|
@@ -318,7 +330,7 @@ module Karafka
|
|
318
330
|
|
319
331
|
@executors.each do |executor|
|
320
332
|
executor.coordinator.increment(:shutdown)
|
321
|
-
job =
|
333
|
+
job = jobs_builder.shutdown(executor)
|
322
334
|
jobs << job
|
323
335
|
end
|
324
336
|
|
@@ -355,7 +367,7 @@ module Karafka
|
|
355
367
|
if coordinator.topic.eofed?
|
356
368
|
@executors.find_all_or_create(topic, partition, coordinator).each do |executor|
|
357
369
|
coordinator.increment(:eofed)
|
358
|
-
eofed_jobs <<
|
370
|
+
eofed_jobs << jobs_builder.eofed(executor)
|
359
371
|
end
|
360
372
|
end
|
361
373
|
|
@@ -372,7 +384,7 @@ module Karafka
|
|
372
384
|
# Start work coordination for this topic partition
|
373
385
|
coordinator.increment(:idle)
|
374
386
|
executor = @executors.find_or_create(topic, partition, 0, coordinator)
|
375
|
-
idle_jobs <<
|
387
|
+
idle_jobs << jobs_builder.idle(executor)
|
376
388
|
|
377
389
|
next
|
378
390
|
end
|
@@ -383,7 +395,7 @@ module Karafka
|
|
383
395
|
@partitioner.call(topic, messages, coordinator) do |group_id, partition_messages|
|
384
396
|
coordinator.increment(:consume)
|
385
397
|
executor = @executors.find_or_create(topic, partition, group_id, coordinator)
|
386
|
-
consume_jobs <<
|
398
|
+
consume_jobs << jobs_builder.consume(executor, partition_messages)
|
387
399
|
end
|
388
400
|
end
|
389
401
|
|
@@ -451,7 +463,7 @@ module Karafka
|
|
451
463
|
|
452
464
|
@executors.find_all_or_create(topic_name, partition, coordinator).each do |executor|
|
453
465
|
coordinator.increment(:periodic)
|
454
|
-
jobs <<
|
466
|
+
jobs << jobs_builder.periodic(executor)
|
455
467
|
end
|
456
468
|
end
|
457
469
|
end
|
@@ -44,7 +44,7 @@ module Karafka
|
|
44
44
|
# clusters can handle our requests.
|
45
45
|
#
|
46
46
|
# @param topic [String] topic name
|
47
|
-
# @param partition [
|
47
|
+
# @param partition [Integer] partition number
|
48
48
|
# @return [Array<Integer, Integer>] watermark offsets
|
49
49
|
def query_watermark_offsets(topic, partition)
|
50
50
|
l_config = @config.query_watermark_offsets
|
@@ -108,6 +108,7 @@ module Karafka
|
|
108
108
|
rescue Rdkafka::RdkafkaError => e
|
109
109
|
return false if e.code == :assignment_lost
|
110
110
|
return false if e.code == :state
|
111
|
+
return false if e.code == :illegal_generation
|
111
112
|
|
112
113
|
raise e
|
113
114
|
end
|
@@ -136,6 +137,8 @@ module Karafka
|
|
136
137
|
return false
|
137
138
|
when :unknown_member_id
|
138
139
|
return false
|
140
|
+
when :illegal_generation
|
141
|
+
return false
|
139
142
|
when :no_offset
|
140
143
|
return true
|
141
144
|
when :coordinator_load_in_progress
|
data/lib/karafka/constraints.rb
CHANGED
@@ -15,13 +15,13 @@ module Karafka
|
|
15
15
|
# Skip verification if web is not used at all
|
16
16
|
return unless require_version('karafka/web')
|
17
17
|
|
18
|
-
# All good if version higher than 0.
|
19
|
-
return if version(Karafka::Web::VERSION) >= version('0.
|
18
|
+
# All good if version higher than 0.10.0 because we expect 0.10.0 or higher
|
19
|
+
return if version(Karafka::Web::VERSION) >= version('0.10.0')
|
20
20
|
|
21
21
|
# If older web-ui used, we cannot allow it
|
22
22
|
raise(
|
23
23
|
Errors::DependencyConstraintsError,
|
24
|
-
'karafka-web < 0.
|
24
|
+
'karafka-web < 0.10.0 is not compatible with this karafka version'
|
25
25
|
)
|
26
26
|
end
|
27
27
|
|
@@ -5,12 +5,13 @@ module Karafka
|
|
5
5
|
# Base contract for all Karafka contracts
|
6
6
|
class Base < ::Karafka::Core::Contractable::Contract
|
7
7
|
# @param data [Hash] data for validation
|
8
|
+
# @param scope [Array<String>] nested scope if in use
|
8
9
|
# @return [Boolean] true if all good
|
9
10
|
# @raise [Errors::InvalidConfigurationError] invalid configuration error
|
10
11
|
# @note We use contracts only in the config validation context, so no need to add support
|
11
12
|
# for multiple error classes. It will be added when it will be needed.
|
12
|
-
def validate!(data)
|
13
|
-
super(data, Errors::InvalidConfigurationError)
|
13
|
+
def validate!(data, scope: [])
|
14
|
+
super(data, Errors::InvalidConfigurationError, scope: scope)
|
14
15
|
end
|
15
16
|
end
|
16
17
|
end
|
@@ -35,6 +35,7 @@ module Karafka
|
|
35
35
|
required(:group_id) { |val| val.is_a?(String) && Contracts::TOPIC_REGEXP.match?(val) }
|
36
36
|
required(:kafka) { |val| val.is_a?(Hash) && !val.empty? }
|
37
37
|
required(:strict_declarative_topics) { |val| [true, false].include?(val) }
|
38
|
+
required(:worker_thread_priority) { |val| (-3..3).to_a.include?(val) }
|
38
39
|
|
39
40
|
nested(:swarm) do
|
40
41
|
required(:nodes) { |val| val.is_a?(Integer) && val.positive? }
|
@@ -52,7 +53,8 @@ module Karafka
|
|
52
53
|
required(:kafka) { |val| val.is_a?(Hash) }
|
53
54
|
required(:group_id) { |val| val.is_a?(String) && Contracts::TOPIC_REGEXP.match?(val) }
|
54
55
|
required(:max_wait_time) { |val| val.is_a?(Integer) && val.positive? }
|
55
|
-
required(:
|
56
|
+
required(:retry_backoff) { |val| val.is_a?(Integer) && val >= 100 }
|
57
|
+
required(:max_retries_duration) { |val| val.is_a?(Integer) && val >= 1_000 }
|
56
58
|
end
|
57
59
|
|
58
60
|
# We validate internals just to be sure, that they are present and working
|
@@ -81,6 +83,7 @@ module Karafka
|
|
81
83
|
required(:manager) { |val| !val.nil? }
|
82
84
|
required(:conductor) { |val| !val.nil? }
|
83
85
|
required(:reset_backoff) { |val| val.is_a?(Integer) && val >= 1_000 }
|
86
|
+
required(:listener_thread_priority) { |val| (-3..3).to_a.include?(val) }
|
84
87
|
|
85
88
|
nested(:proxy) do
|
86
89
|
nested(:commit) do
|
@@ -114,6 +117,7 @@ module Karafka
|
|
114
117
|
required(:jobs_queue_class) { |val| !val.nil? }
|
115
118
|
required(:scheduler_class) { |val| !val.nil? }
|
116
119
|
required(:coordinator_class) { |val| !val.nil? }
|
120
|
+
required(:errors_tracker_class) { |val| val.nil? || val.is_a?(Class) }
|
117
121
|
required(:partitioner_class) { |val| !val.nil? }
|
118
122
|
required(:strategy_selector) { |val| !val.nil? }
|
119
123
|
required(:expansions_selector) { |val| !val.nil? }
|
@@ -70,7 +70,7 @@ module Karafka
|
|
70
70
|
next unless ::Karafka::App.config.strict_topics_namespacing
|
71
71
|
|
72
72
|
value = data.fetch(:name)
|
73
|
-
namespacing_chars_count = value.chars.find_all { |c| ['.', '_'].include?(c) }.uniq.
|
73
|
+
namespacing_chars_count = value.chars.find_all { |c| ['.', '_'].include?(c) }.uniq.size
|
74
74
|
|
75
75
|
next if namespacing_chars_count <= 1
|
76
76
|
|
data/lib/karafka/errors.rb
CHANGED
@@ -22,7 +22,34 @@ module Karafka
|
|
22
22
|
InvalidConfigurationError = Class.new(BaseError)
|
23
23
|
|
24
24
|
# Raised when we try to use Karafka CLI commands (except install) without a boot file
|
25
|
-
MissingBootFileError = Class.new(BaseError)
|
25
|
+
MissingBootFileError = Class.new(BaseError) do
|
26
|
+
# @param boot_file_path [Pathname] path where the boot file should be
|
27
|
+
def initialize(boot_file_path)
|
28
|
+
message = <<~MSG
|
29
|
+
|
30
|
+
\e[31mKarafka Boot File Missing:\e[0m #{boot_file_path}
|
31
|
+
|
32
|
+
Cannot find Karafka boot file - this file configures your Karafka application.
|
33
|
+
|
34
|
+
\e[33mQuick fixes:\e[0m
|
35
|
+
\e[32m1.\e[0m Navigate to your Karafka app directory
|
36
|
+
\e[32m2.\e[0m Check if following file exists: \e[36m#{boot_file_path}\e[0m
|
37
|
+
\e[32m3.\e[0m Install Karafka if needed: \e[36mkarafka install\e[0m
|
38
|
+
|
39
|
+
\e[33mCommon causes:\e[0m
|
40
|
+
\e[31m•\e[0m Wrong directory (not in Karafka app root)
|
41
|
+
\e[31m•\e[0m File was accidentally moved or deleted
|
42
|
+
\e[31m•\e[0m New project needing initialization
|
43
|
+
|
44
|
+
For setup help: \e[34mhttps://karafka.io/docs/Getting-Started\e[0m
|
45
|
+
MSG
|
46
|
+
|
47
|
+
super(message)
|
48
|
+
# In case of this error backtrace is irrelevant and we want to print comprehensive error
|
49
|
+
# message without backtrace, this is why nullified.
|
50
|
+
set_backtrace([])
|
51
|
+
end
|
52
|
+
end
|
26
53
|
|
27
54
|
# Raised when we've waited enough for shutting down a non-responsive process
|
28
55
|
ForcefulShutdownError = Class.new(BaseError)
|
@@ -35,6 +62,9 @@ module Karafka
|
|
35
62
|
# Raised when given topic is not found while expected
|
36
63
|
TopicNotFoundError = Class.new(BaseError)
|
37
64
|
|
65
|
+
# Raised when given consumer group is not found while expected
|
66
|
+
ConsumerGroupNotFoundError = Class.new(BaseError)
|
67
|
+
|
38
68
|
# This should never happen. Please open an issue if it does.
|
39
69
|
UnsupportedCaseError = Class.new(BaseError)
|
40
70
|
|
@@ -62,7 +92,17 @@ module Karafka
|
|
62
92
|
ResultNotVisibleError = Class.new(BaseError)
|
63
93
|
|
64
94
|
# Raised when there is an attempt to run an unrecognized CLI command
|
65
|
-
UnrecognizedCommandError = Class.new(BaseError)
|
95
|
+
UnrecognizedCommandError = Class.new(BaseError) do
|
96
|
+
# Overwritten not to print backtrace for unknown CLI command
|
97
|
+
def initialize(*args)
|
98
|
+
super
|
99
|
+
set_backtrace([])
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Raised when you were executing a command and it could not finish successfully because of
|
104
|
+
# a setup state or parameters configuration
|
105
|
+
CommandValidationError = Class.new(BaseError)
|
66
106
|
|
67
107
|
# Raised when we attempt to perform operation that is only allowed inside of a transaction and
|
68
108
|
# there is no transaction around us
|
@@ -71,6 +111,10 @@ module Karafka
|
|
71
111
|
# Raised in case user would want to perform nested transactions.
|
72
112
|
TransactionAlreadyInitializedError = Class.new(BaseError)
|
73
113
|
|
114
|
+
# Raised when user used transactional offset marking but after that tried to use
|
115
|
+
# non-transactional marking, effectively mixing both. This is not allowed.
|
116
|
+
NonTransactionalMarkingAttemptError = Class.new(BaseError)
|
117
|
+
|
74
118
|
# Raised in case a listener that was paused is being resumed
|
75
119
|
InvalidListenerResumeError = Class.new(BaseError)
|
76
120
|
|
@@ -37,12 +37,14 @@ module Karafka
|
|
37
37
|
|
38
38
|
# Runs the `#call` method in a new thread
|
39
39
|
# @param thread_name [String] name that we want to assign to the thread when we start it
|
40
|
-
|
40
|
+
# @param thread_priority [Integer] Ruby thread priority
|
41
|
+
def async_call(thread_name, thread_priority = 0)
|
41
42
|
MUTEX.synchronize do
|
42
43
|
return if @thread&.alive?
|
43
44
|
|
44
45
|
@thread = Thread.new do
|
45
46
|
Thread.current.name = thread_name
|
47
|
+
Thread.current.priority = thread_priority
|
46
48
|
|
47
49
|
Thread.current.abort_on_exception = true
|
48
50
|
|
@@ -30,6 +30,14 @@ module Karafka
|
|
30
30
|
@block.call
|
31
31
|
end
|
32
32
|
|
33
|
+
# Runs the requested code bypassing any time frequencies
|
34
|
+
# Useful when we have certain actions that usually need to run periodically but in some
|
35
|
+
# cases need to run asap
|
36
|
+
def call!
|
37
|
+
@last_called_at = monotonic_now
|
38
|
+
@block.call
|
39
|
+
end
|
40
|
+
|
33
41
|
# Resets the runner, so next `#call` will run the underlying code
|
34
42
|
def reset
|
35
43
|
@last_called_at = monotonic_now - @interval
|
@@ -12,8 +12,10 @@ module Karafka
|
|
12
12
|
|
13
13
|
# @param subscription_group [Karafka::Routes::SubscriptionGroup] subscription group for
|
14
14
|
# which we want to manage rebalances
|
15
|
-
|
15
|
+
# @param client_id [String] id of the client managing this rebalance
|
16
|
+
def initialize(subscription_group, client_id)
|
16
17
|
@subscription_group = subscription_group
|
18
|
+
@client_id = client_id
|
17
19
|
end
|
18
20
|
|
19
21
|
# Publishes an event that partitions are going to be revoked.
|
@@ -62,6 +64,7 @@ module Karafka
|
|
62
64
|
subscription_group: @subscription_group,
|
63
65
|
consumer_group_id: @subscription_group.consumer_group.id,
|
64
66
|
consumer_group: @subscription_group.consumer_group,
|
67
|
+
client_id: @client_id,
|
65
68
|
tpl: tpl
|
66
69
|
)
|
67
70
|
rescue StandardError => e
|
@@ -71,6 +74,7 @@ module Karafka
|
|
71
74
|
subscription_group_id: @subscription_group.id,
|
72
75
|
consumer_group_id: @subscription_group.consumer_group.id,
|
73
76
|
type: "callbacks.rebalance.#{name}.error",
|
77
|
+
client_id: @client_id,
|
74
78
|
error: e
|
75
79
|
)
|
76
80
|
end
|
@@ -24,14 +24,29 @@ module Karafka
|
|
24
24
|
@log_polling = log_polling
|
25
25
|
end
|
26
26
|
|
27
|
+
#
|
28
|
+
#
|
29
|
+
# @param event [Karafka::Core::Monitoring::Event] event details including payload
|
30
|
+
def on_connection_listener_before_fetch_loop(event)
|
31
|
+
listener_id = event[:caller].id
|
32
|
+
subscription_group = event[:subscription_group]
|
33
|
+
consumer_group_id = subscription_group.consumer_group.id
|
34
|
+
topics = subscription_group.topics.select(&:active?).map(&:name).join(', ')
|
35
|
+
group_details = "#{consumer_group_id}/#{subscription_group.id}"
|
36
|
+
|
37
|
+
info(
|
38
|
+
"[#{listener_id}] Group #{group_details} subscribing to topics: #{topics}"
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
27
42
|
# Logs each messages fetching attempt
|
28
43
|
#
|
29
44
|
# @param event [Karafka::Core::Monitoring::Event] event details including payload
|
30
45
|
def on_connection_listener_fetch_loop(event)
|
31
46
|
return unless log_polling?
|
32
47
|
|
33
|
-
|
34
|
-
debug "[#{
|
48
|
+
listener_id = event[:caller].id
|
49
|
+
debug "[#{listener_id}] Polling messages..."
|
35
50
|
end
|
36
51
|
|
37
52
|
# Logs about messages that we've received from Kafka
|
@@ -40,11 +55,11 @@ module Karafka
|
|
40
55
|
def on_connection_listener_fetch_loop_received(event)
|
41
56
|
return unless log_polling?
|
42
57
|
|
43
|
-
|
58
|
+
listener_id = event[:caller].id
|
44
59
|
time = event[:time].round(2)
|
45
60
|
messages_count = event[:messages_buffer].size
|
46
61
|
|
47
|
-
message = "[#{
|
62
|
+
message = "[#{listener_id}] Polled #{messages_count} messages in #{time}ms"
|
48
63
|
|
49
64
|
# We don't want the "polled 0" in dev as it would spam the log
|
50
65
|
# Instead we publish only info when there was anything we could poll and fail over to the
|
@@ -61,7 +76,7 @@ module Karafka
|
|
61
76
|
consumer = job.executor.topic.consumer
|
62
77
|
topic = job.executor.topic.name
|
63
78
|
partition = job.executor.partition
|
64
|
-
info "[#{job.id}] #{job_type} job for #{consumer} on #{topic}
|
79
|
+
info "[#{job.id}] #{job_type} job for #{consumer} on #{topic}-#{partition} started"
|
65
80
|
end
|
66
81
|
|
67
82
|
# Prints info about the fact that a given job has finished
|
@@ -76,7 +91,7 @@ module Karafka
|
|
76
91
|
partition = job.executor.partition
|
77
92
|
info <<~MSG.tr("\n", ' ').strip!
|
78
93
|
[#{job.id}] #{job_type} job for #{consumer}
|
79
|
-
on #{topic}
|
94
|
+
on #{topic}-#{partition} finished in #{time} ms
|
80
95
|
MSG
|
81
96
|
end
|
82
97
|
|
@@ -93,7 +108,7 @@ module Karafka
|
|
93
108
|
|
94
109
|
info <<~MSG.tr("\n", ' ').strip!
|
95
110
|
[#{client.id}]
|
96
|
-
Pausing on topic #{topic}
|
111
|
+
Pausing on topic #{topic}-#{partition}
|
97
112
|
on #{offset ? "offset #{offset}" : 'the consecutive offset'}
|
98
113
|
MSG
|
99
114
|
end
|
@@ -107,7 +122,7 @@ module Karafka
|
|
107
122
|
client = event[:caller]
|
108
123
|
|
109
124
|
info <<~MSG.tr("\n", ' ').strip!
|
110
|
-
[#{client.id}] Resuming on topic #{topic}
|
125
|
+
[#{client.id}] Resuming on topic #{topic}-#{partition}
|
111
126
|
MSG
|
112
127
|
end
|
113
128
|
|
@@ -123,7 +138,7 @@ module Karafka
|
|
123
138
|
|
124
139
|
info <<~MSG.tr("\n", ' ').strip!
|
125
140
|
[#{consumer.id}] Retrying of #{consumer.class} after #{timeout} ms
|
126
|
-
on topic #{topic}
|
141
|
+
on topic #{topic}-#{partition} from offset #{offset}
|
127
142
|
MSG
|
128
143
|
end
|
129
144
|
|
@@ -138,7 +153,7 @@ module Karafka
|
|
138
153
|
|
139
154
|
info <<~MSG.tr("\n", ' ').strip!
|
140
155
|
[#{consumer.id}] Seeking from #{consumer.class}
|
141
|
-
on topic #{topic}
|
156
|
+
on topic #{topic}-#{partition} to offset #{seek_offset}
|
142
157
|
MSG
|
143
158
|
end
|
144
159
|
|
@@ -147,7 +162,8 @@ module Karafka
|
|
147
162
|
#
|
148
163
|
# @param event [Karafka::Core::Monitoring::Event] event details including payload
|
149
164
|
def on_process_notice_signal(event)
|
150
|
-
|
165
|
+
server_id = Karafka::Server.id
|
166
|
+
info "[#{server_id}] Received #{event[:signal]} system signal"
|
151
167
|
|
152
168
|
# We print backtrace only for ttin
|
153
169
|
return unless event[:signal] == :SIGTTIN
|
@@ -168,38 +184,76 @@ module Karafka
|
|
168
184
|
|
169
185
|
# Logs info that we're running Karafka app.
|
170
186
|
#
|
171
|
-
# @param
|
172
|
-
def on_app_running(
|
173
|
-
|
174
|
-
|
187
|
+
# @param event [Karafka::Core::Monitoring::Event] event details including payload
|
188
|
+
def on_app_running(event)
|
189
|
+
server_id = event[:server_id]
|
190
|
+
|
191
|
+
info "[#{server_id}] Running in #{RUBY_DESCRIPTION}"
|
192
|
+
info "[#{server_id}] Running Karafka #{Karafka::VERSION} server"
|
175
193
|
|
176
194
|
return if Karafka.pro?
|
177
195
|
|
178
|
-
info
|
196
|
+
info "[#{server_id}] See LICENSE and the LGPL-3.0 for licensing details"
|
179
197
|
end
|
180
198
|
|
181
|
-
# @param
|
182
|
-
def on_app_quieting(
|
183
|
-
info
|
199
|
+
# @param event [Karafka::Core::Monitoring::Event] event details including payload
|
200
|
+
def on_app_quieting(event)
|
201
|
+
info "[#{event[:server_id]}] Switching to quiet mode. New messages will not be processed"
|
184
202
|
end
|
185
203
|
|
186
|
-
# @param
|
187
|
-
def on_app_quiet(
|
188
|
-
info
|
204
|
+
# @param event [Karafka::Core::Monitoring::Event] event details including payload
|
205
|
+
def on_app_quiet(event)
|
206
|
+
info "[#{event[:server_id]}] Reached quiet mode. No messages will be processed anymore"
|
189
207
|
end
|
190
208
|
|
191
209
|
# Logs info that we're going to stop the Karafka server.
|
192
210
|
#
|
193
|
-
# @param
|
194
|
-
def on_app_stopping(
|
195
|
-
info
|
211
|
+
# @param event [Karafka::Core::Monitoring::Event] event details including payload
|
212
|
+
def on_app_stopping(event)
|
213
|
+
info "[#{event[:server_id]}] Stopping Karafka server"
|
196
214
|
end
|
197
215
|
|
198
216
|
# Logs info that we stopped the Karafka server.
|
199
217
|
#
|
200
|
-
# @param
|
201
|
-
def on_app_stopped(
|
202
|
-
info
|
218
|
+
# @param event [Karafka::Core::Monitoring::Event] event details including payload
|
219
|
+
def on_app_stopped(event)
|
220
|
+
info "[#{event[:server_id]}] Stopped Karafka server"
|
221
|
+
end
|
222
|
+
|
223
|
+
# Logs info about partitions we have lost
|
224
|
+
#
|
225
|
+
# @param event [Karafka::Core::Monitoring::Event] event details with revoked partitions
|
226
|
+
def on_rebalance_partitions_revoked(event)
|
227
|
+
revoked_partitions = event[:tpl].to_h.transform_values { |part| part.map(&:partition) }
|
228
|
+
group_id = event[:consumer_group_id]
|
229
|
+
client_id = event[:client_id]
|
230
|
+
group_prefix = "[#{client_id}] Group #{group_id} rebalance"
|
231
|
+
|
232
|
+
if revoked_partitions.empty?
|
233
|
+
info "#{group_prefix}: No partitions revoked"
|
234
|
+
else
|
235
|
+
revoked_partitions.each do |topic, partitions|
|
236
|
+
info "#{group_prefix}: #{topic}-[#{partitions.join(',')}] revoked"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Logs info about partitions that we've gained
|
242
|
+
#
|
243
|
+
# @param event [Karafka::Core::Monitoring::Event] event details with assigned partitions
|
244
|
+
def on_rebalance_partitions_assigned(event)
|
245
|
+
assigned_partitions = event[:tpl].to_h.transform_values { |part| part.map(&:partition) }
|
246
|
+
group_id = event[:consumer_group_id]
|
247
|
+
client_id = event[:client_id]
|
248
|
+
group_prefix = "[#{client_id}] Group #{group_id} rebalance"
|
249
|
+
|
250
|
+
if assigned_partitions.empty?
|
251
|
+
info "#{group_prefix}: No partitions assigned"
|
252
|
+
else
|
253
|
+
assigned_partitions.each do |topic, partitions|
|
254
|
+
info "#{group_prefix}: #{topic}-[#{partitions.join(',')}] assigned"
|
255
|
+
end
|
256
|
+
end
|
203
257
|
end
|
204
258
|
|
205
259
|
# Logs info when we have dispatched a message the the DLQ
|
@@ -215,7 +269,7 @@ module Karafka
|
|
215
269
|
|
216
270
|
info <<~MSG.tr("\n", ' ').strip!
|
217
271
|
[#{consumer.id}] Dispatched message #{offset}
|
218
|
-
from #{topic}
|
272
|
+
from #{topic}-#{partition}
|
219
273
|
to DLQ topic: #{dlq_topic}
|
220
274
|
MSG
|
221
275
|
end
|
@@ -234,7 +288,7 @@ module Karafka
|
|
234
288
|
info <<~MSG.tr("\n", ' ').strip!
|
235
289
|
[#{consumer.id}] Throttled and will resume
|
236
290
|
from message #{offset}
|
237
|
-
on #{topic}
|
291
|
+
on #{topic}-#{partition}
|
238
292
|
MSG
|
239
293
|
end
|
240
294
|
|
@@ -249,7 +303,7 @@ module Karafka
|
|
249
303
|
|
250
304
|
info <<~MSG.tr("\n", ' ').strip!
|
251
305
|
[#{consumer.id}] Post-filtering seeking to message #{offset}
|
252
|
-
on #{topic}
|
306
|
+
on #{topic}-#{partition}
|
253
307
|
MSG
|
254
308
|
end
|
255
309
|
|
@@ -371,9 +425,18 @@ module Karafka
|
|
371
425
|
when 'connection.client.unsubscribe.error'
|
372
426
|
error "Client unsubscribe error occurred: #{error}"
|
373
427
|
error details
|
428
|
+
when 'parallel_segments.reducer.error'
|
429
|
+
error "Parallel segments reducer error occurred: #{error}"
|
430
|
+
error details
|
431
|
+
when 'parallel_segments.partitioner.error'
|
432
|
+
error "Parallel segments partitioner error occurred: #{error}"
|
433
|
+
error details
|
434
|
+
when 'virtual_partitions.partitioner.error'
|
435
|
+
error "Virtual partitions partitioner error occurred: #{error}"
|
436
|
+
error details
|
374
437
|
# This handles any custom errors coming from places like Web-UI, etc
|
375
438
|
else
|
376
|
-
error "#{type} error occurred: #{error}"
|
439
|
+
error "#{type} error occurred: #{error.class} - #{error}"
|
377
440
|
error details
|
378
441
|
end
|
379
442
|
end
|
@@ -4,6 +4,10 @@ module Karafka
|
|
4
4
|
module Instrumentation
|
5
5
|
# Listener that sets a proc title with a nice descriptive value
|
6
6
|
class ProctitleListener
|
7
|
+
include Helpers::ConfigImporter.new(
|
8
|
+
client_id: %i[client_id]
|
9
|
+
)
|
10
|
+
|
7
11
|
Status::STATES.each_key do |state|
|
8
12
|
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
9
13
|
# Updates proc title to an appropriate state
|
@@ -19,7 +23,7 @@ module Karafka
|
|
19
23
|
# @param status [String] any status we want to set
|
20
24
|
def setproctitle(status)
|
21
25
|
::Process.setproctitle(
|
22
|
-
"karafka #{
|
26
|
+
"karafka #{client_id} (#{status})"
|
23
27
|
)
|
24
28
|
end
|
25
29
|
end
|
@@ -131,11 +131,11 @@ module Karafka
|
|
131
131
|
tags = consumer_tags(consumer)
|
132
132
|
tags.concat(default_tags)
|
133
133
|
|
134
|
-
count('consumer.messages', messages.
|
134
|
+
count('consumer.messages', messages.size, tags: tags)
|
135
135
|
count('consumer.batches', 1, tags: tags)
|
136
136
|
gauge('consumer.offset', metadata.last_offset, tags: tags)
|
137
137
|
histogram('consumer.consumed.time_taken', event[:time], tags: tags)
|
138
|
-
histogram('consumer.batch_size', messages.
|
138
|
+
histogram('consumer.batch_size', messages.size, tags: tags)
|
139
139
|
histogram('consumer.processing_lag', metadata.processing_lag, tags: tags)
|
140
140
|
histogram('consumer.consumption_lag', metadata.consumption_lag, tags: tags)
|
141
141
|
end
|