karafka 2.5.1 → 2.5.2

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 (151) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci_linux_ubuntu_x86_64_gnu.yml +3 -29
  3. data/.github/workflows/ci_macos_arm64.yml +1 -1
  4. data/.github/workflows/push.yml +2 -2
  5. data/.github/workflows/trigger-wiki-refresh.yml +1 -1
  6. data/.ruby-version +1 -1
  7. data/CHANGELOG.md +14 -4
  8. data/Gemfile +0 -2
  9. data/Gemfile.lock +30 -31
  10. data/bin/integrations +2 -1
  11. data/bin/rspecs +4 -0
  12. data/config/locales/errors.yml +6 -4
  13. data/config/locales/pro_errors.yml +5 -4
  14. data/docker-compose.yml +1 -1
  15. data/examples/payloads/json/sample_set_02/download.json +191 -0
  16. data/examples/payloads/json/sample_set_03/event_type_1.json +18 -0
  17. data/examples/payloads/json/sample_set_03/event_type_2.json +263 -0
  18. data/examples/payloads/json/sample_set_03/event_type_3.json +41 -0
  19. data/karafka.gemspec +1 -1
  20. data/lib/active_job/queue_adapters/karafka_adapter.rb +1 -1
  21. data/lib/karafka/active_job/consumer.rb +5 -1
  22. data/lib/karafka/active_job/current_attributes/job_wrapper.rb +45 -0
  23. data/lib/karafka/active_job/current_attributes/loading.rb +1 -1
  24. data/lib/karafka/active_job/current_attributes/persistence.rb +19 -7
  25. data/lib/karafka/active_job/current_attributes.rb +1 -0
  26. data/lib/karafka/active_job/deserializer.rb +61 -0
  27. data/lib/karafka/active_job/dispatcher.rb +32 -12
  28. data/lib/karafka/active_job/job_options_contract.rb +2 -4
  29. data/lib/karafka/admin/acl.rb +8 -4
  30. data/lib/karafka/admin/configs/config.rb +6 -4
  31. data/lib/karafka/admin/consumer_groups.rb +74 -4
  32. data/lib/karafka/admin/topics.rb +40 -7
  33. data/lib/karafka/admin.rb +13 -4
  34. data/lib/karafka/base_consumer.rb +5 -5
  35. data/lib/karafka/cli/base.rb +1 -1
  36. data/lib/karafka/cli/contracts/server.rb +2 -4
  37. data/lib/karafka/cli/install.rb +1 -1
  38. data/lib/karafka/cli/topics/align.rb +1 -1
  39. data/lib/karafka/cli/topics/repartition.rb +2 -2
  40. data/lib/karafka/connection/client.rb +12 -2
  41. data/lib/karafka/connection/listeners_batch.rb +2 -3
  42. data/lib/karafka/connection/proxy.rb +11 -7
  43. data/lib/karafka/env.rb +1 -2
  44. data/lib/karafka/helpers/interval_runner.rb +4 -1
  45. data/lib/karafka/instrumentation/assignments_tracker.rb +17 -0
  46. data/lib/karafka/instrumentation/monitor.rb +1 -1
  47. data/lib/karafka/instrumentation/notifications.rb +1 -0
  48. data/lib/karafka/instrumentation/vendors/appsignal/base.rb +2 -3
  49. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +2 -3
  50. data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +8 -9
  51. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +2 -3
  52. data/lib/karafka/messages/builders/batch_metadata.rb +1 -1
  53. data/lib/karafka/messages/builders/message.rb +1 -1
  54. data/lib/karafka/messages/messages.rb +2 -3
  55. data/lib/karafka/patches/rdkafka/bindings.rb +6 -6
  56. data/lib/karafka/patches/rdkafka/opaque.rb +1 -1
  57. data/lib/karafka/pro/active_job/dispatcher.rb +7 -3
  58. data/lib/karafka/pro/active_job/job_options_contract.rb +2 -4
  59. data/lib/karafka/pro/cleaner/messages/messages.rb +2 -3
  60. data/lib/karafka/pro/cli/contracts/server.rb +2 -4
  61. data/lib/karafka/pro/cli/parallel_segments/base.rb +1 -2
  62. data/lib/karafka/pro/cli/parallel_segments/collapse.rb +2 -2
  63. data/lib/karafka/pro/cli/parallel_segments/distribute.rb +2 -2
  64. data/lib/karafka/pro/connection/manager.rb +2 -2
  65. data/lib/karafka/pro/encryption/contracts/config.rb +4 -6
  66. data/lib/karafka/pro/encryption/messages/parser.rb +3 -3
  67. data/lib/karafka/pro/instrumentation/performance_tracker.rb +3 -3
  68. data/lib/karafka/pro/iterator/expander.rb +1 -1
  69. data/lib/karafka/pro/iterator/tpl_builder.rb +1 -1
  70. data/lib/karafka/pro/iterator.rb +2 -2
  71. data/lib/karafka/pro/processing/coordinators/errors_tracker.rb +2 -3
  72. data/lib/karafka/pro/processing/coordinators/filters_applier.rb +3 -3
  73. data/lib/karafka/pro/processing/filters/delayer.rb +1 -1
  74. data/lib/karafka/pro/processing/filters/expirer.rb +1 -1
  75. data/lib/karafka/pro/processing/filters/throttler.rb +1 -1
  76. data/lib/karafka/pro/processing/schedulers/default.rb +2 -4
  77. data/lib/karafka/pro/processing/strategies/lrj/default.rb +2 -4
  78. data/lib/karafka/pro/processing/strategies/vp/default.rb +2 -4
  79. data/lib/karafka/pro/processing/subscription_groups_coordinator.rb +2 -3
  80. data/lib/karafka/pro/recurring_tasks/contracts/config.rb +2 -4
  81. data/lib/karafka/pro/recurring_tasks/contracts/task.rb +2 -4
  82. data/lib/karafka/pro/recurring_tasks/dispatcher.rb +6 -5
  83. data/lib/karafka/pro/recurring_tasks/schedule.rb +4 -6
  84. data/lib/karafka/pro/recurring_tasks.rb +8 -5
  85. data/lib/karafka/pro/routing/features/adaptive_iterator/contracts/topic.rb +2 -4
  86. data/lib/karafka/pro/routing/features/dead_letter_queue/contracts/topic.rb +2 -4
  87. data/lib/karafka/pro/routing/features/delaying/contracts/topic.rb +2 -4
  88. data/lib/karafka/pro/routing/features/delaying/topic.rb +2 -4
  89. data/lib/karafka/pro/routing/features/direct_assignments/contracts/consumer_group.rb +4 -8
  90. data/lib/karafka/pro/routing/features/direct_assignments/contracts/topic.rb +5 -7
  91. data/lib/karafka/pro/routing/features/direct_assignments/subscription_group.rb +7 -6
  92. data/lib/karafka/pro/routing/features/direct_assignments/topic.rb +2 -2
  93. data/lib/karafka/pro/routing/features/expiring/contracts/topic.rb +2 -4
  94. data/lib/karafka/pro/routing/features/expiring/topic.rb +2 -4
  95. data/lib/karafka/pro/routing/features/filtering/contracts/topic.rb +2 -4
  96. data/lib/karafka/pro/routing/features/filtering/topic.rb +2 -3
  97. data/lib/karafka/pro/routing/features/inline_insights/contracts/topic.rb +2 -4
  98. data/lib/karafka/pro/routing/features/long_running_job/contracts/topic.rb +2 -4
  99. data/lib/karafka/pro/routing/features/multiplexing/contracts/topic.rb +3 -5
  100. data/lib/karafka/pro/routing/features/non_blocking_job/topic.rb +3 -3
  101. data/lib/karafka/pro/routing/features/offset_metadata/contracts/topic.rb +2 -4
  102. data/lib/karafka/pro/routing/features/parallel_segments/contracts/consumer_group.rb +2 -4
  103. data/lib/karafka/pro/routing/features/patterns/contracts/consumer_group.rb +3 -5
  104. data/lib/karafka/pro/routing/features/patterns/contracts/pattern.rb +2 -4
  105. data/lib/karafka/pro/routing/features/patterns/contracts/topic.rb +2 -4
  106. data/lib/karafka/pro/routing/features/pausing/config.rb +26 -0
  107. data/lib/karafka/pro/routing/features/pausing/contracts/topic.rb +17 -11
  108. data/lib/karafka/pro/routing/features/pausing/topic.rb +69 -8
  109. data/lib/karafka/pro/routing/features/periodic_job/contracts/topic.rb +2 -4
  110. data/lib/karafka/pro/routing/features/recurring_tasks/contracts/topic.rb +2 -4
  111. data/lib/karafka/pro/routing/features/scheduled_messages/contracts/topic.rb +2 -4
  112. data/lib/karafka/pro/routing/features/swarm/contracts/routing.rb +2 -4
  113. data/lib/karafka/pro/routing/features/swarm/contracts/topic.rb +6 -8
  114. data/lib/karafka/pro/routing/features/throttling/contracts/topic.rb +2 -4
  115. data/lib/karafka/pro/routing/features/virtual_partitions/contracts/topic.rb +2 -4
  116. data/lib/karafka/pro/scheduled_messages/contracts/config.rb +2 -4
  117. data/lib/karafka/pro/scheduled_messages/contracts/message.rb +2 -4
  118. data/lib/karafka/pro/scheduled_messages.rb +4 -6
  119. data/lib/karafka/pro/swarm/liveness_listener.rb +2 -2
  120. data/lib/karafka/processing/coordinator.rb +2 -4
  121. data/lib/karafka/processing/coordinators_buffer.rb +2 -3
  122. data/lib/karafka/processing/executor.rb +2 -3
  123. data/lib/karafka/processing/jobs/base.rb +2 -3
  124. data/lib/karafka/processing/workers_batch.rb +2 -3
  125. data/lib/karafka/railtie.rb +1 -0
  126. data/lib/karafka/routing/activity_manager.rb +2 -2
  127. data/lib/karafka/routing/builder.rb +5 -7
  128. data/lib/karafka/routing/consumer_group.rb +4 -6
  129. data/lib/karafka/routing/contracts/consumer_group.rb +3 -5
  130. data/lib/karafka/routing/contracts/routing.rb +2 -4
  131. data/lib/karafka/routing/contracts/topic.rb +2 -4
  132. data/lib/karafka/routing/features/active_job/contracts/topic.rb +2 -4
  133. data/lib/karafka/routing/features/active_job/topic.rb +6 -0
  134. data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +2 -4
  135. data/lib/karafka/routing/features/declaratives/contracts/topic.rb +3 -5
  136. data/lib/karafka/routing/features/deserializers/contracts/topic.rb +2 -4
  137. data/lib/karafka/routing/features/eofed/contracts/topic.rb +2 -4
  138. data/lib/karafka/routing/features/inline_insights/contracts/topic.rb +2 -4
  139. data/lib/karafka/routing/features/manual_offset_management/contracts/topic.rb +2 -4
  140. data/lib/karafka/routing/topics.rb +4 -9
  141. data/lib/karafka/server.rb +1 -1
  142. data/lib/karafka/setup/config.rb +66 -9
  143. data/lib/karafka/setup/contracts/config.rb +12 -10
  144. data/lib/karafka/setup/defaults_injector.rb +3 -2
  145. data/lib/karafka/setup/dsl.rb +2 -3
  146. data/lib/karafka/swarm/liveness_listener.rb +2 -3
  147. data/lib/karafka/swarm/supervisor.rb +1 -1
  148. data/lib/karafka/version.rb +1 -1
  149. data/lib/karafka.rb +2 -2
  150. metadata +8 -2
  151. data/.diffend.yml +0 -3
