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.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +3 -0
  3. data/.github/workflows/ci.yml +58 -14
  4. data/.github/workflows/push.yml +36 -0
  5. data/.github/workflows/verify-action-pins.yml +16 -0
  6. data/.ruby-version +1 -1
  7. data/CHANGELOG.md +60 -0
  8. data/Gemfile +2 -2
  9. data/Gemfile.lock +69 -50
  10. data/LICENSE-COMM +2 -2
  11. data/README.md +1 -1
  12. data/Rakefile +4 -0
  13. data/bin/clean_kafka +43 -0
  14. data/bin/integrations +19 -6
  15. data/bin/rspecs +15 -3
  16. data/bin/verify_kafka_warnings +35 -0
  17. data/bin/verify_topics_naming +27 -0
  18. data/config/locales/errors.yml +3 -0
  19. data/config/locales/pro_errors.yml +13 -2
  20. data/docker-compose.yml +1 -1
  21. data/examples/payloads/json/enrollment_event.json +579 -0
  22. data/examples/payloads/json/ingestion_event.json +30 -0
  23. data/examples/payloads/json/transaction_event.json +17 -0
  24. data/examples/payloads/json/user_event.json +11 -0
  25. data/karafka.gemspec +3 -8
  26. data/lib/karafka/active_job/current_attributes.rb +1 -1
  27. data/lib/karafka/admin/acl.rb +5 -1
  28. data/lib/karafka/admin/configs.rb +5 -1
  29. data/lib/karafka/admin.rb +69 -34
  30. data/lib/karafka/base_consumer.rb +17 -8
  31. data/lib/karafka/cli/base.rb +8 -2
  32. data/lib/karafka/cli/topics/align.rb +7 -4
  33. data/lib/karafka/cli/topics/base.rb +17 -0
  34. data/lib/karafka/cli/topics/create.rb +9 -7
  35. data/lib/karafka/cli/topics/delete.rb +4 -2
  36. data/lib/karafka/cli/topics/help.rb +39 -0
  37. data/lib/karafka/cli/topics/repartition.rb +4 -2
  38. data/lib/karafka/cli/topics.rb +10 -3
  39. data/lib/karafka/cli.rb +2 -0
  40. data/lib/karafka/connection/client.rb +30 -9
  41. data/lib/karafka/connection/listener.rb +24 -12
  42. data/lib/karafka/connection/messages_buffer.rb +1 -1
  43. data/lib/karafka/connection/proxy.rb +3 -0
  44. data/lib/karafka/constraints.rb +3 -3
  45. data/lib/karafka/contracts/config.rb +3 -0
  46. data/lib/karafka/contracts/topic.rb +1 -1
  47. data/lib/karafka/errors.rb +46 -2
  48. data/lib/karafka/helpers/async.rb +3 -1
  49. data/lib/karafka/instrumentation/callbacks/rebalance.rb +5 -1
  50. data/lib/karafka/instrumentation/logger_listener.rb +86 -23
  51. data/lib/karafka/instrumentation/proctitle_listener.rb +5 -1
  52. data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +2 -2
  53. data/lib/karafka/messages/builders/batch_metadata.rb +1 -1
  54. data/lib/karafka/pro/cleaner.rb +8 -0
  55. data/lib/karafka/pro/cli/parallel_segments/base.rb +89 -0
  56. data/lib/karafka/pro/cli/parallel_segments/collapse.rb +164 -0
  57. data/lib/karafka/pro/cli/parallel_segments/distribute.rb +164 -0
  58. data/lib/karafka/pro/cli/parallel_segments.rb +60 -0
  59. data/lib/karafka/pro/connection/manager.rb +5 -8
  60. data/lib/karafka/pro/encryption.rb +8 -0
  61. data/lib/karafka/pro/instrumentation/performance_tracker.rb +1 -1
  62. data/lib/karafka/pro/iterator/expander.rb +5 -3
  63. data/lib/karafka/pro/iterator/tpl_builder.rb +23 -0
  64. data/lib/karafka/pro/loader.rb +10 -0
  65. data/lib/karafka/pro/processing/coordinator.rb +4 -1
  66. data/lib/karafka/pro/processing/coordinators/errors_tracker.rb +27 -3
  67. data/lib/karafka/pro/processing/coordinators/filters_applier.rb +11 -0
  68. data/lib/karafka/pro/processing/filters/base.rb +10 -2
  69. data/lib/karafka/pro/processing/filters/expirer.rb +5 -0
  70. data/lib/karafka/pro/processing/filters/inline_insights_delayer.rb +2 -2
  71. data/lib/karafka/pro/processing/filters/virtual_limiter.rb +5 -0
  72. data/lib/karafka/pro/processing/parallel_segments/filters/base.rb +73 -0
  73. data/lib/karafka/pro/processing/parallel_segments/filters/default.rb +85 -0
  74. data/lib/karafka/pro/processing/parallel_segments/filters/mom.rb +66 -0
  75. data/lib/karafka/pro/processing/partitioner.rb +1 -13
  76. data/lib/karafka/pro/processing/piping/consumer.rb +13 -13
  77. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +1 -1
  78. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +1 -1
  79. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +1 -1
  80. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +1 -1
  81. data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom_vp.rb +1 -1
  82. data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +1 -1
  83. data/lib/karafka/pro/processing/strategies/default.rb +36 -8
  84. data/lib/karafka/pro/processing/strategies/dlq/default.rb +14 -10
  85. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj.rb +1 -1
  86. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +1 -1
  87. data/lib/karafka/pro/processing/strategies/dlq/lrj.rb +3 -1
  88. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +1 -1
  89. data/lib/karafka/pro/processing/strategies/ftr/default.rb +1 -1
  90. data/lib/karafka/pro/processing/strategies/lrj/default.rb +4 -1
  91. data/lib/karafka/pro/processing/strategies/lrj/ftr.rb +1 -1
  92. data/lib/karafka/pro/processing/strategies/lrj/ftr_mom.rb +1 -1
  93. data/lib/karafka/pro/processing/strategies/lrj/mom.rb +1 -1
  94. data/lib/karafka/pro/processing/virtual_partitions/distributors/balanced.rb +50 -0
  95. data/lib/karafka/pro/processing/virtual_partitions/distributors/base.rb +29 -0
  96. data/lib/karafka/pro/processing/virtual_partitions/distributors/consistent.rb +27 -0
  97. data/lib/karafka/pro/recurring_tasks/contracts/config.rb +8 -4
  98. data/lib/karafka/pro/recurring_tasks/dispatcher.rb +3 -3
  99. data/lib/karafka/pro/recurring_tasks/setup/config.rb +7 -2
  100. data/lib/karafka/pro/recurring_tasks.rb +13 -0
  101. data/lib/karafka/pro/routing/features/dead_letter_queue/topic.rb +1 -1
  102. data/lib/karafka/pro/routing/features/multiplexing/config.rb +1 -0
  103. data/lib/karafka/pro/routing/features/multiplexing/contracts/topic.rb +17 -0
  104. data/lib/karafka/pro/routing/features/multiplexing/proxy.rb +5 -2
  105. data/lib/karafka/pro/routing/features/multiplexing/subscription_group.rb +8 -1
  106. data/lib/karafka/pro/routing/features/parallel_segments/builder.rb +47 -0
  107. data/lib/karafka/pro/routing/features/parallel_segments/config.rb +27 -0
  108. data/lib/karafka/pro/routing/features/parallel_segments/consumer_group.rb +83 -0
  109. data/lib/karafka/pro/routing/features/parallel_segments/contracts/consumer_group.rb +49 -0
  110. data/lib/karafka/pro/routing/features/parallel_segments/topic.rb +43 -0
  111. data/lib/karafka/pro/routing/features/parallel_segments.rb +24 -0
  112. data/lib/karafka/pro/routing/features/patterns/pattern.rb +1 -1
  113. data/lib/karafka/pro/routing/features/recurring_tasks/builder.rb +2 -2
  114. data/lib/karafka/pro/routing/features/scheduled_messages/builder.rb +10 -6
  115. data/lib/karafka/pro/routing/features/virtual_partitions/config.rb +20 -2
  116. data/lib/karafka/pro/routing/features/virtual_partitions/contracts/topic.rb +1 -0
  117. data/lib/karafka/pro/routing/features/virtual_partitions/topic.rb +8 -2
  118. data/lib/karafka/pro/scheduled_messages/consumer.rb +19 -21
  119. data/lib/karafka/pro/scheduled_messages/daily_buffer.rb +9 -6
  120. data/lib/karafka/pro/scheduled_messages/deserializers/headers.rb +7 -1
  121. data/lib/karafka/pro/scheduled_messages/max_epoch.rb +15 -6
  122. data/lib/karafka/pro/scheduled_messages.rb +13 -0
  123. data/lib/karafka/processing/coordinators_buffer.rb +1 -0
  124. data/lib/karafka/processing/strategies/default.rb +4 -4
  125. data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +1 -0
  126. data/lib/karafka/routing/subscription_group.rb +1 -1
  127. data/lib/karafka/runner.rb +7 -1
  128. data/lib/karafka/server.rb +19 -19
  129. data/lib/karafka/setup/attributes_map.rb +2 -0
  130. data/lib/karafka/setup/config.rb +22 -1
  131. data/lib/karafka/setup/defaults_injector.rb +26 -1
  132. data/lib/karafka/status.rb +6 -1
  133. data/lib/karafka/swarm/node.rb +31 -0
  134. data/lib/karafka/swarm/supervisor.rb +4 -0
  135. data/lib/karafka/templates/karafka.rb.erb +14 -1
  136. data/lib/karafka/version.rb +1 -1
  137. data/lib/karafka.rb +17 -9
  138. data/renovate.json +14 -2
  139. metadata +40 -40
  140. checksums.yaml.gz.sig +0 -0
  141. data/certs/cert.pem +0 -26
  142. data.tar.gz.sig +0 -0
  143. 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
