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.
Files changed (159) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +3 -0
  3. data/.github/workflows/ci.yml +59 -15
  4. data/.github/workflows/push.yml +35 -0
  5. data/.github/workflows/verify-action-pins.yml +16 -0
  6. data/.ruby-version +1 -1
  7. data/CHANGELOG.md +75 -0
  8. data/Gemfile +2 -2
  9. data/Gemfile.lock +72 -53
  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 +20 -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 +5 -1
  19. data/config/locales/pro_errors.yml +13 -2
  20. data/docker-compose.yml +1 -1
  21. data/examples/payloads/avro/.gitkeep +0 -0
  22. data/examples/payloads/json/sample_set_01/enrollment_event.json +579 -0
  23. data/examples/payloads/json/sample_set_01/ingestion_event.json +30 -0
  24. data/examples/payloads/json/sample_set_01/transaction_event.json +17 -0
  25. data/examples/payloads/json/sample_set_01/user_event.json +11 -0
  26. data/karafka.gemspec +3 -8
  27. data/lib/karafka/active_job/current_attributes.rb +1 -1
  28. data/lib/karafka/active_job/job_extensions.rb +4 -1
  29. data/lib/karafka/admin/acl.rb +5 -1
  30. data/lib/karafka/admin/configs.rb +5 -1
  31. data/lib/karafka/admin.rb +89 -42
  32. data/lib/karafka/base_consumer.rb +17 -8
  33. data/lib/karafka/cli/base.rb +8 -2
  34. data/lib/karafka/cli/topics/align.rb +7 -4
  35. data/lib/karafka/cli/topics/base.rb +17 -0
  36. data/lib/karafka/cli/topics/create.rb +9 -7
  37. data/lib/karafka/cli/topics/delete.rb +4 -2
  38. data/lib/karafka/cli/topics/help.rb +39 -0
  39. data/lib/karafka/cli/topics/repartition.rb +4 -2
  40. data/lib/karafka/cli/topics.rb +10 -3
  41. data/lib/karafka/cli.rb +2 -0
  42. data/lib/karafka/connection/client.rb +39 -9
  43. data/lib/karafka/connection/listener.rb +24 -12
  44. data/lib/karafka/connection/messages_buffer.rb +1 -1
  45. data/lib/karafka/connection/proxy.rb +4 -1
  46. data/lib/karafka/constraints.rb +3 -3
  47. data/lib/karafka/contracts/base.rb +3 -2
  48. data/lib/karafka/contracts/config.rb +5 -1
  49. data/lib/karafka/contracts/topic.rb +1 -1
  50. data/lib/karafka/errors.rb +46 -2
  51. data/lib/karafka/helpers/async.rb +3 -1
  52. data/lib/karafka/helpers/interval_runner.rb +8 -0
  53. data/lib/karafka/instrumentation/callbacks/rebalance.rb +5 -1
  54. data/lib/karafka/instrumentation/logger_listener.rb +95 -32
  55. data/lib/karafka/instrumentation/proctitle_listener.rb +5 -1
  56. data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +2 -2
  57. data/lib/karafka/instrumentation/vendors/kubernetes/base_listener.rb +17 -2
  58. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +29 -6
  59. data/lib/karafka/instrumentation/vendors/kubernetes/swarm_liveness_listener.rb +9 -0
  60. data/lib/karafka/messages/builders/batch_metadata.rb +1 -1
  61. data/lib/karafka/pro/cleaner.rb +8 -0
  62. data/lib/karafka/pro/cli/parallel_segments/base.rb +89 -0
  63. data/lib/karafka/pro/cli/parallel_segments/collapse.rb +164 -0
  64. data/lib/karafka/pro/cli/parallel_segments/distribute.rb +164 -0
  65. data/lib/karafka/pro/cli/parallel_segments.rb +60 -0
  66. data/lib/karafka/pro/connection/manager.rb +5 -8
  67. data/lib/karafka/pro/encryption.rb +12 -1
  68. data/lib/karafka/pro/instrumentation/performance_tracker.rb +1 -1
  69. data/lib/karafka/pro/iterator/expander.rb +5 -3
  70. data/lib/karafka/pro/iterator/tpl_builder.rb +23 -0
  71. data/lib/karafka/pro/loader.rb +10 -0
  72. data/lib/karafka/pro/processing/coordinator.rb +4 -1
  73. data/lib/karafka/pro/processing/coordinators/errors_tracker.rb +32 -3
  74. data/lib/karafka/pro/processing/coordinators/filters_applier.rb +11 -0
  75. data/lib/karafka/pro/processing/filters/base.rb +10 -2
  76. data/lib/karafka/pro/processing/filters/expirer.rb +5 -0
  77. data/lib/karafka/pro/processing/filters/inline_insights_delayer.rb +2 -2
  78. data/lib/karafka/pro/processing/filters/virtual_limiter.rb +5 -0
  79. data/lib/karafka/pro/processing/parallel_segments/filters/base.rb +73 -0
  80. data/lib/karafka/pro/processing/parallel_segments/filters/default.rb +85 -0
  81. data/lib/karafka/pro/processing/parallel_segments/filters/mom.rb +66 -0
  82. data/lib/karafka/pro/processing/partitioner.rb +1 -13
  83. data/lib/karafka/pro/processing/piping/consumer.rb +13 -13
  84. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +1 -1
  85. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +1 -1
  86. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +1 -1
  87. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +1 -1
  88. data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom_vp.rb +1 -1
  89. data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +1 -1
  90. data/lib/karafka/pro/processing/strategies/default.rb +36 -8
  91. data/lib/karafka/pro/processing/strategies/dlq/default.rb +15 -10
  92. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj.rb +1 -1
  93. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +1 -1
  94. data/lib/karafka/pro/processing/strategies/dlq/lrj.rb +3 -1
  95. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +1 -1
  96. data/lib/karafka/pro/processing/strategies/ftr/default.rb +1 -1
  97. data/lib/karafka/pro/processing/strategies/lrj/default.rb +4 -1
  98. data/lib/karafka/pro/processing/strategies/lrj/ftr.rb +1 -1
  99. data/lib/karafka/pro/processing/strategies/lrj/ftr_mom.rb +1 -1
  100. data/lib/karafka/pro/processing/strategies/lrj/mom.rb +1 -1
  101. data/lib/karafka/pro/processing/virtual_partitions/distributors/balanced.rb +50 -0
  102. data/lib/karafka/pro/processing/virtual_partitions/distributors/base.rb +29 -0
  103. data/lib/karafka/pro/processing/virtual_partitions/distributors/consistent.rb +27 -0
  104. data/lib/karafka/pro/recurring_tasks/contracts/config.rb +8 -4
  105. data/lib/karafka/pro/recurring_tasks/dispatcher.rb +3 -3
  106. data/lib/karafka/pro/recurring_tasks/setup/config.rb +7 -2
  107. data/lib/karafka/pro/recurring_tasks.rb +21 -2
  108. data/lib/karafka/pro/routing/features/dead_letter_queue/topic.rb +1 -1
  109. data/lib/karafka/pro/routing/features/multiplexing/config.rb +1 -0
  110. data/lib/karafka/pro/routing/features/multiplexing/contracts/topic.rb +17 -0
  111. data/lib/karafka/pro/routing/features/multiplexing/proxy.rb +5 -2
  112. data/lib/karafka/pro/routing/features/multiplexing/subscription_group.rb +8 -1
  113. data/lib/karafka/pro/routing/features/parallel_segments/builder.rb +47 -0
  114. data/lib/karafka/pro/routing/features/parallel_segments/config.rb +27 -0
  115. data/lib/karafka/pro/routing/features/parallel_segments/consumer_group.rb +83 -0
  116. data/lib/karafka/pro/routing/features/parallel_segments/contracts/consumer_group.rb +49 -0
  117. data/lib/karafka/pro/routing/features/parallel_segments/topic.rb +43 -0
  118. data/lib/karafka/pro/routing/features/parallel_segments.rb +24 -0
  119. data/lib/karafka/pro/routing/features/patterns/pattern.rb +1 -1
  120. data/lib/karafka/pro/routing/features/recurring_tasks/builder.rb +2 -2
  121. data/lib/karafka/pro/routing/features/scheduled_messages/builder.rb +10 -6
  122. data/lib/karafka/pro/routing/features/swarm/contracts/routing.rb +3 -2
  123. data/lib/karafka/pro/routing/features/swarm.rb +4 -1
  124. data/lib/karafka/pro/routing/features/virtual_partitions/config.rb +20 -2
  125. data/lib/karafka/pro/routing/features/virtual_partitions/contracts/topic.rb +1 -0
  126. data/lib/karafka/pro/routing/features/virtual_partitions/topic.rb +8 -2
  127. data/lib/karafka/pro/scheduled_messages/consumer.rb +61 -26
  128. data/lib/karafka/pro/scheduled_messages/daily_buffer.rb +9 -6
  129. data/lib/karafka/pro/scheduled_messages/deserializers/headers.rb +7 -1
  130. data/lib/karafka/pro/scheduled_messages/dispatcher.rb +2 -1
  131. data/lib/karafka/pro/scheduled_messages/max_epoch.rb +15 -6
  132. data/lib/karafka/pro/scheduled_messages/proxy.rb +15 -3
  133. data/lib/karafka/pro/scheduled_messages/serializer.rb +2 -4
  134. data/lib/karafka/pro/scheduled_messages/state.rb +20 -23
  135. data/lib/karafka/pro/scheduled_messages/tracker.rb +34 -8
  136. data/lib/karafka/pro/scheduled_messages.rb +17 -1
  137. data/lib/karafka/processing/coordinators_buffer.rb +1 -0
  138. data/lib/karafka/processing/strategies/default.rb +4 -4
  139. data/lib/karafka/routing/builder.rb +12 -3
  140. data/lib/karafka/routing/features/base/expander.rb +8 -2
  141. data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +1 -0
  142. data/lib/karafka/routing/subscription_group.rb +1 -1
  143. data/lib/karafka/runner.rb +7 -1
  144. data/lib/karafka/server.rb +21 -18
  145. data/lib/karafka/setup/attributes_map.rb +2 -0
  146. data/lib/karafka/setup/config.rb +40 -7
  147. data/lib/karafka/setup/defaults_injector.rb +26 -1
  148. data/lib/karafka/status.rb +6 -1
  149. data/lib/karafka/swarm/node.rb +31 -0
  150. data/lib/karafka/swarm/supervisor.rb +9 -2
  151. data/lib/karafka/templates/karafka.rb.erb +14 -1
  152. data/lib/karafka/version.rb +1 -1
  153. data/lib/karafka.rb +17 -9
  154. data/renovate.json +14 -2
  155. metadata +41 -40
  156. checksums.yaml.gz.sig +0 -0
  157. data/certs/cert.pem +0 -26
  158. data.tar.gz.sig +0 -0
  159. 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
 
@@ -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 [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
@@ -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
 
@@ -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(:max_attempts) { |val| val.is_a?(Integer) && val.positive? }
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.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
 
@@ -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
- 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
@@ -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}/#{partition} started"
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}/#{partition} finished in #{time} ms
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}/#{partition}
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}/#{partition}
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}/#{partition} from offset #{offset}
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}/#{partition} to offset #{seek_offset}
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
- 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}: #{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}/#{partition}
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}/#{partition}
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}/#{partition}
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 #{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