karafka 2.6.0.beta1 → 2.6.0.rc1

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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +64 -27
  3. data/certs/expired.txt +2 -0
  4. data/config/locales/errors.yml +5 -0
  5. data/karafka.gemspec +1 -1
  6. data/lib/active_job/queue_adapters/karafka_adapter.rb +1 -1
  7. data/lib/karafka/active_job/current_attributes/loading.rb +1 -1
  8. data/lib/karafka/active_job/current_attributes.rb +4 -2
  9. data/lib/karafka/active_job/dispatcher.rb +1 -2
  10. data/lib/karafka/admin/consumer_groups.rb +8 -2
  11. data/lib/karafka/admin/replication.rb +6 -3
  12. data/lib/karafka/admin/topics.rb +32 -12
  13. data/lib/karafka/admin.rb +155 -59
  14. data/lib/karafka/app.rb +1 -1
  15. data/lib/karafka/base_consumer.rb +33 -5
  16. data/lib/karafka/cli/console.rb +0 -1
  17. data/lib/karafka/cli/help.rb +0 -1
  18. data/lib/karafka/cli/info.rb +0 -1
  19. data/lib/karafka/cli/install.rb +0 -1
  20. data/lib/karafka/cli/server.rb +0 -1
  21. data/lib/karafka/cli/swarm.rb +0 -1
  22. data/lib/karafka/cli.rb +2 -2
  23. data/lib/karafka/connection/client.rb +44 -7
  24. data/lib/karafka/connection/listener.rb +3 -3
  25. data/lib/karafka/connection/manager.rb +0 -1
  26. data/lib/karafka/connection/pauses_manager.rb +17 -0
  27. data/lib/karafka/connection/proxy.rb +45 -0
  28. data/lib/karafka/connection/status.rb +0 -1
  29. data/lib/karafka/instrumentation/assignments_tracker.rb +34 -0
  30. data/lib/karafka/instrumentation/callbacks/consumer_groups/decorator.rb +19 -0
  31. data/lib/karafka/instrumentation/callbacks/consumer_groups/error.rb +0 -1
  32. data/lib/karafka/instrumentation/callbacks/consumer_groups/statistics.rb +3 -2
  33. data/lib/karafka/instrumentation/critical_errors_listener.rb +85 -0
  34. data/lib/karafka/instrumentation/logger_listener.rb +2 -0
  35. data/lib/karafka/instrumentation/monitor.rb +2 -3
  36. data/lib/karafka/instrumentation/vendors/appsignal/errors_listener.rb +0 -1
  37. data/lib/karafka/instrumentation/vendors/appsignal/metrics_listener.rb +0 -1
  38. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +4 -3
  39. data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +0 -2
  40. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +181 -18
  41. data/lib/karafka/pro/admin/recovery/errors.rb +0 -1
  42. data/lib/karafka/pro/admin/recovery.rb +6 -4
  43. data/lib/karafka/pro/base_consumer.rb +5 -5
  44. data/lib/karafka/pro/cleaner/messages/metadata.rb +0 -1
  45. data/lib/karafka/pro/cli/parallel_segments/base.rb +1 -2
  46. data/lib/karafka/pro/connection/manager.rb +7 -4
  47. data/lib/karafka/pro/encryption/messages/middleware.rb +5 -0
  48. data/lib/karafka/pro/encryption/messages/parser.rb +4 -0
  49. data/lib/karafka/pro/instrumentation/callbacks/consumer_groups/decorator.rb +63 -0
  50. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/compensator.rb +120 -0
  51. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/fetcher.rb +88 -0
  52. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/refresher.rb +185 -0
  53. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/registry.rb +102 -0
  54. data/lib/karafka/pro/instrumentation/performance_tracker.rb +40 -6
  55. data/lib/karafka/pro/iterator/tpl_builder.rb +27 -7
  56. data/lib/karafka/pro/iterator.rb +22 -7
  57. data/lib/karafka/pro/loader.rb +12 -0
  58. data/lib/karafka/pro/processing/consumer_groups/adaptive_iterator/consumer.rb +0 -3
  59. data/lib/karafka/pro/processing/consumer_groups/adaptive_iterator/tracker.rb +2 -4
  60. data/lib/karafka/pro/processing/consumer_groups/collapser.rb +6 -9
  61. data/lib/karafka/pro/processing/consumer_groups/coordinator.rb +1 -4
  62. data/lib/karafka/pro/processing/consumer_groups/coordinators/errors_tracker.rb +0 -3
  63. data/lib/karafka/pro/processing/consumer_groups/coordinators/filters_applier.rb +21 -10
  64. data/lib/karafka/pro/processing/consumer_groups/coordinators/virtual_offset_manager.rb +4 -4
  65. data/lib/karafka/pro/processing/consumer_groups/expansions_selector.rb +0 -3
  66. data/lib/karafka/pro/processing/consumer_groups/filters/actions.rb +84 -0
  67. data/lib/karafka/pro/processing/consumer_groups/filters/base.rb +5 -6
  68. data/lib/karafka/pro/processing/consumer_groups/filters/delayer.rb +2 -5
  69. data/lib/karafka/pro/processing/consumer_groups/filters/expirer.rb +1 -4
  70. data/lib/karafka/pro/processing/consumer_groups/filters/inline_insights_delayer.rb +1 -4
  71. data/lib/karafka/pro/processing/consumer_groups/filters/throttler.rb +2 -5
  72. data/lib/karafka/pro/processing/consumer_groups/filters/virtual_limiter.rb +0 -3
  73. data/lib/karafka/pro/processing/consumer_groups/jobs/consume_non_blocking.rb +2 -3
  74. data/lib/karafka/pro/processing/consumer_groups/jobs/eofed_non_blocking.rb +0 -2
  75. data/lib/karafka/pro/processing/consumer_groups/jobs/periodic.rb +2 -3
  76. data/lib/karafka/pro/processing/consumer_groups/jobs/periodic_non_blocking.rb +0 -2
  77. data/lib/karafka/pro/processing/consumer_groups/jobs/revoked_non_blocking.rb +0 -3
  78. data/lib/karafka/pro/processing/consumer_groups/jobs_builder.rb +5 -4
  79. data/lib/karafka/pro/processing/consumer_groups/offset_metadata/consumer.rb +3 -7
  80. data/lib/karafka/pro/processing/consumer_groups/offset_metadata/fetcher.rb +8 -10
  81. data/lib/karafka/pro/processing/consumer_groups/offset_metadata/listener.rb +0 -3
  82. data/lib/karafka/pro/processing/consumer_groups/parallel_segments/filters/base.rb +3 -5
  83. data/lib/karafka/pro/processing/consumer_groups/parallel_segments/filters/default.rb +0 -3
  84. data/lib/karafka/pro/processing/consumer_groups/parallel_segments/filters/mom.rb +0 -3
  85. data/lib/karafka/pro/processing/consumer_groups/partitioner.rb +2 -5
  86. data/lib/karafka/pro/processing/consumer_groups/periodic_job/consumer.rb +2 -5
  87. data/lib/karafka/pro/processing/consumer_groups/piping/consumer.rb +2 -5
  88. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_lrj_mom.rb +10 -9
  89. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_lrj_mom_vp.rb +11 -10
  90. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_mom.rb +0 -3
  91. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_mom_vp.rb +0 -3
  92. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_lrj_mom.rb +8 -7
  93. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_lrj_mom_vp.rb +9 -8
  94. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_mom.rb +5 -8
  95. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_mom_vp.rb +3 -6
  96. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_lrj_mom.rb +4 -7
  97. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_lrj_mom_vp.rb +6 -9
  98. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_mom.rb +3 -7
  99. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_mom_vp.rb +4 -8
  100. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/lrj_mom.rb +3 -6
  101. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/lrj_mom_vp.rb +4 -7
  102. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/mom.rb +2 -5
  103. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/mom_vp.rb +3 -6
  104. data/lib/karafka/pro/processing/consumer_groups/strategies/base.rb +0 -3
  105. data/lib/karafka/pro/processing/consumer_groups/strategies/default.rb +56 -33
  106. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/default.rb +27 -14
  107. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr.rb +0 -3
  108. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj.rb +4 -7
  109. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj_mom.rb +5 -8
  110. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj_mom_vp.rb +5 -8
  111. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj_vp.rb +3 -7
  112. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_mom.rb +0 -3
  113. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_mom_vp.rb +0 -3
  114. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_vp.rb +0 -3
  115. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj.rb +2 -5
  116. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj_mom.rb +0 -3
  117. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj_mom_vp.rb +0 -3
  118. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj_vp.rb +3 -6
  119. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/mom.rb +0 -3
  120. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/mom_vp.rb +3 -6
  121. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/vp.rb +65 -8
  122. data/lib/karafka/pro/processing/consumer_groups/strategies/ftr/default.rb +0 -4
  123. data/lib/karafka/pro/processing/consumer_groups/strategies/ftr/vp.rb +2 -5
  124. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/default.rb +0 -3
  125. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr.rb +3 -6
  126. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr_mom.rb +4 -7
  127. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr_mom_vp.rb +4 -8
  128. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr_vp.rb +3 -6
  129. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/mom.rb +2 -5
  130. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/mom_vp.rb +3 -7
  131. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/vp.rb +2 -5
  132. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/default.rb +0 -3
  133. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/ftr.rb +0 -3
  134. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/ftr_vp.rb +0 -3
  135. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/vp.rb +0 -3
  136. data/lib/karafka/pro/processing/consumer_groups/strategies/vp/default.rb +11 -6
  137. data/lib/karafka/pro/processing/consumer_groups/strategy_selector.rb +0 -3
  138. data/lib/karafka/pro/processing/consumer_groups/subscription_groups_coordinator.rb +0 -3
  139. data/lib/karafka/pro/processing/consumer_groups/virtual_partitions/distributors/balanced.rb +0 -3
  140. data/lib/karafka/pro/processing/consumer_groups/virtual_partitions/distributors/base.rb +2 -4
  141. data/lib/karafka/pro/processing/consumer_groups/virtual_partitions/distributors/consistent.rb +0 -3
  142. data/lib/karafka/pro/processing/jobs_queue.rb +4 -1
  143. data/lib/karafka/pro/processing/schedulers/base.rb +4 -2
  144. data/lib/karafka/pro/processing/schedulers/default.rb +2 -2
  145. data/lib/karafka/pro/recurring_tasks/consumer.rb +9 -2
  146. data/lib/karafka/pro/recurring_tasks/contracts/config.rb +0 -1
  147. data/lib/karafka/pro/recurring_tasks/executor.rb +25 -13
  148. data/lib/karafka/pro/routing/features/consumer_groups/direct_assignments/subscription_group.rb +0 -2
  149. data/lib/karafka/pro/routing/features/consumer_groups/long_running_job.rb +0 -2
  150. data/lib/karafka/pro/routing/features/consumer_groups/multiplexing/proxy.rb +2 -2
  151. data/lib/karafka/pro/routing/features/consumer_groups/non_blocking_job.rb +0 -2
  152. data/lib/karafka/pro/routing/features/consumer_groups/offset_metadata/topic.rb +0 -6
  153. data/lib/karafka/pro/routing/features/consumer_groups/parallel_segments/consumer_group.rb +5 -4
  154. data/lib/karafka/pro/routing/features/consumer_groups/patterns/config.rb +0 -6
  155. data/lib/karafka/pro/routing/features/consumer_groups/patterns/consumer_group.rb +2 -1
  156. data/lib/karafka/pro/routing/features/consumer_groups/patterns/contracts/pattern.rb +0 -1
  157. data/lib/karafka/pro/routing/features/consumer_groups/patterns/detector.rb +2 -1
  158. data/lib/karafka/pro/routing/features/consumer_groups/patterns/pattern.rb +2 -1
  159. data/lib/karafka/pro/routing/features/consumer_groups/patterns/topics.rb +8 -8
  160. data/lib/karafka/pro/routing/features/consumer_groups/periodic_job/topic.rb +5 -5
  161. data/lib/karafka/pro/routing/features/consumer_groups/recurring_tasks/builder.rb +33 -13
  162. data/lib/karafka/pro/routing/features/consumer_groups/scheduled_messages/builder.rb +52 -28
  163. data/lib/karafka/pro/routing/features/consumer_groups/scheduled_messages/topic.rb +2 -1
  164. data/lib/karafka/pro/routing/features/consumer_groups/virtual_partitions/topic.rb +6 -5
  165. data/lib/karafka/pro/scheduled_messages/consumer.rb +7 -1
  166. data/lib/karafka/pro/scheduled_messages/schema_validator.rb +6 -1
  167. data/lib/karafka/pro/swarm/liveness_listener.rb +118 -13
  168. data/lib/karafka/pro.rb +0 -6
  169. data/lib/karafka/processing/consumer_groups/coordinator.rb +4 -5
  170. data/lib/karafka/processing/consumer_groups/coordinators_buffer.rb +6 -2
  171. data/lib/karafka/processing/consumer_groups/executor.rb +4 -4
  172. data/lib/karafka/processing/consumer_groups/executors_buffer.rb +2 -4
  173. data/lib/karafka/processing/consumer_groups/expansions_selector.rb +0 -2
  174. data/lib/karafka/processing/consumer_groups/inline_insights/consumer.rb +0 -2
  175. data/lib/karafka/processing/consumer_groups/inline_insights/listener.rb +0 -2
  176. data/lib/karafka/processing/consumer_groups/inline_insights/tracker.rb +14 -16
  177. data/lib/karafka/processing/consumer_groups/jobs/consume.rb +2 -5
  178. data/lib/karafka/processing/consumer_groups/jobs/eofed.rb +2 -3
  179. data/lib/karafka/processing/consumer_groups/jobs/idle.rb +2 -2
  180. data/lib/karafka/processing/consumer_groups/jobs/revoked.rb +2 -4
  181. data/lib/karafka/processing/consumer_groups/jobs/shutdown.rb +2 -2
  182. data/lib/karafka/processing/consumer_groups/partitioner.rb +3 -6
  183. data/lib/karafka/processing/consumer_groups/strategies/aj_dlq_mom.rb +5 -2
  184. data/lib/karafka/processing/consumer_groups/strategies/aj_mom.rb +2 -4
  185. data/lib/karafka/processing/consumer_groups/strategies/base.rb +3 -5
  186. data/lib/karafka/processing/consumer_groups/strategies/default.rb +43 -13
  187. data/lib/karafka/processing/consumer_groups/strategies/dlq.rb +5 -2
  188. data/lib/karafka/processing/consumer_groups/strategies/dlq_mom.rb +5 -2
  189. data/lib/karafka/processing/consumer_groups/strategies/mom.rb +2 -3
  190. data/lib/karafka/processing/consumer_groups/strategy_selector.rb +0 -2
  191. data/lib/karafka/processing/coordinator.rb +0 -2
  192. data/lib/karafka/processing/coordinators_buffer.rb +0 -2
  193. data/lib/karafka/processing/partitioner.rb +0 -2
  194. data/lib/karafka/processing/strategy_selector.rb +0 -2
  195. data/lib/karafka/processing/worker.rb +14 -6
  196. data/lib/karafka/processing/workers_pool.rb +21 -4
  197. data/lib/karafka/railtie.rb +0 -1
  198. data/lib/karafka/routing/contracts/routing.rb +2 -2
  199. data/lib/karafka/routing/features/consumer_groups/active_job/topic.rb +6 -5
  200. data/lib/karafka/routing/features/consumer_groups/manual_offset_management/topic.rb +2 -2
  201. data/lib/karafka/routing/features/deserializers/topic.rb +0 -1
  202. data/lib/karafka/routing/subscription_group.rb +1 -1
  203. data/lib/karafka/routing/topic.rb +4 -4
  204. data/lib/karafka/routing/topics.rb +21 -1
  205. data/lib/karafka/server.rb +9 -0
  206. data/lib/karafka/setup/config.rb +54 -10
  207. data/lib/karafka/setup/config_proxy.rb +2 -1
  208. data/lib/karafka/setup/contracts/config.rb +14 -0
  209. data/lib/karafka/swarm/node.rb +2 -2
  210. data/lib/karafka/version.rb +1 -2
  211. data/lib/karafka.rb +2 -2
  212. metadata +12 -4