@@ -234,6 +234,76 @@ module Karafka
234
234
  end
235
235
  end
236
236
 
237
+ # Triggers a rebalance for the specified consumer group by briefly joining and leaving
238
+ #
239
+ # @param consumer_group_id [String] consumer group id to trigger rebalance for
240
+ #
241
+ # @return [void]
242
+ #
243
+ # @raise [Karafka::Errors::InvalidConfigurationError] when consumer group is not found in
244
+ # routing or has no topics
245
+ #
246
+ # @note This method creates a temporary "fake" consumer that joins the consumer group,
247
+ # triggering a rebalance when it joins and another when it leaves. This should only be
248
+ # used for operational/testing purposes as it causes two rebalances.
249
+ #
250
+ # @note The consumer group does not need to be running for this to work, but if it is,
251
+ # it will experience rebalances.
252
+ #
253
+ # @note The behavior follows the configured rebalance protocol. For cooperative sticky
254
+ # rebalancing or KIP-848 based protocols, there may be no immediate reaction to the
255
+ # rebalance trigger as these protocols allow incremental partition reassignments without
256
+ # stopping all consumers.
257
+ #
258
+ # @note Topics are always detected from the routing configuration. The consumer settings
259
+ # (kafka config) are taken from the first topic in the consumer group to ensure
260
+ # consistency with the actual consumer configuration.
261
+ #
262
+ # @example Trigger rebalance for a consumer group
263
+ # Karafka::Admin::ConsumerGroups.trigger_rebalance('my-group')
264
+ def trigger_rebalance(consumer_group_id)
265
+ consumer_group = Karafka::App.routes.find { |cg| cg.id == consumer_group_id }
266
+
267
+ unless consumer_group
268
+ raise(
269
+ Errors::InvalidConfigurationError,
270
+ "Consumer group '#{consumer_group_id}' not found in routing"
271
+ )
272
+ end
273
+
274
+ topics = consumer_group.topics.map(&:name)
275
+
276
+ if topics.empty?
277
+ raise(
278
+ Errors::InvalidConfigurationError,
279
+ "Consumer group '#{consumer_group_id}' has no topics"
280
+ )
281
+ end
282
+
283
+ # Get the first topic to extract kafka settings
284
+ first_topic = consumer_group.topics.first
285
+
286
+ # Build consumer settings using the consumer group's kafka config from first topic
287
+ # This ensures we use the same settings as the actual consumers
288
+ # Following the same pattern as in Karafka::Connection::Client#build_kafka
289
+ consumer_settings = Setup::AttributesMap.consumer(first_topic.kafka.dup)
290
+ consumer_settings[:'group.id'] = consumer_group.id
291
+ consumer_settings[:'enable.auto.offset.store'] = false
292
+ consumer_settings[:'auto.offset.reset'] ||= first_topic.initial_offset
293
+
294
+ with_consumer(consumer_settings) do |consumer|
295
+ # Subscribe to the topics - this triggers the first rebalance
296
+ consumer.subscribe(*topics)
297
+
298
+ # Wait briefly (100ms) to allow the rebalance to initiate
299
+ # The actual rebalance happens asynchronously, so we just need to give it a moment
300
+ sleep(0.1)
301
+
302
+ # Unsubscribe - this will trigger the second rebalance when the consumer closes
303
+ # The ensure block in with_consumer will handle the unsubscribe and close
304
+ end
305
+ end
306
+
237
307
  # Reads lags and offsets for given topics in the context of consumer groups defined in the
