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
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  # Consumer-group-specific job types
9
7
  module Jobs
@@ -11,7 +9,8 @@ module Karafka
11
9
  class Eofed < ::Karafka::Processing::Jobs::Base
12
10
  self.action = :eofed
13
11
 
14
- # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is supposed to run the job
12
+ # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is
13
+ # supposed to run the job
15
14
  # @return [Eofed]
16
15
  def initialize(executor)
17
16
  @executor = executor
@@ -9,8 +9,8 @@ module Karafka
9
9
  class Idle < ::Karafka::Processing::Jobs::Base
10
10
  self.action = :idle
11
11
 
12
- # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is supposed to run a given
13
- # job on an active consumer
12
+ # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is
13
+ # supposed to run a given job on an active consumer
14
14
  # @return [Idle]
15
15
  def initialize(executor)
16
16
  @executor = executor
@@ -2,16 +2,14 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
- # Consumer-group-specific job types
9
6
  module Jobs
10
7
  # Job that runs the revoked operation when we lose a partition on a consumer that lost it.
11
8
  class Revoked < ::Karafka::Processing::Jobs::Base
12
9
  self.action = :revoked
13
10
 
14
- # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is supposed to run the job
11
+ # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is
12
+ # supposed to run the job
15
13
  # @return [Revoked]
16
14
  def initialize(executor)
17
15
  @executor = executor
@@ -8,8 +8,8 @@ module Karafka
8
8
  class Shutdown < ::Karafka::Processing::Jobs::Base
9
9
  self.action = :shutdown
10
10
 
11
- # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is supposed to run a given
12
- # job on an active consumer
11
+ # @param executor [Karafka::Processing::ConsumerGroups::Executor] executor that is
12
+ # supposed to run a given job on an active consumer
13
13
  # @return [Shutdown]
14
14
  def initialize(executor)
15
15
  @executor = executor
@@ -2,11 +2,8 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
- # Basic partitioner for work division
9
- # It does not divide any work.
6
+ # Basic partitioner for work division. It does not divide any work.
10
7
  class Partitioner
11
8
  # @param subscription_group [Karafka::Routing::SubscriptionGroup] subscription group
12
9
  def initialize(subscription_group)
@@ -15,8 +12,8 @@ module Karafka
15
12
 
16
13
  # @param _topic [String] topic name
17
14
  # @param messages [Array<Karafka::Messages::Message>] karafka messages
18
- # @param _coordinator [Karafka::Processing::ConsumerGroups::Coordinator] processing coordinator that will
19
- # be used with those messages
15
+ # @param _coordinator [Karafka::Processing::ConsumerGroups::Coordinator] processing
16
+ # coordinator that will be used with those messages
20
17
  # @yieldparam [Integer] group id
21
18
  # @yieldparam [Array<Karafka::Messages::Message>] karafka messages
22
19
  def call(_topic, messages, _coordinator)
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  module Strategies
9
7
  # ActiveJob strategy to cooperate with the DLQ.
@@ -28,6 +26,11 @@ module Karafka
28
26
  if coordinator.success?
29
27
  # Do NOT commit offsets, they are comitted after each job in the AJ consumer.
30
28
  coordinator.pause_tracker.reset
29
+ # Process-critical errors are never dispatched to the DLQ regardless of the retries
30
+ # state: the retry pause protects the partition during the critical shutdown and
31
+ # the failed batch is redelivered after the restart
32
+ elsif critical_error?(coordinator.consumption(self).cause)
33
+ retry_after_pause
31
34
  elsif coordinator.pause_tracker.attempt <= topic.dead_letter_queue.max_retries
32
35
  retry_after_pause
33
36
  else
@@ -2,12 +2,10 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  module Strategies
9
- # ActiveJob enabled
10
- # Manual offset management enabled
7
+ # - ActiveJob enabled
8
+ # - Manual offset management enabled
11
9
  #
12
10
  # This is the default AJ strategy since AJ cannot be used without MOM
13
11
  module AjMom
