karafka 2.4.18 → 2.5.0.beta2
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 +58 -14
- data/.github/workflows/push.yml +36 -0
- data/.github/workflows/verify-action-pins.yml +16 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +60 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +69 -50
- data/LICENSE-COMM +2 -2
- data/README.md +1 -1
- data/Rakefile +4 -0
- data/bin/clean_kafka +43 -0
- data/bin/integrations +19 -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 +3 -0
- data/config/locales/pro_errors.yml +13 -2
- data/docker-compose.yml +1 -1
- data/examples/payloads/json/enrollment_event.json +579 -0
- data/examples/payloads/json/ingestion_event.json +30 -0
- data/examples/payloads/json/transaction_event.json +17 -0
- data/examples/payloads/json/user_event.json +11 -0
- data/karafka.gemspec +3 -8
- data/lib/karafka/active_job/current_attributes.rb +1 -1
- data/lib/karafka/admin/acl.rb +5 -1
- data/lib/karafka/admin/configs.rb +5 -1
- data/lib/karafka/admin.rb +69 -34
- 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 +30 -9
- data/lib/karafka/connection/listener.rb +24 -12
- data/lib/karafka/connection/messages_buffer.rb +1 -1
- data/lib/karafka/connection/proxy.rb +3 -0
- data/lib/karafka/constraints.rb +3 -3
- data/lib/karafka/contracts/config.rb +3 -0
- 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/instrumentation/callbacks/rebalance.rb +5 -1
- data/lib/karafka/instrumentation/logger_listener.rb +86 -23
- data/lib/karafka/instrumentation/proctitle_listener.rb +5 -1
- data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +2 -2
- 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 +8 -0
- 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 +27 -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 +14 -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 +13 -0
- 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/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 +19 -21
- 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/max_epoch.rb +15 -6
- data/lib/karafka/pro/scheduled_messages.rb +13 -0
- data/lib/karafka/processing/coordinators_buffer.rb +1 -0
- data/lib/karafka/processing/strategies/default.rb +4 -4
- 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 +19 -19
- data/lib/karafka/setup/attributes_map.rb +2 -0
- data/lib/karafka/setup/config.rb +22 -1
- 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 +4 -0
- 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 +40 -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
|
@@ -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
|
|
@@ -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? }
|
@@ -81,6 +82,7 @@ module Karafka
|
|
81
82
|
required(:manager) { |val| !val.nil? }
|
82
83
|
required(:conductor) { |val| !val.nil? }
|
83
84
|
required(:reset_backoff) { |val| val.is_a?(Integer) && val >= 1_000 }
|
85
|
+
required(:listener_thread_priority) { |val| (-3..3).to_a.include?(val) }
|
84
86
|
|
85
87
|
nested(:proxy) do
|
86
88
|
nested(:commit) do
|
@@ -114,6 +116,7 @@ module Karafka
|
|
114
116
|
required(:jobs_queue_class) { |val| !val.nil? }
|
115
117
|
required(:scheduler_class) { |val| !val.nil? }
|
116
118
|
required(:coordinator_class) { |val| !val.nil? }
|
119
|
+
required(:errors_tracker_class) { |val| val.nil? || val.is_a?(Class) }
|
117
120
|
required(:partitioner_class) { |val| !val.nil? }
|
118
121
|
required(:strategy_selector) { |val| !val.nil? }
|
119
122
|
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
|
|
@@ -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
|
@@ -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}: Partition(s) #{partitions.join(', ')} of #{topic} 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}: Partition(s) #{partitions.join(', ')} of #{topic} assigned"
|
255
|
+
end
|
256
|
+
end
|
203
257
|
end
|
204
258
|
|
205
259
|
# Logs info when we have dispatched a message the the DLQ
|
@@ -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
|
@@ -18,7 +18,7 @@ module Karafka
|
|
18
18
|
# picked up for processing.
|
19
19
|
def call(messages, topic, partition, scheduled_at)
|
20
20
|
Karafka::Messages::BatchMetadata.new(
|
21
|
-
size: messages.
|
21
|
+
size: messages.size,
|
22
22
|
first_offset: messages.first&.offset || -1001,
|
23
23
|
last_offset: messages.last&.offset || -1001,
|
24
24
|
deserializers: topic.deserializers,
|
data/lib/karafka/pro/cleaner.rb
CHANGED
@@ -28,6 +28,14 @@ module Karafka
|
|
28
28
|
def post_setup(_config)
|
29
29
|
true
|
30
30
|
end
|
31
|
+
|
32
|
+
# This feature does not need any changes post-fork
|
33
|
+
#
|
34
|
+
# @param _config [Karafka::Core::Configurable::Node]
|
35
|
+
# @param _pre_fork_producer [WaterDrop::Producer]
|
36
|
+
def post_fork(_config, _pre_fork_producer)
|
37
|
+
true
|
38
|
+
end
|
31
39
|
end
|
32
40
|
end
|
33
41
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This code is part of Karafka Pro, a commercial component not licensed under LGPL.
|
4
|
+
# See LICENSE for details.
|
5
|
+
|
6
|
+
module Karafka
|
7
|
+
module Pro
|
8
|
+
module Cli
|
9
|
+
class ParallelSegments < Karafka::Cli::Base
|
10
|
+
# Base class for all the parallel segments related operations
|
11
|
+
class Base
|
12
|
+
include Helpers::Colorize
|
13
|
+
|
14
|
+
# @param options [Hash] cli flags options
|
15
|
+
def initialize(options)
|
16
|
+
@options = options
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# @return [Hash]
|
22
|
+
attr_reader :options
|
23
|
+
|
24
|
+
# Returns consumer groups for parallel segments with which we should be working
|
25
|
+
#
|
26
|
+
# @return [Hash<String, Array<Karafka::Routing::ConsumerGroup>>] hash with all parallel
|
27
|
+
# consumer groups as values and names of segments origin consumer group as the key.
|
28
|
+
def applicable_groups
|
29
|
+
requested_groups = options[:groups].dup || []
|
30
|
+
|
31
|
+
workable_groups = ::Karafka::App
|
32
|
+
.routes
|
33
|
+
.select(&:parallel_segments?)
|
34
|
+
.group_by(&:segment_origin)
|
35
|
+
|
36
|
+
# Use all if none provided
|
37
|
+
return workable_groups if requested_groups.empty?
|
38
|
+
|
39
|
+
applicable_groups = {}
|
40
|
+
|
41
|
+
requested_groups.each do |requested_group|
|
42
|
+
workable_group = workable_groups[requested_group]
|
43
|
+
|
44
|
+
if workable_group
|
45
|
+
requested_groups.delete(requested_group)
|
46
|
+
applicable_groups[requested_group] = workable_group
|
47
|
+
else
|
48
|
+
raise(
|
49
|
+
::Karafka::Errors::ConsumerGroupNotFoundError,
|
50
|
+
"Consumer group #{requested_group} was not found"
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
applicable_groups
|
56
|
+
end
|
57
|
+
|
58
|
+
# Collects the offsets for the segment origin consumer group and the parallel segments
|
59
|
+
# consumers groups. We use segment origin cg offsets as a baseline for the distribution
|
60
|
+
# and use existing (if any) parallel segments cgs offsets for validations.
|
61
|
+
#
|
62
|
+
# @param segment_origin [String] name of the origin consumer group
|
63
|
+
# @param segments [Array<Karafka::Routing::ConsumerGroup>]
|
64
|
+
# @return [Hash] fetched offsets for all the cg topics for all the consumer groups
|
65
|
+
def collect_offsets(segment_origin, segments)
|
66
|
+
topics_names = segments.first.topics.map(&:name)
|
67
|
+
consumer_groups = [segment_origin, segments.map(&:name)].flatten
|
68
|
+
|
69
|
+
consumer_groups_with_topics = consumer_groups
|
70
|
+
.map { |name| [name, topics_names] }
|
71
|
+
.to_h
|
72
|
+
|
73
|
+
lags_with_offsets = Karafka::Admin.read_lags_with_offsets(
|
74
|
+
consumer_groups_with_topics
|
75
|
+
)
|
76
|
+
|
77
|
+
lags_with_offsets.each do |_cg_name, topics|
|
78
|
+
topics.each do |_topic_name, partitions|
|
79
|
+
partitions.transform_values! { |details| details[:offset] }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
lags_with_offsets
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|