238
308
  # routing
239
309
  #
@@ -257,19 +327,19 @@ module Karafka
257
327
  # We first fetch all the topics with partitions count that exist in the cluster so we
258
328
  # do not query for topics that do not exist and so we can get partitions count for all
259
329
  # the topics we may need. The non-existent and not consumed will be filled at the end
260
- existing_topics = cluster_info.topics.map do |topic|
330
+ existing_topics = cluster_info.topics.to_h do |topic|
261
331
  [topic[:topic_name], topic[:partition_count]]
262
- end.to_h.freeze
332
+ end.freeze
263
333
 
264
334
  # If no expected CGs, we use all from routing that have active topics
265
335
  if consumer_groups_with_topics.empty?
266
- consumer_groups_with_topics = Karafka::App.routes.map do |cg|
336
+ consumer_groups_with_topics = Karafka::App.routes.to_h do |cg|
267
337
  cg_topics = cg.topics.select do |cg_topic|
268
338
  active_topics_only ? cg_topic.active? : true
269
339
  end
270
340
 
271
341
  [cg.id, cg_topics.map(&:name)]
272
- end.to_h
342
+ end
273
343
  end
274
344
 
275
345
  # We make a copy because we will remove once with non-existing topics
@@ -36,7 +36,7 @@ module Karafka
36
36
 