@@ -2,12 +2,10 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
- # Our processing patterns differ depending on various features configurations
9
- # In this namespace we collect strategies for particular feature combinations to simplify the
10
- # design. Based on features combinations we can then select handling strategy for a given case.
6
+ # Our processing patterns differ depending on various features configurations. In this
7
+ # namespace we collect strategies for particular feature combinations to simplify the design.
8
+ # Based on features combinations we can then select handling strategy for a given case.
11
9
  #
12
10
  # @note The lack of common code here is intentional. It would get complex if there would be
13
11
  # any type of composition, so each strategy is expected to be self-sufficient
@@ -2,13 +2,12 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  module Strategies
9
- # No features enabled.
10
- # No manual offset management
11
- # No long running jobs
7
+ # No features enabled:
8
+ # - No manual offset management
9
+ # - No long running jobs
10
+ #
12
11
  # Nothing. Just standard, automatic flow
13
12
  module Default
14
13
  include Base
@@ -59,9 +58,18 @@ module Karafka
59
58
  # In case like this we ignore marking
60
59
  return true if seek_offset.nil?
61
60
  # Ignore double markings of the same offset
61
+ # Only this exact re-mark is skipped - marking genuinely older offsets is intentionally
62
+ # allowed and rewinds the seek offset for reprocessing (see #2432)
62
63
  return true if (seek_offset - 1) == message.offset
63
64
  return false if revoked?
64
- return revoked? unless client.mark_as_consumed(message)
65
+ unless client.mark_as_consumed(message)
66
+ # Evaluate ownership for its coordinator-revoking side effect - all marking
67
+ # failure causes are ownership related. The failure itself always reports false:
68
+ # the offset was not stored, regardless of the current ownership state
69
+ revoked?
70
+
71
+ return false
72
+ end
65
73
 
66
74
  self.seek_offset = message.offset + 1
67
75
 
@@ -78,10 +86,19 @@ module Karafka
78
86
  # In case like this we ignore marking
79
87
  return true if seek_offset.nil?
80
88
  # Ignore double markings of the same offset
89
+ # Only this exact re-mark is skipped - marking genuinely older offsets is intentionally
90
+ # allowed and rewinds the seek offset for reprocessing (see #2432)
81
91
  return true if (seek_offset - 1) == message.offset
82
92
  return false if revoked?
83
93
 
84
- return revoked? unless client.mark_as_consumed!(message)
94
+ unless client.mark_as_consumed!(message)
95
+ # Evaluate ownership for its coordinator-revoking side effect - all marking
96
+ # failure causes are ownership related. The failure itself always reports false:
97
+ # the offset was not committed, regardless of the current ownership state
98
+ revoked?
99
+
100
+ return false
101
+ end
85
102
 
86
103
  self.seek_offset = message.offset + 1
87
104
 
@@ -91,17 +108,27 @@ module Karafka
91
108
  # Triggers an async offset commit
92
109
  #
93
110
  # @param async [Boolean] should we use async (default) or sync commit
94
- # @return [Boolean] true if we still own the partition.
111
+ # @return [Boolean] true if the commit went through and we still owned the partition
112
+ # at that time, false otherwise. False means the offsets were not committed - the
113
+ # caller can retry (the offsets stay stored client-side) or treat the partition as
114
+ # fenced.
95
115
  # @note Due to its async nature, this may not fully represent the offset state in some
96
116
  # edge cases (like for example going beyond max.poll.interval)
117
+ # @note A commit with no offsets pending is a client-side no-op reported as success
118
+ # without broker validation - the marking result is the fence for that case.
97
119
  def commit_offsets(async: true)
98
120
  # Do not commit if we already lost the assignment
99
121
  return false if revoked?
100
122
  return true if client.commit_offsets(async: async)
101
123
 
102
- # This will once more check the librdkafka revocation status and will revoke the
103
- # coordinator in case it was not revoked
124
+ # Evaluate ownership once more for its coordinator-revoking side effect: all
125
+ # commit failure causes the client reports as false are ownership related. The
126
+ # failed commit itself always reports false - its value must not depend on the
127
+ # revocation state, which may not yet be locally visible (or may indicate a
128
+ # retained partition under a cooperative rebalance generation bump)
104
129
  revoked?
130
+
131
+ false
105
132
  end