- @jobs_builder = proc_config.jobs_builder
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("karafka.listener##{@subscription_group.id}")
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 = ::Karafka::App.config.internal.connection.reset_backoff / 10_000.0
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 << @jobs_builder.revoked(executor)
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 = @jobs_builder.shutdown(executor)
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 << @jobs_builder.eofed(executor)
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 << @jobs_builder.idle(executor)
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 << @jobs_builder.consume(executor, partition_messages)
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 << @jobs_builder.periodic(executor)
466
+ jobs << jobs_builder.periodic(executor)
455
467
  end
456
468
  end
457
469
  end
@@ -45,7 +45,7 @@ module Karafka
45
45
  last_polled_at = raw_messages_buffer.last_polled_at
46
46
 
47
47
  raw_messages_buffer.each do |topic, partition, messages, eof|
48
- @size += messages.count
48
+ @size += messages.size
49
49
 
50
50
  ktopic = @subscription_group.topics.find(topic)
51
51
 
@@ -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
@@ -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.9.0.rc3 because we expect 0.9.0.rc3 or higher
19
- return if version(Karafka::Web::VERSION) >= version('0.9.0.rc3')
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.9.0 is not compatible with this karafka version'
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.count
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
 
@@ -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
- def async_call(thread_name)
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
- def initialize(subscription_group)
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
- listener = event[:caller]
34
- debug "[#{listener.id}] Polling messages..."
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
- listener = event[:caller]
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 = "[#{listener.id}] Polled #{messages_count} messages in #{time}ms"
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
- info "Received #{event[:signal]} system signal"
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 _event [Karafka::Core::Monitoring::Event] event details including payload
172
- def on_app_running(_event)
173
- info "Running in #{RUBY_DESCRIPTION}"
174
- info "Running Karafka #{Karafka::VERSION} server"
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 'See LICENSE and the LGPL-3.0 for licensing details'
196
+ info "[#{server_id}] See LICENSE and the LGPL-3.0 for licensing details"
179
197
  end
180
198
 
181
- # @param _event [Karafka::Core::Monitoring::Event] event details including payload
182
- def on_app_quieting(_event)
183
- info 'Switching to quiet mode. New messages will not be processed'
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 _event [Karafka::Core::Monitoring::Event] event details including payload
187
- def on_app_quiet(_event)
188
- info 'Reached quiet mode. No messages will be processed anymore'
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 _event [Karafka::Core::Monitoring::Event] event details including payload
194
- def on_app_stopping(_event)
195
- info 'Stopping Karafka server'
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 _event [Karafka::Core::Monitoring::Event] event details including payload
201
- def on_app_stopped(_event)
202
- info 'Stopped Karafka server'
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 #{Karafka::App.config.client_id} (#{status})"
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.count, tags: tags)
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.count, tags: tags)
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.count,
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,
@@ -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