37
37
  # Build the requested range - since first element is on the start offset we need to
38
38
  # subtract one from requested count to end up with expected number of elements
39
- requested_range = (start_offset..start_offset + (count - 1))
39
+ requested_range = (start_offset..(start_offset + (count - 1)))
40
40
  # Establish theoretical available range. Note, that this does not handle cases related
41
41
  # to log retention or compaction
42
42
  available_range = (low_offset..(high_offset - 1))
@@ -139,15 +139,48 @@ module Karafka
139
139
  end
140
140
  end
141
141
 
142
- # Fetches the watermark offsets for a given topic partition
142
+ # Fetches the watermark offsets for a given topic partition or multiple topics and
143
+ # partitions
143
144
  #
144
- # @param name [String, Symbol] topic name
145
- # @param partition [Integer] partition
146
- # @return [Array<Integer, Integer>] low watermark offset and high watermark offset
147
- def read_watermark_offsets(name, partition)
145
+ # @param name_or_hash [String, Symbol, Hash] topic name or hash with topics and partitions
146
+ # @param partition [Integer, nil] partition number (required when first param is topic name)
147
+ #
148
+ # @return [Array<Integer, Integer>, Hash] when querying single partition returns array with
149
+ # low and high watermark offsets, when querying multiple returns nested hash
150
+ #
151
+ # @example Query single partition
152
+ # Karafka::Admin::Topics.read_watermark_offsets('events', 0)
153
+ # # => [0, 100]
154
+ #
155
+ # @example Query specific partitions across multiple topics
156
+ # Karafka::Admin::Topics.read_watermark_offsets(
157
+ # { 'events' => [0, 1], 'logs' => [0] }
158
+ # )
159
+ # # => {
160
+ # # 'events' => {
161
+ # # 0 => [0, 100],
162
+ # # 1 => [0, 150]
163
+ # # },
164
+ # # 'logs' => {
165
+ # # 0 => [0, 50]
166
+ # # }
167
+ # # }
168
+ def read_watermark_offsets(name_or_hash, partition = nil)
169
+ # Normalize input to hash format
170
+ topics_with_partitions = partition ? { name_or_hash => [partition] } : name_or_hash
171
+
172
+ result = Hash.new { |h, k| h[k] = {} }
173
+
148
174
  with_consumer do |consumer|
149
- consumer.query_watermark_offsets(name, partition)
175
+ topics_with_partitions.each do |topic, partitions|
176
+ partitions.each do |partition_id|
177
+ result[topic][partition_id] = consumer.query_watermark_offsets(topic, partition_id)
178
+ end
179
+ end
150
180
  end
181
+
182
+ # Return single array for single partition query, hash for multiple
183
+ partition ? result.dig(name_or_hash, partition) : result
151
184
  end
152
185
 
153
186
  # Returns basic topic metadata
data/lib/karafka/admin.rb CHANGED
@@ -59,11 +59,11 @@ module Karafka
59
59
  Topics.create_partitions(name, partitions)