106
133
 
107
134
  # Triggers a synchronous offsets commit to Kafka
@@ -138,7 +165,10 @@ module Karafka
138
165
 
139
166
  # Mark job as successful
140
167
  coordinator.success!(self)
141
- rescue => e
168
+ # Failure recording must be class-agnostic: an unrecorded non-StandardError would
169
+ # leave the consumption result in its default (successful) state and the after-consume
170
+ # flow would mark the failed batch as consumed instead of engaging the retry
171
+ rescue Exception => e
142
172
  coordinator.failure!(self, e)
143
173
 
144
174
  # Re-raise so reported in the consumer
@@ -187,8 +217,8 @@ module Karafka
187
217
  end
188
218
 
189
219
  # We need to always un-pause the processing in case we have lost a given partition.
190
- # Otherwise the underlying librdkafka would not know we may want to continue processing and
191
- # the pause could in theory last forever
220
+ # Otherwise the underlying librdkafka would not know we may want to continue processing
221
+ # and the pause could in theory last forever
192
222
  def handle_revoked
193
223
  resume
194
224
 
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  module Strategies
9
7
  # When using dead letter queue, processing won't stop after defined number of retries
@@ -64,6 +62,11 @@ module Karafka
64
62
  return if coordinator.manual_pause?
65
63
 
66
64
  mark_as_consumed(messages.last)
65
+ # Process-critical errors are never dispatched to the DLQ regardless of the retries
66
+ # state: the retry pause protects the partition during the critical shutdown and
67
+ # the failed batch is redelivered after the restart
68
+ elsif critical_error?(coordinator.consumption(self).cause)
69
+ retry_after_pause
67
70
  elsif coordinator.pause_tracker.attempt <= topic.dead_letter_queue.max_retries
68
71
  retry_after_pause
69
72
  # If we've reached number of retries that we could, we need to skip the first message
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  module Strategies
9
7
  # Same as pure dead letter queue but we do not marked failed message as consumed
@@ -23,6 +21,11 @@ module Karafka
23
21
 
24
22
  if coordinator.success?
25
23
  coordinator.pause_tracker.reset
24
+ # Process-critical errors are never dispatched to the DLQ regardless of the retries
25
+ # state: the retry pause protects the partition during the critical shutdown and
26
+ # the failed batch is redelivered after the restart
27
+ elsif critical_error?(coordinator.consumption(self).cause)
28
+ retry_after_pause
26
29
  elsif coordinator.pause_tracker.attempt <= topic.dead_letter_queue.max_retries
27
30
  retry_after_pause
28
31
  # If we've reached number of retries that we could, we need to skip the first message
@@ -2,11 +2,10 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  module Strategies
9
- # When using manual offset management, we do not mark as consumed after successful processing
7
+ # When using manual offset management, we do not mark as consumed after successful
8
+ # processing
10
9
  module Mom
11
10
  include Default
12
11
 
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Karafka
4
4
  module Processing
5
- # Consumer-group-specific processing components (driven by rebalance callbacks and partition
6
- # ticks). Parallel `ShareGroups` will live next to this namespace once KIP-932 lands.
7
5
  module ConsumerGroups
8
6
  # Selector of appropriate processing strategy matching topic combinations
9
7
  class StrategySelector
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Backwards-compatible alias kept for karafka-web gem that references the old, un-namespaced
4
- # constant. Will be removed in Karafka 3.0.
5
3
  module Karafka
6
4
  module Processing
7
5
  # @see ConsumerGroups::Coordinator
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Backwards-compatible alias kept for karafka-testing gem that references the old, un-namespaced
4
- # constant. Will be removed in Karafka 3.0.
5
3
  module Karafka
6
4
  module Processing
7
5
  # @see ConsumerGroups::CoordinatorsBuffer
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Backwards-compatible alias kept for external code that references the old, un-namespaced
4
- # constant. Will be removed in Karafka 3.0.
5
3
  module Karafka
6
4
  module Processing
7
5
  # @see ConsumerGroups::Partitioner
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Backwards-compatible alias kept for external code that references the old, un-namespaced
4
- # constant. Will be removed in Karafka 3.0.
5
3
  module Karafka