@@ -37,8 +37,8 @@ module Karafka
37
37
  #
38
38
  # Methods here are suppose to be always available or are expected to be redefined
39
39
  module BaseConsumer
40
- # @return [Karafka::Pro::Processing::ConsumerGroups::Coordinators::ErrorsTracker] tracker for errors that
41
- # occurred during processing until another successful processing
40
+ # @return [Karafka::Pro::Processing::ConsumerGroups::Coordinators::ErrorsTracker] tracker for
41
+ # errors that occurred during processing until another successful processing
42
42
  #
43
43
  # @note This will always contain **only** details of errors that occurred during `#consume`
44
44
  # because only those are retryable.
@@ -53,9 +53,9 @@ module Karafka
53
53
  coordinator.errors_tracker
54
54
  end
55
55
 
56
- # @return [Karafka::Pro::Processing::ConsumerGroups::SubscriptionGroupsCoordinator] Coordinator allowing to
57
- # pause and resume polling of the given subscription group jobs queue for postponing
58
- # further work.
56
+ # @return [Karafka::Pro::Processing::ConsumerGroups::SubscriptionGroupsCoordinator]
57
+ # Coordinator allowing to pause and resume polling of the given subscription group jobs
58
+ # queue for postponing further work.
59
59
  #
60
60
  # @note Since this stops polling, it can cause reaching `max.poll.interval.ms` limitations.