60
60
  end
61
61
 
62
- # @param name [String, Symbol] topic name
63
- # @param partition [Integer] partition
62
+ # @param name_or_hash [String, Symbol, Hash] topic name or hash with topics and partitions
63
+ # @param partition [Integer, nil] partition (nil when using hash format)
64
64
  # @see Topics.read_watermark_offsets
65
- def read_watermark_offsets(name, partition)
66
- Topics.read_watermark_offsets(name, partition)
65
+ def read_watermark_offsets(name_or_hash, partition = nil)
66
+ Topics.read_watermark_offsets(name_or_hash, partition)
67
67
  end
68
68
 
69
69
  # @param topic_name [String] name of the topic we're interested in
@@ -113,6 +113,15 @@ module Karafka
113
113
  ConsumerGroups.delete(consumer_group_id)
114
114
  end
115
115
 
116
+ # Triggers a rebalance for the specified consumer group
117
+ #
118
+ # @param consumer_group_id [String] consumer group id to trigger rebalance for
119
+ # @see ConsumerGroups.trigger_rebalance
120
+ # @note This API should be used only for development.
121
+ def trigger_rebalance(consumer_group_id)
122
+ ConsumerGroups.trigger_rebalance(consumer_group_id)
123
+ end
124
+
116
125
  # Reads lags and offsets for given topics in the context of consumer groups defined in the
117
126
  # routing
118
127
  # @param consumer_groups_with_topics [Hash<String, Array<String>>] hash with consumer groups
@@ -14,8 +14,9 @@ module Karafka
14
14
 
15
15
  def_delegators :@coordinator, :topic, :partition, :eofed?, :seek_offset, :seek_offset=
16
16
 
17
- def_delegators :producer, :produce_async, :produce_sync, :produce_many_async,
18
- :produce_many_sync
17
+ def_delegators(
18
+ :producer, :produce_async, :produce_sync, :produce_many_async, :produce_many_sync
19
+ )
19
20
 
20
21
  def_delegators :messages, :each
21
22
 
@@ -81,9 +82,8 @@ module Karafka
81
82
  # @private
82
83
  #
83
84
  # @param action [Symbol]
84
- # @param block [Proc]
85
- def on_wrap(action, &block)
86
- handle_wrap(action, &block)
85
+ def on_wrap(action, &)
86
+ handle_wrap(action, &)
87
87
  rescue StandardError => e