6
4
  module Processing
7
5
  # @see ConsumerGroups::StrategySelector
@@ -45,10 +45,16 @@ module Karafka
45
45
 
46
46
  # Fetches a single job, processes it and marks as completed.
47
47
  #
48
- # @note We do not have error handling here, as no errors should propagate this far. If they
49
- # do, it is a critical error and should bubble up.
48
+ # @note No user or framework errors should propagate this far. If they do, it is a critical
49
+ # error that we instrument while keeping the worker alive.
50
50
  #
51
51
  # @note Upon closing the jobs queue, worker will close it's thread
52
+ #
53
+ # @return [Boolean] should the worker keep running. False only when the jobs queue is
54
+ # closed (full shutdown) or a downscale sentinel was received. This decision is
55
+ # deliberately decoupled from the processing flow result: the flow passes through the
56
+ # user-redefinable `#wrap`, whose return value (or an error raised in user code or in an
57
+ # instrumentation subscriber) must never be able to silently kill the worker thread.
52
58
  def process
53
59
  job = @jobs_queue.pop
54
60
 
@@ -76,10 +82,10 @@ module Karafka
76
82
  end
77
83
 
78
84
  job.after_call
79
-
80
- true
81
85
  end
82
86
  end
87
+
88
+ true
83
89
  else
84
90
  # nil means either queue closed (full shutdown) or pool downscaling.
85
91
  # Either way, deregister from pool so it reflects the actual worker count.
@@ -87,9 +93,7 @@ module Karafka
87
93
  false
88
94
  end
89
95
  # We signal critical exceptions, notify and do not allow worker to fail
90
- # rubocop:disable Lint/RescueException
91
96
  rescue Exception => e
92
- # rubocop:enable Lint/RescueException
93
97
  monitor.instrument(
94
98
  "error.occurred",
95
99
  caller: self,
@@ -98,6 +102,10 @@ module Karafka
98
102
  error: e,
99
103
  type: "worker.process.error"
100
104
  )
105
+
106
+ # Keep the worker alive. Without an explicit true this arm would return the result of
107
+ # the instrumentation call (nil) and the worker thread would silently exit
108
+ true
101
109
  ensure
102
110
  # job can be nil when the queue is being closed or during pool downscaling
103
111
  if job
@@ -39,6 +39,13 @@ module Karafka
39
39
  # after a worker exits, so thread names remain unique across the lifetime of the process
40
40
  # and make it easy to correlate log entries with specific worker generations.
41
41
  @next_index = 0
42
+ # Number of downscale sentinels enqueued but not yet picked up by a worker. Workers
43
+ # deregister asynchronously, so the live thread count alone overstates the pool between
44
+ # a scale-down request and its completion. All sizing decisions use the committed size
45
+ # (live workers minus pending shrinks) so repeated or overlapping scale-down requests do
46
+ # not enqueue duplicate sentinels and drain the pool below the requested target (or
47
+ # below the minimum of one worker).
48
+ @pending_shrinks = 0
42
49
  end
43
50
 
44
51
  # Scale pool towards `target` workers (minimum 1).
@@ -63,7 +70,9 @@ module Karafka
63
70
  event = nil
64
71
 
65
72
  @mutex.synchronize do
66
- current = @workers.size
73
+ # Compute against the committed size, not the live thread count: sentinels already in
74
+ # flight are removals this pool is committed to, just not yet executed by the workers
75
+ current = @workers.size - @pending_shrinks
67
76
  delta = target - current
68
77
 
69
78
  if delta.positive?
@@ -106,6 +115,11 @@ module Karafka
106
115
  @mutex.synchronize do
107
116
  @workers.delete(worker)
108
117
  @size = @workers.size
118
+ # A worker exits either because it picked up a downscale sentinel or because the jobs
119
+ # queue was closed for shutdown. The worker cannot tell those apart, so pending
120
+ # removals are settled here, with a floor because shutdown exits may have no pending
121
+ # shrink to consume
122
+ @pending_shrinks -= 1 if @pending_shrinks.positive?
109
123
  end
110
124
  end
111
125
 