61
61
  #
@@ -31,7 +31,6 @@
31
31
  module Karafka
32
32
  module Pro
33
33
  module Cleaner
34
- # Cleaner messages components related enhancements
35
34
  module Messages
36
35
  # Extensions to the message metadata that allow for granular memory control on a per
37
36
  # message basis
@@ -52,7 +52,7 @@ module Karafka
52
52
  # @return [Hash{String => Array<Karafka::Routing::ConsumerGroup>}] hash with all parallel
53
53
  # consumer groups as values and names of segments origin consumer group as the key.
54
54
  def applicable_groups
55
- requested_groups = options[:groups].dup || []
55
+ requested_groups = options[:groups] || []
56
56
 
57
57
  workable_groups = Karafka::App
58
58
  .routes
@@ -68,7 +68,6 @@ module Karafka
68
68
  workable_group = workable_groups[requested_group]
69
69
 
70
70
  if workable_group
71
- requested_groups.delete(requested_group)
72
71
  applicable_groups[requested_group] = workable_group
73
72
  else
74
73
  raise(
@@ -47,7 +47,6 @@ module Karafka
47
47
  # Creates new manager instance
48
48
  def initialize
49
49
  super
50
- @mutex = Mutex.new
51
50
  @changes = Hash.new do |h, k|
52
51
  h[k] = {
53
52
  state: "",
@@ -253,7 +252,11 @@ module Karafka
253
252
  # are also stable. This is a strong indicator that no rebalances or other operations are
254
253
  # happening at a given moment.
255
254
  def stable?(sg_listeners)
256
- @scale_delay ||= sg_listeners.first.subscription_group.multiplexing.scale_delay
255
+ # Must stay a local, not a memoized `@scale_delay`: this manager is a single process-wide
256
+ # instance shared by every subscription-group family, so memoizing here would freeze the
257
+ # first family's `scale_delay` and apply it to all the others, making them ignore their
258
+ # own configured value.
259
+ scale_delay = sg_listeners.first.subscription_group.multiplexing.scale_delay
257
260
 
258
261
  sg_listeners.all? do |sg_listener|
259
262
  # If a listener is not active, we do not take it into consideration when looking at
@@ -262,8 +265,8 @@ module Karafka
262
265
 
263
266
  state = @changes[sg_listener.subscription_group.id]
264
267
 
265
- state[:state_age] >= @scale_delay &&
266
- (monotonic_now - state[:changed_at]) >= @scale_delay &&
268
+ state[:state_age] >= scale_delay &&
269
+ (monotonic_now - state[:changed_at]) >= scale_delay &&
267
270
  state[:state] == "up" &&
268
271
  state[:join_state] == "steady"
269
272
  end
@@ -48,6 +48,11 @@ module Karafka
48
48
  def call(message)
49
49
  payload = message[:payload]
50
50
 
51
+ # Tombstones (records with a nil payload) carry no data to encrypt. We pass them
52
+ # through untouched so they remain valid tombstones (for example for log compaction)
53
+ # instead of crashing on `cipher.encrypt(nil)`.
54
+ return message if payload.nil?
55
+
51
56
  message[:headers] ||= {}
52
57
  message[:headers]["encryption"] = version
53
58
  message[:payload] = cipher.encrypt(payload)
@@ -52,6 +52,10 @@ module Karafka
52
52
 
53
53
  return super unless active && encryption
54
54
 
55
+ # Tombstones (nil payload) have nothing to decrypt; defer to the default parser so they
56
+ # remain valid tombstones instead of crashing on `cipher.decrypt(nil)`.
57
+ return super if message.raw_payload.nil?
58
+
55
59
  # Decrypt raw payload so it can be handled by the default parser logic
56
60
  decrypted_payload = cipher.decrypt(
57
61
  encryption,
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Karafka Pro - Source Available Commercial Software
4
+ # Copyright (c) 2017-present Maciej Mensfeld. All rights reserved.
5
+ #
6
+ # This software is NOT open source. It is source-available commercial software
7
+ # requiring a paid license for use. It is NOT covered by LGPL.
8
+ #
9
+ # The author retains all right, title, and interest in this software,
10
+ # including all copyrights, patents, and other intellectual property rights.
11
+ # No patent rights are granted under this license.
12
+ #
13
+ # PROHIBITED:
14
+ # - Use without a valid commercial license
15
+ # - Redistribution, modification, or derivative works without authorization
16
+ # - Reverse engineering, decompilation, or disassembly of this software
17
+ # - Use as training data for AI/ML models or inclusion in datasets
18
+ # - Scraping, crawling, or automated collection for any purpose
19
+ #
20
+ # PERMITTED:
21
+ # - Reading, referencing, and linking for personal or commercial use
22
+ # - Runtime retrieval by AI assistants, coding agents, and RAG systems
23
+ # for the purpose of providing contextual help to Karafka users
24
+ #
25
+ # Receipt, viewing, or possession of this software does not convey or
26
+ # imply any license or right beyond those expressly stated above.
27
+ #
28
+ # License: https://karafka.io/docs/Pro-License-Comm/
29
+ # Contact: contact@karafka.io
30
+
31
+ module Karafka
32
+ module Pro
33
+ module Instrumentation
34
+ # Namespace for Pro instrumentation callbacks related components
35
+ module Callbacks
36
+ # Namespace for Pro consumer groups callbacks related components
37
+ module ConsumerGroups
38
+ # Pro statistics decorator for consumer groups. On top of the standard decoration
39
+ # (deltas, freeze durations) it invokes Pro statistics enrichments, each encapsulated
40
+ # in its own component with a `#call` entrypoint. Currently: the lag compensation of
41
+ # long-paused partitions.
42
+ class Decorator < Karafka::Instrumentation::Callbacks::ConsumerGroups::Decorator
43
+ def initialize
44
+ super
45
+ @lag_compensator = Pro::Instrumentation::ConsumerGroups::LagCompensation::Compensator.new
46
+ end
47
+
48
+ # @param statistics [Hash] raw librdkafka statistics
49
+ # @return [Hash] decorated statistics with the Pro enrichments applied
50
+ #
51
+ # @note When the lag compensation is disabled, its refresher is not subscribed and
52
+ # the registry stays empty, so the compensation is a pass-through by itself
53
+ def call(statistics)
54
+ @lag_compensator.call(statistics)
55
+
56
+ super
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Karafka Pro - Source Available Commercial Software
4
+ # Copyright (c) 2017-present Maciej Mensfeld. All rights reserved.
5
+ #
6
+ # This software is NOT open source. It is source-available commercial software
7
+ # requiring a paid license for use. It is NOT covered by LGPL.
8
+ #
9
+ # The author retains all right, title, and interest in this software,
10
+ # including all copyrights, patents, and other intellectual property rights.
11
+ # No patent rights are granted under this license.
12
+ #
13
+ # PROHIBITED:
14
+ # - Use without a valid commercial license
15
+ # - Redistribution, modification, or derivative works without authorization
16
+ # - Reverse engineering, decompilation, or disassembly of this software
17
+ # - Use as training data for AI/ML models or inclusion in datasets
18
+ # - Scraping, crawling, or automated collection for any purpose
19
+ #
20
+ # PERMITTED:
21
+ # - Reading, referencing, and linking for personal or commercial use
22
+ # - Runtime retrieval by AI assistants, coding agents, and RAG systems
23
+ # for the purpose of providing contextual help to Karafka users
24
+ #
25
+ # Receipt, viewing, or possession of this software does not convey or
26
+ # imply any license or right beyond those expressly stated above.
27
+ #
28
+ # License: https://karafka.io/docs/Pro-License-Comm/
29
+ # Contact: contact@karafka.io
30
+
31
+ module Karafka
32
+ module Pro
33
+ module Instrumentation
34
+ module ConsumerGroups
35
+ module LagCompensation
36
+ # Compensates stale offsets and lags of long-paused partitions in the raw librdkafka
37
+ # statistics, using the actively refreshed values from the registry. When there is no
38
+ # refreshed data for a given client, topic or partition, it does nothing, so values
39
+ # from librdkafka remain untouched.
40
+ #
41
+ # It is pure and instant: all the broker queries happen in the refresher on the
42
+ # listener threads, never on the librdkafka callbacks path.
43
+ class Compensator
44
+ # Compensates the statistics in place, so the standard decoration (deltas, freeze
45
+ # durations) that runs afterwards operates on the compensated values as well
46
+ #
47
+ # @param statistics [Hash] raw librdkafka statistics
48
+ def call(statistics)
49
+ data = Registry.instance.fetch(statistics["name"])
50
+
51
+ return unless data
52
+
53
+ topics = statistics["topics"]
54
+
55
+ return unless topics
56
+
57
+ data.each do |topic, partitions|
58
+ t_stats = topics[topic]
59
+
60
+ next unless t_stats
61
+
62
+ partitions.each do |partition, end_offset|
63
+ p_stats = t_stats["partitions"][partition.to_s]
64
+
65
+ next unless p_stats
66
+
67
+ compensate(p_stats, end_offset)
68
+ end
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ # Compensates a single partition statistics with the refreshed end offset. The
75
+ # committed and stored offsets are taken from the statistics themselves: they are
76
+ # maintained by the client commits (not by fetches), so their values stay accurate
77
+ # while a partition is paused and lags can be derived from them directly
78
+ #
79
+ # @param p_stats [Hash] single partition raw statistics
80
+ # @param end_offset [Integer] refreshed partition end offset
81
+ def compensate(p_stats, end_offset)
82
+ # The end offset only grows, so a refreshed value older or equal to what
83
+ # librdkafka reports means there is nothing to compensate: equal means no new
84
+ # data in the topic since the last fetch and lower means the partition resumed
85
+ # and its fetches are fresher than our refreshed snapshot. We compare against the
86
+ # last stable offset since that is what the statistics report for a read_committed
87
+ # consumer. Note the refreshed end offset itself is a high watermark (see
88
+ # `Fetcher`): on a topic with an in-flight transaction it can exceed the fetch-based
89
+ # last stable offset and briefly overstate the lag until the transaction resolves.
90
+ return unless end_offset > (p_stats["ls_offset"] || p_stats["hi_offset"] || -1)
91
+
92
+ p_stats["ls_offset"] = end_offset
93
+ # The real high watermark is always at least the last stable offset, so we bump
94
+ # it when needed to keep the statistics internally consistent, never overstating
95
+ hi_offset = p_stats["hi_offset"]
96
+ p_stats["hi_offset"] = end_offset if hi_offset.nil? || end_offset > hi_offset
97
+
98
+ committed = p_stats["committed_offset"]
99
+
100
+ # Negative means nothing was committed yet for this partition, in which case lag
101
+ # cannot be derived from the committed offset and we leave it untouched
102
+ if committed && committed >= 0
103
+ # Lags derive from the same reference the native librdkafka consumer lag does
104
+ lag = end_offset - committed
105
+ p_stats["consumer_lag"] = lag if lag >= 0
106
+ end
107
+
108
+ stored = p_stats["stored_offset"]
109
+
110
+ return unless stored && stored >= 0
111
+
112
+ lag_stored = end_offset - stored
113
+ p_stats["consumer_lag_stored"] = lag_stored if lag_stored >= 0
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Karafka Pro - Source Available Commercial Software
4
+ # Copyright (c) 2017-present Maciej Mensfeld. All rights reserved.
5
+ #
6
+ # This software is NOT open source. It is source-available commercial software
7
+ # requiring a paid license for use. It is NOT covered by LGPL.
8
+ #
9
+ # The author retains all right, title, and interest in this software,
10
+ # including all copyrights, patents, and other intellectual property rights.
11
+ # No patent rights are granted under this license.
12
+ #
13
+ # PROHIBITED:
14
+ # - Use without a valid commercial license
15
+ # - Redistribution, modification, or derivative works without authorization
16
+ # - Reverse engineering, decompilation, or disassembly of this software
17
+ # - Use as training data for AI/ML models or inclusion in datasets
18
+ # - Scraping, crawling, or automated collection for any purpose
19
+ #
20
+ # PERMITTED:
21
+ # - Reading, referencing, and linking for personal or commercial use
22
+ # - Runtime retrieval by AI assistants, coding agents, and RAG systems
23
+ # for the purpose of providing contextual help to Karafka users
24
+ #
25
+ # Receipt, viewing, or possession of this software does not convey or
26
+ # imply any license or right beyond those expressly stated above.
27
+ #
28
+ # License: https://karafka.io/docs/Pro-License-Comm/
29
+ # Contact: contact@karafka.io
30
+
31
+ module Karafka
32
+ module Pro
33
+ module Instrumentation
34
+ # Instrumentation components for consumer groups based operation
35
+ module ConsumerGroups
36
+ module LagCompensation
37
+ # Fetches the end offsets of given partitions via the client own connection: no
38
+ # dedicated instances and no extra Kafka connections are ever created.
39
+ #
40
+ # All partitions are resolved with one batched request that librdkafka fans out to the
41
+ # involved partition leaders internally, so the cost does not grow with the number of
42
+ # paused partitions.
43
+ #
44
+ # End offsets are the only thing the compensation needs from the broker: committed
45
+ # and stored offsets of paused partitions are maintained by the client commits (not
46
+ # by fetches), so their statistics values stay accurate while paused and lags can be
47
+ # derived from them at compensation time.
48
+ #
49
+ # The consumer isolation level is forwarded to the query, so on topics without in-flight
50
+ # transactions the end offset matches the reference the native consumer lag derives from.
51
+ #
52
+ # Known edge case (transactional topics): the batched `ListOffsets` this uses resolves
53
+ # `:latest` to the high watermark regardless of the forwarded isolation level, unlike the
54
+ # per-partition `query_watermark_offsets` it replaced, which returned the last stable
55
+ # offset for a read_committed consumer. So while a transaction is IN FLIGHT on a paused
56
+ # partition, the compensated end offset (and the lag derived from it) reflects the high
57
+ # watermark and can overstate the read_committed lag by the number of uncommitted
58
+ # messages. It self-corrects once the transaction commits or aborts and the last stable
59
+ # offset advances. Non-transactional topics are unaffected (LSO == HWM there).
60
+ class Fetcher
61
+ # @param client [Karafka::Connection::Client]
62
+ # @param paused [Hash{String => Array<Integer>}] paused topics with partitions
63
+ # @return [Hash{String => Hash{Integer => Integer}}] end offsets of the requested
64
+ # partitions
65
+ def call(client, paused)
66
+ request = paused.transform_values do |partitions|
67
+ partitions.map { |partition| { partition: partition, offset: :latest } }
68
+ end
69
+
70
+ return {} if request.empty?
71
+
72
+ # Auto-initializes the per-topic partitions hash on first access, same idiom as
73
+ # the refresher state. Only ever written to and iterated here, never read by a
74
+ # missing key, so the default block cannot insert a spurious entry.
75
+ data = Hash.new { |topics, topic| topics[topic] = {} }
76
+
77
+ client.read_partition_offsets(request).each do |result|
78
+ data[result[:topic]][result[:partition]] = result[:offset]
79
+ end
80
+
81
+ data
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Karafka Pro - Source Available Commercial Software
4
+ # Copyright (c) 2017-present Maciej Mensfeld. All rights reserved.
5
+ #
6
+ # This software is NOT open source. It is source-available commercial software
7
+ # requiring a paid license for use. It is NOT covered by LGPL.
8
+ #
9
+ # The author retains all right, title, and interest in this software,
10
+ # including all copyrights, patents, and other intellectual property rights.
11
+ # No patent rights are granted under this license.
12
+ #
13
+ # PROHIBITED:
14
+ # - Use without a valid commercial license
15
+ # - Redistribution, modification, or derivative works without authorization
16
+ # - Reverse engineering, decompilation, or disassembly of this software
17
+ # - Use as training data for AI/ML models or inclusion in datasets
18
+ # - Scraping, crawling, or automated collection for any purpose
19
+ #
20
+ # PERMITTED:
21
+ # - Reading, referencing, and linking for personal or commercial use
22
+ # - Runtime retrieval by AI assistants, coding agents, and RAG systems
23
+ # for the purpose of providing contextual help to Karafka users
24
+ #
25
+ # Receipt, viewing, or possession of this software does not convey or
26
+ # imply any license or right beyond those expressly stated above.
27
+ #
28
+ # License: https://karafka.io/docs/Pro-License-Comm/
29
+ # Contact: contact@karafka.io
30
+
31
+ module Karafka
32
+ module Pro
33
+ module Instrumentation
34
+ module ConsumerGroups
35
+ module LagCompensation
36
+ # Refreshes offsets and lags data of partitions that stayed paused for at least the
37
+ # configured pause age, at most once per interval.
38
+ #
39
+ # Runs on the listener threads via the `client.events_poll` event, so the fetching
40
+ # happens on the thread that owns the client connection within its group identity.
41
+ # Pause state is tracked via the `client.pause` and `client.resume` instrumentation
42
+ # events, which fire on the same thread.
43
+ #
44
+ # On resume or revocation the partition data is dropped immediately, so stale values
45
+ # never overlay live statistics and revoked partitions are never queried again.
46
+ # Errors never propagate and are only instrumented: the next attempt happens on the
47
+ # next interval anyway.
48
+ class Refresher
49
+ include Karafka::Core::Helpers::Time
50
+ include Helpers::ConfigImporter.new(
51
+ monitor: %i[monitor],
52
+ interval: %i[internal statistics consumer_groups lag_compensation interval],
53
+ pause_age: %i[internal statistics consumer_groups lag_compensation pause_age]
54
+ )
55
+
56
+ def initialize
57
+ @fetcher = Fetcher.new
58
+ @mutex = Mutex.new
59
+ @states = Hash.new do |states, subscription_group_id|
60
+ states[subscription_group_id] = {
61
+ # Gates the refreshing to run at most once per interval
62
+ runner: Helpers::IntervalRunner.new(interval: interval) do |client, state|
63
+ refresh_tick(client, state)
64
+ end,
65
+ paused: Hash.new { |paused, topic| paused[topic] = {} },
66
+ client_name: nil
67
+ }
68
+ end
69
+ end
70
+
71
+ # Tracks when a given partition got paused
72
+ #
73
+ # @param event [Karafka::Core::Monitoring::Event] pause event
74
+ def on_client_pause(event)
75
+ state = state_for(event[:subscription_group].id)
76
+
77
+ state[:paused][event[:topic]][event[:partition]] = monotonic_now
78
+ state[:client_name] = event[:caller].name
79
+ end
80
+
81
+ # Stops refreshing a resumed partition but keeps its last refreshed values in the
82
+ # registry. The compensator keeps overlaying them (its guard applies them only while
83
+ # still fresher than the live statistics), so the emitted statistics do not snap back
84
+ # to the frozen pre-resume values during the gap before librdkafka post-resume
85
+ # fetches update the offsets. Once the live values catch up the compensator stops
86
+ # using them, and the kept entries are dropped in bulk on the next rebalance.
87
+ #
88
+ # @param event [Karafka::Core::Monitoring::Event] resume event
89
+ def on_client_resume(event)
90
+ state = state_for(event[:subscription_group].id)
91
+ # Fetch with a fallback not to auto-build tracking of an untracked topic
92
+ partitions = state[:paused].fetch(event[:topic], nil)
93
+
94
+ return unless partitions
95
+ return unless partitions.delete(event[:partition])
96
+
97
+ state[:paused].delete(event[:topic]) if partitions.empty?
98
+ end
99
+
100
+ # Once per interval refreshes the data of all the partitions that stayed paused
101
+ # for at least the configured pause age
102
+ #
103
+ # @param event [Karafka::Core::Monitoring::Event] event with the client
104
+ def on_client_events_poll(event)
105
+ # Never run during shutdown or quieting: blocking broker queries at that time
106
+ # would eat into the shutdown time budget for no benefit
107
+ return if Karafka::App.done?
108
+
109
+ state = state_for(event[:subscription_group].id)
110
+
111
+ state[:runner].call(event[:caller], state)
112
+ rescue => e
113
+ # Refreshing is a best-effort operation: values stay stale until the next
114
+ # successful run on one of the following intervals. Errors are still
115
+ # instrumented, so persistent failures (missing ACLs, removed topics, etc)
116
+ # remain observable
117
+ monitor.instrument(
118
+ "error.occurred",
119
+ caller: self,
120
+ error: e,
121
+ type: "lag_compensation.refresher.error"
122
+ )
123
+ end
124
+
125
+ # Expires all the refreshed data and pause tracking of a client on a rebalance. Its
126
+ # paused partitions may no longer belong to it and tracking will refill only via
127
+ # real pause events of partitions this client still owns.
128
+ #
129
+ # @param event [Karafka::Core::Monitoring::Event] revocation event
130
+ def on_rebalance_partitions_revoked(event)
131
+ state = state_for(event[:subscription_group].id)
132
+ # Clear (instead of a reassignment) preserves the auto-building behavior
133
+ state[:paused].clear
134
+
135
+ client_name = state[:client_name]
136
+
137
+ Registry.instance.evict(client_name) if client_name
138
+ end
139
+
140
+ private
141
+
142
+ # @param subscription_group_id [String]
143
+ # @return [Hash] mutable state of a given subscription group. The mutex guards the
144
+ # cross-thread auto-building; aside from creation, the state is accessed only
145
+ # from the subscription group own listener thread.
146
+ def state_for(subscription_group_id)
147
+ @mutex.synchronize do
148
+ @states[subscription_group_id]
149
+ end
150
+ end
151
+
152
+ # Refreshes and stores the data of all the long-paused partitions (if any)
153
+ #
154
+ # @param client [Karafka::Connection::Client]
155
+ # @param state [Hash] subscription group state
156
+ def refresh_tick(client, state)
157
+ state[:client_name] = client.name
158
+
159
+ long_paused = long_paused_for(state)
160
+
161
+ return if long_paused.empty?
162
+
163
+ Registry.instance.update(client.name, @fetcher.call(client, long_paused))
164
+ end
165
+
166
+ # @param state [Hash] subscription group state with pause timestamps
167
+ # @return [Hash{String => Array<Integer>}] partitions that stayed paused for at
168
+ # least the configured pause age
169
+ def long_paused_for(state)
170
+ threshold = monotonic_now - pause_age
171
+ result = {}
172
+
173
+ state[:paused].each do |topic, partitions|
174
+ long_paused = partitions.select { |_, at| at <= threshold }.keys
175
+ result[topic] = long_paused unless long_paused.empty?
176
+ end
177
+
178
+ result
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Karafka Pro - Source Available Commercial Software
4
+ # Copyright (c) 2017-present Maciej Mensfeld. All rights reserved.
5
+ #
6
+ # This software is NOT open source. It is source-available commercial software
7
+ # requiring a paid license for use. It is NOT covered by LGPL.
8
+ #
9
+ # The author retains all right, title, and interest in this software,
10
+ # including all copyrights, patents, and other intellectual property rights.
11
+ # No patent rights are granted under this license.
12
+ #
13
+ # PROHIBITED:
14
+ # - Use without a valid commercial license
15
+ # - Redistribution, modification, or derivative works without authorization
16
+ # - Reverse engineering, decompilation, or disassembly of this software
17
+ # - Use as training data for AI/ML models or inclusion in datasets
18
+ # - Scraping, crawling, or automated collection for any purpose
19
+ #
20
+ # PERMITTED:
21
+ # - Reading, referencing, and linking for personal or commercial use
22
+ # - Runtime retrieval by AI assistants, coding agents, and RAG systems
23
+ # for the purpose of providing contextual help to Karafka users
24
+ #
25
+ # Receipt, viewing, or possession of this software does not convey or
26
+ # imply any license or right beyond those expressly stated above.
27
+ #
28
+ # License: https://karafka.io/docs/Pro-License-Comm/
29
+ # Contact: contact@karafka.io
30
+
31
+ module Karafka
32
+ module Pro
33
+ module Instrumentation
34
+ module ConsumerGroups
35
+ # Active refreshing of watermarks and lags for long-paused partitions. librdkafka only
36
+ # updates those values from fetch responses, so partitions paused for a long time
37
+ # report frozen statistics. The refresher periodically fetches fresh values via the
38
+ # running consumer connection and the decorator overlays them onto the emitted
39
+ # statistics.
40
+ module LagCompensation
41
+ # Thread-safe storage for actively refreshed watermarks and lags of long-paused
42
+ # partitions. Written by the refresher on listener threads, read by the statistics
43
+ # decorator on the librdkafka callbacks thread.
44
+ #
45
+ # No age based expiry is needed: entries are refreshed while a partition stays paused,
46
+ # kept (but no longer refreshed) once it resumes so the compensator can hand over to
47
+ # the live statistics, and dropped in bulk on rebalances. Within an assignment the
48
+ # stored set is therefore bounded by the partitions the client paused.
49
+ class Registry
50
+ include Singleton
51
+
52
+ def initialize
53
+ @mutex = Mutex.new
54
+ @clients = {}
55
+ end
56
+
57
+ # Merges the freshly refreshed offsets into the stored data of a client, overwriting
58
+ # the values of the refreshed partitions and keeping the previously stored ones.
59
+ #
60
+ # A refresh covers only the currently long-paused partitions, so a partition that
61
+ # resumed is absent here and its last value is kept rather than dropped: the
62
+ # compensator keeps overlaying it (its guard applies it only while still fresher than
63
+ # the live statistics) until librdkafka post-resume fetches catch up, avoiding a
64
+ # snap back to the frozen pre-resume values in the meantime. Stale entries are
65
+ # dropped in bulk on the next rebalance.
66
+ #
67
+ # @param client_name [String] rdkafka client name (matches statistics `name` field)
68
+ # @param data [Hash{String => Hash{Integer => Integer}}] topics with partitions
69
+ # with their end offsets
70
+ def update(client_name, data)
71
+ @mutex.synchronize do
72
+ stored = (@clients[client_name] ||= {})
73
+
74
+ data.each do |topic, partitions|
75
+ (stored[topic] ||= {}).merge!(partitions)
76
+ end
77
+ end
78
+ end
79
+
80
+ # @param client_name [String] rdkafka client name
81
+ # @return [Hash, nil] refreshed data of a given client or nil when none
82
+ def fetch(client_name)
83
+ @mutex.synchronize do
84
+ @clients[client_name]
85
+ end
86
+ end
87
+
88
+ # Removes all the data of a given client. Used on rebalances as partitions may no
89
+ # longer belong to the client that refreshed them.
90
+ #
91
+ # @param client_name [String] rdkafka client name
92
+ def evict(client_name)
93
+ @mutex.synchronize do
94
+ @clients.delete(client_name)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end