88
88
  monitor.instrument(
89
89
  'error.occurred',
@@ -68,7 +68,7 @@ module Karafka
68
68
 
69
69
  # However when it is unavailable, we still want to be able to run help command
70
70
  # and install command as they don't require configured app itself to run
71
- return if %w[-h install].any? { |cmd| cmd == ARGV[0] }
71
+ return if %w[-h install].any?(ARGV[0])
72
72
 
73
73
  # All other commands except help and install do require an existing boot file if it was
74
74
  # declared
@@ -7,10 +7,8 @@ module Karafka
7
7
  # Contract for validating correctness of the server cli command options.
8
8
  class Server < ::Karafka::Contracts::Base
9
9
  configure do |config|
10
- config.error_messages = YAML.safe_load(
11
- File.read(
12
- File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
13
- )
10
+ config.error_messages = YAML.safe_load_file(
11
+ File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
14
12
  ).fetch('en').fetch('validations').fetch('cli').fetch('server')
15
13
  end
16
14
 
@@ -54,7 +54,7 @@ module Karafka
54
54
  template = File.read(Karafka.core_root.join("templates/#{source}"))
55
55
  render = ::ERB.new(template, trim_mode: '-').result(binding)
56
56
 
57
- File.open(pathed_target, 'w') { |file| file.write(render) }
57
+ File.write(pathed_target, render)
58
58
 
59
59
  puts "#{green('Created')} #{target}"
60
60
  end
@@ -95,7 +95,7 @@ module Karafka
95
95
  names = config.synonyms.map(&:name) << config.name
96
96
 
97
97
  # We move forward only if given topic config is for altering
98
- next if (desired_configs.keys & names).empty?
98
+ next unless desired_configs.keys.intersect?(names)
99
99
 
100
100
  desired_config = nil
101
101
 
@@ -10,9 +10,9 @@ module Karafka
10
10
  def call
11
11
  any_repartitioned = false
12
12
 
13
- existing_partitions = existing_topics.map do |topic|
13
+ existing_partitions = existing_topics.to_h do |topic|
14
14
  [topic.fetch(:topic_name), topic.fetch(:partition_count)]
15
- end.to_h
15
+ end
16
16
 
17
17
  declaratives_routing_topics.each do |topic|
18
18
  name = topic.name
@@ -51,7 +51,9 @@ module Karafka
51
51
  :fenced, # -144
52
52
  :auto_offset_reset, # -140
53
53
  # This can happen for many reasons, including issues with static membership being fenced
54
- :fatal # -150
54
+ :fatal, # -150,
55
+ # This can happen with new rebalance protocol and same group.instance.id in use
56
+ :unreleased_instance_id # 111
55
57
  ].freeze
56
58
 
57
59
  private_constant :MAX_POLL_RETRIES, :COOP_UNSUBSCRIBE_FACTOR, :EARLY_REPORT_ERRORS
@@ -407,6 +409,14 @@ module Karafka
407
409
  # 2ms when no callbacks are triggered.
408
410
  def events_poll(timeout = 0)
409
411
  kafka.events_poll(timeout)
412
+
413
+ # Emit event for monitoring - happens once per tick_interval (default 5s)
414
+ # Listeners can check assignment_lost?, track polling health, etc.
415
+ Karafka.monitor.instrument(
416
+ 'client.events_poll',
417
+ caller: self,
418
+ subscription_group: @subscription_group
419
+ )
410
420
  end
411
421
 
412
422
  # Returns pointer to the consumer group metadata. It is used only in the context of
@@ -612,7 +622,7 @@ module Karafka
612
622
  # We should not run a single poll longer than the tick frequency. Otherwise during a single
613
623
  # `#batch_poll` we would not be able to run `#events_poll` often enough effectively
614
624
  # blocking events from being handled.
615
- poll_tick = timeout > tick_interval ? tick_interval : timeout
625
+ poll_tick = [timeout, tick_interval].min
616
626
 
617
627
  result = kafka.poll(poll_tick)
618
628
 
@@ -25,9 +25,8 @@ module Karafka
25
25
  end
26
26
 
27
27
  # Iterates over available listeners and yields each listener
28
- # @param block [Proc] block we want to run
29
- def each(&block)
30
- @batch.each(&block)
28
+ def each(&)
29
+ @batch.each(&)
31
30
  end
32
31
 
33
32
  # @return [Array<Listener>] active listeners
@@ -10,6 +10,10 @@ module Karafka
10
10
  # do still want to be able to alter some functionalities. This wrapper helps us do it when
11
11
  # it would be needed
12
12
  class Proxy < SimpleDelegator
13
+ include Helpers::ConfigImporter.new(
14
+ proxy_config: %i[internal connection proxy]
15
+ )
16
+
13
17
  # Errors on which we want to retry
14
18
  # Includes temporary errors related to node not being (or not yet being) coordinator or a
15
19
  # leader to a given set of partitions. Usually goes away after a retry
@@ -21,6 +25,7 @@ module Karafka
21
25
  not_coordinator
22
26
  not_leader_for_partition
23
27
  coordinator_load_in_progress
28
+ stale_member_epoch
24
29
  ].freeze
25
30
 
26
31
  private_constant :RETRYABLE_DEFAULT_ERRORS
@@ -36,7 +41,6 @@ module Karafka
36
41
  # wrap an already wrapped object with another proxy level. Simplifies passing consumers
37
42
  # and makes it safe to wrap without type checking
38
43
  @wrapped = obj.is_a?(self.class) ? obj.wrapped : obj
39
- @config = ::Karafka::App.config.internal.connection.proxy
40
44
  end
41
45
 
42
46
  # Proxies the `#query_watermark_offsets` with extra recovery from timeout problems.
@@ -47,7 +51,7 @@ module Karafka
47
51
  # @param partition [Integer] partition number
48
52
  # @return [Array<Integer, Integer>] watermark offsets
49
53
  def query_watermark_offsets(topic, partition)
50
- l_config = @config.query_watermark_offsets
54
+ l_config = proxy_config.query_watermark_offsets
51
55
 
52
56
  # For newly created topics or in cases where we're trying to get them but there is no
53
57
  # leader, this can fail. It happens more often for new topics under KRaft, however we
@@ -67,7 +71,7 @@ module Karafka
67
71
  # @param tpl [Rdkafka::Consumer::TopicPartitionList] tpl to get time offsets
68
72
  # @return [Rdkafka::Consumer::TopicPartitionList] tpl with time offsets
69
73
  def offsets_for_times(tpl)
70
- l_config = @config.offsets_for_times
74
+ l_config = proxy_config.offsets_for_times
71
75
 