@@ -142,14 +156,17 @@ module Karafka
142
156
  #
143
157
  # @param count [Integer] number of workers to remove
144
158
  # @return [Array, nil] instrumentation event args or nil if no-op
145
- # @note Never shrinks below 1 worker.
159
+ # @note Never shrinks below 1 worker, including removals already in flight - the cap is
160
+ # computed against the committed size, not the live thread count.
146
161
  def shrink(count)
147
- effective = [count, @workers.size - 1].min
162
+ committed = @workers.size - @pending_shrinks
163
+ effective = [count, committed - 1].min
148
164
  return if effective <= 0
149
165
 
150
166
  from = @workers.size
167
+ @pending_shrinks += effective
151
168
  effective.times { @jobs_queue << nil }
152
- to = from - effective
169
+ to = committed - effective
153
170
 
154
171
  ["worker.scaling.down", { workers_pool: self, from: from, to: to }]
155
172
  end
@@ -3,7 +3,6 @@
3
3
  # This file contains Railtie for auto-configuration
4
4
 
5
5
  unless Karafka.rails?
6
- # Without defining this in any way, Zeitwerk ain't happy so we do it that way
7
6
  module Karafka
8
7
  class Railtie
9
8
  end
@@ -12,8 +12,8 @@ module Karafka
12
12
  end
13
13
 
14
14
  # Ensures, that when declarative topics strict requirement is on, all topics have
15
- # declarative definition (including DLQ topics)
16
- # @note It will ignore routing pattern topics because those topics are virtual
15
+ # declarative definition (including DLQ topics). It will ignore routing pattern topics
16
+ # because those topics are virtual
17
17
  virtual do |data, errors|
18
18
  next unless errors.empty?
19
19
  # Do not validate declaratives unless required and explicitly enabled
@@ -7,11 +7,12 @@ module Karafka
7
7
  class ActiveJob < Base
8
8
  # Topic extensions to be able to check if given topic is ActiveJob topic
9
9
  #
10
- # @note ActiveJob topics do not have per-topic deserializer configuration. The deserializer
11
- # is configured globally via `config.internal.active_job.deserializer` because Rails
12
- # serializes jobs before dispatching them, requiring a consistent serialization format
13
- # across all ActiveJob topics. If you need custom serialization (e.g., Avro, Protobuf),
14
- # configure it once at the application level rather than per-topic.
10
+ # @note ActiveJob topics do not have per-topic deserializer configuration. The
11
+ # deserializer is configured globally via `config.internal.active_job.deserializer`
12
+ # because Rails serializes jobs before dispatching them, requiring a consistent
13
+ # serialization format across all ActiveJob topics. If you need custom serialization
14
+ # (e.g., Avro, Protobuf), configure it once at the application level rather than
15
+ # per-topic.
15
16
  module Topic
16
17
  # This method sets up the extra instance variable to nil before calling
17
18
  # the parent class initializer. The explicit initialization
@@ -16,8 +16,8 @@ module Karafka
16
16
  super
17
17
  end
18
18
 
19
- # @param active [Boolean] should we stop managing the offset in Karafka and make the user
20
- # responsible for marking messages as consumed.
19
+ # @param active [Boolean] should we stop managing the offset in Karafka and make the
20
+ # user responsible for marking messages as consumed.
21
21
  # @return [Config] defined config
22
22
  #
23
23
  # @note Since this feature supports only one setting (active), we can use the old API
@@ -3,7 +3,6 @@
3
3
  module Karafka
4
4
  module Routing
5
5
  module Features
6
- # Deserializers for all the message details (payload, headers, key)
7
6
  class Deserializers < Base
8
7
  # Routing topic deserializers API. It allows to configure deserializers for various
9
8
  # components of each message.
@@ -16,7 +16,7 @@ module Karafka
16
16
 
17
17
  attr_reader :id, :name, :topics, :kafka, :group, :position
18
18
 
19
- # Backwards compatible alias for `#group`. Kept purely for compatibility this is an
19
+ # Backwards compatible alias for `#group`. Kept purely for compatibility - this is an
20
20
  # unconditional alias and performs no type validation, so callers should prefer `#group`
21
21
  # once additional group types (e.g. KIP-932 share groups) land.
22
22
  alias_method :consumer_group, :group
@@ -14,7 +14,7 @@ module Karafka
14
14
  attr_reader :id, :name, :group
15
15
  attr_writer :consumer
16
16
 
17
- # Backwards compatible alias for `#group`. Kept purely for compatibility this is an
17
+ # Backwards compatible alias for `#group`. Kept purely for compatibility - this is an
18
18
  # unconditional alias and performs no type validation, so callers should prefer `#group`
19
19
  # once additional group types (e.g. KIP-932 share groups) land.
20
20
  alias_method :consumer_group, :group
@@ -47,9 +47,9 @@ module Karafka
47
47
  @group = group
48
48
  @attributes = {}
49
49
  @active = true
50
- # @note We use identifier related to the group that owns a topic, because from Karafka 0.6
51
- # we can handle multiple Kafka instances with the same process and we can have same
52
- # topic name across multiple groups
50
+ # We use identifier related to the group that owns a topic, because from Karafka 0.6 we can
51
+ # handle multiple Kafka instances with the same process and we can have same topic name
52
+ # across multiple groups
53
53
  @id = "#{group.id}_#{@name}"
54
54
  @consumer = nil
55
55
  @active_assigned = false
@@ -7,13 +7,33 @@ module Karafka
7
7
  include Enumerable
8
8
  extend Forwardable
9
9
 
10
- def_delegators :@accumulator, :[], :size, :empty?, :last, :<<, :map!, :sort_by!, :reverse!
10
+ def_delegators :@accumulator, :[], :size, :empty?, :last, :map!, :sort_by!, :reverse!
11
11
 
12
12
  # @param topics_array [Array<Karafka::Routing::Topic>] array with topics
13
13
  def initialize(topics_array)
14
14
  @accumulator = topics_array.dup
15
15
  end
16
16
 
17
+ # Adds a topic using copy-on-write: we publish a brand-new accumulator array instead of
18
+ # mutating the existing one in place.
19
+ #
20
+ # @param topic [Karafka::Routing::Topic] topic that should become part of this consumer
21
+ # group's topics
22
+ # @return [self]
23
+ #
24
+ # @note Topics can be appended to this collection at runtime from one thread while other
25
+ # threads iterate the same collection (for example during routing lookups). Swapping the
26
+ # `@accumulator` reference atomically (rather than appending in place) guarantees a
27
+ # concurrent `#each` always traverses a complete, immutable snapshot: it either sees the
28
+ # newly added topic or it does not, but never a torn array. This holds on every Ruby
29
+ # runtime, not only on MRI's GIL. The in-place mutators above (`map!`, `sort_by!`,
30
+ # `reverse!`) only run while routes are built at boot, single-threaded, so they do not
31
+ # need the same treatment.
32
+ def <<(topic)
33
+ @accumulator += [topic]
34
+ self
35
+ end
36
+
17
37
  # Yields each topic
18
38
  def each(&)
19
39
  @accumulator.each(&)
@@ -168,6 +168,15 @@ module Karafka
168
168
  # otherwise we would overwrite the shutdown process of the process that started Karafka
169
169
  return unless process.supervised?
170
170
 
171
+ # We intentionally do NOT flush or close the producer before the `Kernel.exit!` below.
172
+ # Forceful shutdown is a last resort - a job already exceeded `shutdown_timeout` - and it
173
+ # is expected that in-flight, async-buffered data (user `produce_async` calls and DLQ
174
+ # `produce_async` copies) may be lost. Flushing here would be risky precisely when it
175
+ # matters: the producer (or its connection pool) may itself be the resource that is blocked
176
+ # (e.g. an unreachable broker), so waiting on it could stall or hang the very forceful exit
177
+ # that must guarantee the process terminates. On a normal (graceful) shutdown the producer
178
+ # is still closed via the `ensure` block below, which `exit!` deliberately skips here.
179
+ #
171
180
  # exit! is not within the instrumentation as it would not trigger due to exit
172
181
  Kernel.exit!(forceful_exit_code)
173
182
  ensure