72
76
  with_broker_errors_retry(
73
77
  # required to be in seconds, not ms
@@ -84,7 +88,7 @@ module Karafka
84
88
  # assignment tpl usage
85
89
  # @return [Rdkafka::Consumer::TopicPartitionList] tpl with committed offsets and metadata
86
90
  def committed(tpl = nil)
87
- c_config = @config.committed
91
+ c_config = proxy_config.committed
88
92
 
89
93
  with_broker_errors_retry(
90
94
  # required to be in seconds, not ms
@@ -121,7 +125,7 @@ module Karafka
121
125
  # even when no stored, because with sync commit, it refreshes the ownership state of the
122
126
  # consumer in a sync way.
123
127
  def commit_offsets(tpl = nil, async: true)
124
- c_config = @config.commit
128
+ c_config = proxy_config.commit
125
129
 
126
130
  with_broker_errors_retry(
127
131
  wait_time: c_config.wait_time / 1_000.to_f,
@@ -153,7 +157,7 @@ module Karafka
153
157
  # we want to get the lag on the defined CG
154
158
  # @return [Hash<String, Hash>] hash with topics and their partitions lags
155
159
  def lag(tpl)
156
- l_config = @config.committed
160
+ l_config = proxy_config.committed
157
161
 
158
162
  with_broker_errors_retry(
159
163
  # required to be in seconds, not ms
@@ -168,7 +172,7 @@ module Karafka
168
172
  # get info on all topics
169
173
  # @return [Rdkafka::Metadata] rdkafka metadata object with the requested details
170
174
  def metadata(topic_name = nil)
171
- m_config = @config.metadata
175
+ m_config = proxy_config.metadata
172
176
 
173
177
  with_broker_errors_retry(
174
178
  # required to be in seconds, not ms
data/lib/karafka/env.rb CHANGED
@@ -21,8 +21,7 @@ module Karafka
21
21
  super('')
22
22
 
23
23
  LOOKUP_ENV_KEYS
24
- .map { |key| ENV[key] }
25
- .compact
24
+ .filter_map { |key| ENV.fetch(key, nil) }
26
25
  .first
27
26
  .then { |env| env || DEFAULT_ENV }
28
27
  .then { |env| replace(env) }
@@ -11,11 +11,14 @@ module Karafka
11
11
  # or other places but would only slow things down if would run with each tick.
12
12
  class IntervalRunner
13
13
  include Karafka::Core::Helpers::Time
14
+ include Helpers::ConfigImporter.new(
15
+ tick_interval: %i[internal tick_interval]
16
+ )
14
17
 
15
18
  # @param interval [Integer] interval in ms for running the provided code. Defaults to the
16
19
  # `internal.tick_interval` value
17
20
  # @param block [Proc] block of code we want to run once in a while
18
- def initialize(interval: ::Karafka::App.config.internal.tick_interval, &block)
21
+ def initialize(interval: tick_interval, &block)
19
22
  @block = block
20
23
  @interval = interval
21
24
  @last_called_at = monotonic_now - @interval
@@ -79,6 +79,23 @@ module Karafka
79
79
  end
80
80
  end
81
81
 
82
+ # Handles events_poll notification to detect assignment loss
83
+ # This is called regularly (every tick_interval) so we check if assignment was lost
84
+ #
85
+ # @param event [Karafka::Core::Monitoring::Event]
86
+ # @note We can run the `#assignment_lost?` on each events poll because they happen once every
87
+ # 5 seconds during processing plus prior to each messages poll. It takes
88
+ # 0.6 microseconds per call.
89
+ def on_client_events_poll(event)
90
+ client = event[:caller]
91
+
92
+ # Only clear assignments if they were actually lost
93
+ return unless client.assignment_lost?
94
+
95
+ # Cleaning happens the same way as with the consumer reset
96
+ on_client_reset(event)
97
+ end
98
+
82
99
  # Removes partitions from the current assignments hash
83
100
  #
84
101
  # @param event [Karafka::Core::Monitoring::Event]
@@ -15,7 +15,7 @@ module Karafka
15
15
  notifications_bus = ::Karafka::Instrumentation::Notifications.new,
16
16
  namespace = nil
17
17
  )
18
- super(notifications_bus, namespace)
18
+ super
19
19
  end
20
20
  end
21
21
  end
@@ -34,6 +34,7 @@ module Karafka
34
34
  client.pause
35
35
  client.resume
36
36
  client.reset
37
+ client.events_poll
37
38
 
38
39
  connection.listener.before_fetch_loop
39
40
  connection.listener.fetch_loop
@@ -18,10 +18,9 @@ module Karafka
18
18
  setup(&block) if block
19
19
  end
20
20
 
21
- # @param block [Proc] configuration block
22
21
  # @note We define this alias to be consistent with `Karafka#setup`
23
- def setup(&block)
24
- configure(&block)
22
+ def setup(&)
23
+ configure(&)
25
24
  end
26
25
  end
27
26
  end
@@ -38,10 +38,9 @@ module Karafka
38
38
  @job_types_cache = {}
39
39
  end
40
40
 
41
- # @param block [Proc] configuration block
42
41
  # @note We define this alias to be consistent with `WaterDrop#setup`
43
- def setup(&block)
44
- configure(&block)
42
+ def setup(&)
43
+ configure(&)
45
44
  end
46
45
 
47
46
  # Prints info about the fact that a given job has started
@@ -14,8 +14,9 @@ module Karafka
14
14
  include ::Karafka::Core::Configurable
15
15
  extend Forwardable
16
16
 
17
- def_delegators :config, :client, :rd_kafka_metrics, :namespace,
18
- :default_tags, :distribution_mode
17
+ def_delegators(
18
+ :config, :client, :rd_kafka_metrics, :namespace, :default_tags, :distribution_mode
19
+ )
19
20
 
20
21
  # Value object for storing a single rdkafka metric publishing details
21
22
  RdKafkaMetric = Struct.new(:type, :scope, :name, :key_location)
@@ -69,10 +70,9 @@ module Karafka
69
70
  setup(&block) if block
70
71
  end
71
72
 
72
- # @param block [Proc] configuration block
73
73
  # @note We define this alias to be consistent with `WaterDrop#setup`
74
- def setup(&block)
75
- configure(&block)
74
+ def setup(&)
75
+ configure(&)
76
76
  end
77
77
 
78
78
  # Hooks up to Karafka instrumentation for emitted statistics
@@ -198,18 +198,17 @@ module Karafka
198
198
 
199
199
  # Selects the histogram mode configured and uses it to report to DD client
200
200
  # @param key [String] non-namespaced key
201
- # @param args [Array] extra arguments to pass to the client
202
- def histogram(key, *args)
201
+ def histogram(key, *)
203
202
  case distribution_mode
204
203
  when :histogram
205
204
  client.histogram(
206
205
  namespaced_metric(key),
207
- *args
206
+ *
208
207
  )
209
208
  when :distribution
210
209
  client.distribution(
211
210
  namespaced_metric(key),
212
- *args
211
+ *
213
212
  )
214
213
  else
215
214
  raise(
@@ -166,9 +166,8 @@ module Karafka
166
166
  end
167
167
 
168
168
  # Wraps the logic with a mutex
169
- # @param block [Proc] code we want to run in mutex
170
- def synchronize(&block)
171
- @mutex.synchronize(&block)
169
+ def synchronize(&)
170
+ @mutex.synchronize(&)
172
171
  end
173
172
 
174
173
  # @return [Integer] object id of the current thread
@@ -51,7 +51,7 @@ module Karafka
51
51
  return now unless last_message
52
52
 
53
53
  timestamp = last_message.timestamp
54
- timestamp > now ? now : timestamp
54
+ [timestamp, now].min
55
55
  end
56
56
  end
57
57
  end
@@ -28,7 +28,7 @@ module Karafka
28
28
 
29
29
  # And nullify it in the kafka message. This can save a lot of memory when used with
30
30
  # the Pro Cleaner API
31
- kafka_message.instance_variable_set('@payload', nil)
31
+ kafka_message.instance_variable_set(:@payload, nil)
32
32
 
33
33
  # Karafka messages cannot be frozen because of the lazy deserialization feature
34
34
  message = Karafka::Messages::Message.new(payload, metadata)
@@ -16,10 +16,9 @@ module Karafka
16
16
  @metadata = metadata
17
17
  end
18
18
 
19
- # @param block [Proc] block we want to execute per each message
20
19
  # @note Invocation of this method will not cause loading and deserializing of messages.
21
- def each(&block)
22
- @messages_array.each(&block)
20
+ def each(&)
21
+ @messages_array.each(&)
23
22
  end
24
23
 
25
24
  # Runs deserialization of all the messages and returns them
@@ -93,29 +93,29 @@ end
93
93
 
94
94
  # We need to replace the original callback with ours.
95
95
  # At the moment there is no API in rdkafka-ruby to do so
96
- ::Rdkafka::Bindings.send(
96
+ Rdkafka::Bindings.send(
97
97
  :remove_const,
98
98
  'RebalanceCallback'
99
99
  )
100
100
 
101
- ::Rdkafka::Bindings.const_set(
102
- 'RebalanceCallback',
101
+ Rdkafka::Bindings.const_set(
102
+ :RebalanceCallback,
103
103
  Karafka::Patches::Rdkafka::Bindings::RebalanceCallback
104
104
  )
105
105
 
106
- ::Rdkafka::Bindings.attach_function(
106
+ Rdkafka::Bindings.attach_function(
107
107
  :rd_kafka_rebalance_protocol,
108
108
  %i[pointer],
109
109
  :string
110
110
  )
111
111
 
112
- ::Rdkafka::Bindings.attach_function(
112
+ Rdkafka::Bindings.attach_function(
113
113
  :rd_kafka_incremental_assign,
114
114
  %i[pointer pointer],
115
115
  :string
116
116
  )
117
117
 
118
- ::Rdkafka::Bindings.attach_function(
118
+ Rdkafka::Bindings.attach_function(
119
119
  :rd_kafka_incremental_unassign,
120
120
  %i[pointer pointer],
121
121
  :string
@@ -31,6 +31,6 @@ module Karafka
31
31
  end
32
32
  end
33
33
 
34
- ::Rdkafka::Opaque.include(
34
+ Rdkafka::Opaque.include(
35
35
  Karafka::Patches::Rdkafka::Opaque
36
36
  )