karafka 2.6.0.beta1 → 2.6.0.rc2

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 (218) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -27
  3. data/certs/expired.txt +2 -0
  4. data/config/locales/errors.yml +5 -0
  5. data/config/locales/pro_errors.yml +5 -5
  6. data/karafka.gemspec +1 -1
  7. data/lib/active_job/queue_adapters/karafka_adapter.rb +1 -1
  8. data/lib/karafka/active_job/current_attributes/loading.rb +1 -1
  9. data/lib/karafka/active_job/current_attributes.rb +4 -2
  10. data/lib/karafka/active_job/dispatcher.rb +1 -2
  11. data/lib/karafka/admin/consumer_groups.rb +8 -2
  12. data/lib/karafka/admin/replication.rb +6 -3
  13. data/lib/karafka/admin/topics.rb +32 -12
  14. data/lib/karafka/admin.rb +155 -59
  15. data/lib/karafka/app.rb +1 -1
  16. data/lib/karafka/base_consumer.rb +33 -5
  17. data/lib/karafka/cli/console.rb +0 -1
  18. data/lib/karafka/cli/help.rb +0 -1
  19. data/lib/karafka/cli/info.rb +0 -1
  20. data/lib/karafka/cli/install.rb +0 -1
  21. data/lib/karafka/cli/server.rb +0 -1
  22. data/lib/karafka/cli/swarm.rb +0 -1
  23. data/lib/karafka/cli.rb +2 -2
  24. data/lib/karafka/connection/client.rb +44 -7
  25. data/lib/karafka/connection/listener.rb +3 -3
  26. data/lib/karafka/connection/manager.rb +0 -1
  27. data/lib/karafka/connection/pauses_manager.rb +20 -3
  28. data/lib/karafka/connection/proxy.rb +45 -0
  29. data/lib/karafka/connection/status.rb +0 -1
  30. data/lib/karafka/instrumentation/assignments_tracker.rb +34 -0
  31. data/lib/karafka/instrumentation/callbacks/consumer_groups/decorator.rb +19 -0
  32. data/lib/karafka/instrumentation/callbacks/consumer_groups/error.rb +0 -1
  33. data/lib/karafka/instrumentation/callbacks/consumer_groups/statistics.rb +3 -2
  34. data/lib/karafka/instrumentation/critical_errors_listener.rb +85 -0
  35. data/lib/karafka/instrumentation/logger_listener.rb +2 -0
  36. data/lib/karafka/instrumentation/monitor.rb +2 -3
  37. data/lib/karafka/instrumentation/vendors/appsignal/errors_listener.rb +0 -1
  38. data/lib/karafka/instrumentation/vendors/appsignal/metrics_listener.rb +0 -1
  39. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +4 -3
  40. data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +0 -2
  41. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +181 -18
  42. data/lib/karafka/pro/admin/recovery/errors.rb +0 -1
  43. data/lib/karafka/pro/admin/recovery.rb +6 -4
  44. data/lib/karafka/pro/base_consumer.rb +5 -5
  45. data/lib/karafka/pro/cleaner/messages/metadata.rb +0 -1
  46. data/lib/karafka/pro/cli/parallel_segments/base.rb +1 -2
  47. data/lib/karafka/pro/connection/manager.rb +7 -4
  48. data/lib/karafka/pro/encryption/messages/middleware.rb +5 -0
  49. data/lib/karafka/pro/encryption/messages/parser.rb +4 -0
  50. data/lib/karafka/pro/instrumentation/callbacks/consumer_groups/decorator.rb +63 -0
  51. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/compensator.rb +120 -0
  52. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/fetcher.rb +88 -0
  53. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/refresher.rb +185 -0
  54. data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/registry.rb +102 -0
  55. data/lib/karafka/pro/instrumentation/performance_tracker.rb +40 -6
  56. data/lib/karafka/pro/iterator/tpl_builder.rb +27 -7
  57. data/lib/karafka/pro/iterator.rb +22 -7
  58. data/lib/karafka/pro/loader.rb +12 -0
  59. data/lib/karafka/pro/processing/consumer_groups/adaptive_iterator/consumer.rb +0 -3
  60. data/lib/karafka/pro/processing/consumer_groups/adaptive_iterator/tracker.rb +2 -4
  61. data/lib/karafka/pro/processing/consumer_groups/collapser.rb +6 -9
  62. data/lib/karafka/pro/processing/consumer_groups/coordinator.rb +1 -4
  63. data/lib/karafka/pro/processing/consumer_groups/coordinators/errors_tracker.rb +0 -3
  64. data/lib/karafka/pro/processing/consumer_groups/coordinators/filters_applier.rb +21 -10
  65. data/lib/karafka/pro/processing/consumer_groups/coordinators/virtual_offset_manager.rb +4 -4
  66. data/lib/karafka/pro/processing/consumer_groups/expansions_selector.rb +0 -3
  67. data/lib/karafka/pro/processing/consumer_groups/filters/actions.rb +84 -0
  68. data/lib/karafka/pro/processing/consumer_groups/filters/base.rb +5 -6
  69. data/lib/karafka/pro/processing/consumer_groups/filters/delayer.rb +2 -5
  70. data/lib/karafka/pro/processing/consumer_groups/filters/expirer.rb +1 -4
  71. data/lib/karafka/pro/processing/consumer_groups/filters/inline_insights_delayer.rb +1 -4
  72. data/lib/karafka/pro/processing/consumer_groups/filters/throttler.rb +2 -5
  73. data/lib/karafka/pro/processing/consumer_groups/filters/virtual_limiter.rb +0 -3
  74. data/lib/karafka/pro/processing/consumer_groups/jobs/consume_non_blocking.rb +2 -3
  75. data/lib/karafka/pro/processing/consumer_groups/jobs/eofed_non_blocking.rb +0 -2
  76. data/lib/karafka/pro/processing/consumer_groups/jobs/periodic.rb +2 -3
  77. data/lib/karafka/pro/processing/consumer_groups/jobs/periodic_non_blocking.rb +0 -2
  78. data/lib/karafka/pro/processing/consumer_groups/jobs/revoked_non_blocking.rb +0 -3
  79. data/lib/karafka/pro/processing/consumer_groups/jobs_builder.rb +5 -4
  80. data/lib/karafka/pro/processing/consumer_groups/offset_metadata/consumer.rb +3 -7
  81. data/lib/karafka/pro/processing/consumer_groups/offset_metadata/fetcher.rb +8 -10
  82. data/lib/karafka/pro/processing/consumer_groups/offset_metadata/listener.rb +0 -3
  83. data/lib/karafka/pro/processing/consumer_groups/parallel_segments/filters/base.rb +3 -5
  84. data/lib/karafka/pro/processing/consumer_groups/parallel_segments/filters/default.rb +0 -3
  85. data/lib/karafka/pro/processing/consumer_groups/parallel_segments/filters/mom.rb +0 -3
  86. data/lib/karafka/pro/processing/consumer_groups/partitioner.rb +2 -5
  87. data/lib/karafka/pro/processing/consumer_groups/periodic_job/consumer.rb +2 -5
  88. data/lib/karafka/pro/processing/consumer_groups/piping/consumer.rb +2 -5
  89. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_lrj_mom.rb +10 -9
  90. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_lrj_mom_vp.rb +11 -10
  91. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_mom.rb +0 -3
  92. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_mom_vp.rb +0 -3
  93. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_lrj_mom.rb +8 -7
  94. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_lrj_mom_vp.rb +9 -8
  95. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_mom.rb +5 -8
  96. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_mom_vp.rb +3 -6
  97. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_lrj_mom.rb +4 -7
  98. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_lrj_mom_vp.rb +6 -9
  99. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_mom.rb +3 -7
  100. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_mom_vp.rb +4 -8
  101. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/lrj_mom.rb +3 -6
  102. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/lrj_mom_vp.rb +4 -7
  103. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/mom.rb +2 -5
  104. data/lib/karafka/pro/processing/consumer_groups/strategies/aj/mom_vp.rb +3 -6
  105. data/lib/karafka/pro/processing/consumer_groups/strategies/base.rb +0 -3
  106. data/lib/karafka/pro/processing/consumer_groups/strategies/default.rb +56 -33
  107. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/default.rb +27 -14
  108. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr.rb +0 -3
  109. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj.rb +4 -7
  110. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj_mom.rb +5 -8
  111. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj_mom_vp.rb +5 -8
  112. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj_vp.rb +3 -7
  113. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_mom.rb +0 -3
  114. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_mom_vp.rb +0 -3
  115. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_vp.rb +0 -3
  116. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj.rb +2 -5
  117. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj_mom.rb +0 -3
  118. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj_mom_vp.rb +0 -3
  119. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj_vp.rb +3 -6
  120. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/mom.rb +0 -3
  121. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/mom_vp.rb +3 -6
  122. data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/vp.rb +65 -8
  123. data/lib/karafka/pro/processing/consumer_groups/strategies/ftr/default.rb +0 -4
  124. data/lib/karafka/pro/processing/consumer_groups/strategies/ftr/vp.rb +2 -5
  125. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/default.rb +0 -3
  126. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr.rb +3 -6
  127. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr_mom.rb +4 -7
  128. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr_mom_vp.rb +4 -8
  129. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr_vp.rb +3 -6
  130. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/mom.rb +2 -5
  131. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/mom_vp.rb +3 -7
  132. data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/vp.rb +2 -5
  133. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/default.rb +0 -3
  134. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/ftr.rb +0 -3
  135. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/ftr_vp.rb +0 -3
  136. data/lib/karafka/pro/processing/consumer_groups/strategies/mom/vp.rb +0 -3
  137. data/lib/karafka/pro/processing/consumer_groups/strategies/vp/default.rb +11 -6
  138. data/lib/karafka/pro/processing/consumer_groups/strategy_selector.rb +0 -3
  139. data/lib/karafka/pro/processing/consumer_groups/subscription_groups_coordinator.rb +0 -3
  140. data/lib/karafka/pro/processing/consumer_groups/virtual_partitions/distributors/balanced.rb +0 -3
  141. data/lib/karafka/pro/processing/consumer_groups/virtual_partitions/distributors/base.rb +2 -4
  142. data/lib/karafka/pro/processing/consumer_groups/virtual_partitions/distributors/consistent.rb +0 -3
  143. data/lib/karafka/pro/processing/jobs_queue.rb +4 -1
  144. data/lib/karafka/pro/processing/schedulers/base.rb +4 -2
  145. data/lib/karafka/pro/processing/schedulers/default.rb +2 -2
  146. data/lib/karafka/pro/recurring_tasks/consumer.rb +9 -2
  147. data/lib/karafka/pro/recurring_tasks/contracts/config.rb +0 -1
  148. data/lib/karafka/pro/recurring_tasks/executor.rb +25 -13
  149. data/lib/karafka/pro/routing/features/consumer_groups/direct_assignments/subscription_group.rb +0 -2
  150. data/lib/karafka/pro/routing/features/consumer_groups/long_running_job.rb +0 -2
  151. data/lib/karafka/pro/routing/features/consumer_groups/multiplexing/proxy.rb +2 -2
  152. data/lib/karafka/pro/routing/features/consumer_groups/non_blocking_job.rb +0 -2
  153. data/lib/karafka/pro/routing/features/consumer_groups/offset_metadata/topic.rb +0 -6
  154. data/lib/karafka/pro/routing/features/consumer_groups/parallel_segments/consumer_group.rb +5 -4
  155. data/lib/karafka/pro/routing/features/consumer_groups/patterns/config.rb +0 -6
  156. data/lib/karafka/pro/routing/features/consumer_groups/patterns/consumer_group.rb +2 -1
  157. data/lib/karafka/pro/routing/features/consumer_groups/patterns/contracts/pattern.rb +0 -1
  158. data/lib/karafka/pro/routing/features/consumer_groups/patterns/detector.rb +2 -1
  159. data/lib/karafka/pro/routing/features/consumer_groups/patterns/pattern.rb +2 -1
  160. data/lib/karafka/pro/routing/features/consumer_groups/patterns/topics.rb +8 -8
  161. data/lib/karafka/pro/routing/features/consumer_groups/periodic_job/topic.rb +5 -5
  162. data/lib/karafka/pro/routing/features/consumer_groups/recurring_tasks/builder.rb +34 -15
  163. data/lib/karafka/pro/routing/features/consumer_groups/scheduled_messages/builder.rb +52 -28
  164. data/lib/karafka/pro/routing/features/consumer_groups/scheduled_messages/topic.rb +2 -1
  165. data/lib/karafka/pro/routing/features/consumer_groups/virtual_partitions/topic.rb +6 -5
  166. data/lib/karafka/pro/routing/features/pausing/config.rb +7 -11
  167. data/lib/karafka/pro/routing/features/pausing/contracts/topic.rb +6 -8
  168. data/lib/karafka/pro/routing/features/pausing/topic.rb +18 -63
  169. data/lib/karafka/pro/scheduled_messages/consumer.rb +7 -1
  170. data/lib/karafka/pro/scheduled_messages/schema_validator.rb +6 -1
  171. data/lib/karafka/pro/swarm/liveness_listener.rb +118 -13
  172. data/lib/karafka/pro.rb +0 -6
  173. data/lib/karafka/processing/consumer_groups/coordinator.rb +4 -5
  174. data/lib/karafka/processing/consumer_groups/coordinators_buffer.rb +6 -2
  175. data/lib/karafka/processing/consumer_groups/executor.rb +4 -4
  176. data/lib/karafka/processing/consumer_groups/executors_buffer.rb +2 -4
  177. data/lib/karafka/processing/consumer_groups/expansions_selector.rb +0 -2
  178. data/lib/karafka/processing/consumer_groups/inline_insights/consumer.rb +0 -2
  179. data/lib/karafka/processing/consumer_groups/inline_insights/listener.rb +0 -2
  180. data/lib/karafka/processing/consumer_groups/inline_insights/tracker.rb +14 -16
  181. data/lib/karafka/processing/consumer_groups/jobs/consume.rb +2 -5
  182. data/lib/karafka/processing/consumer_groups/jobs/eofed.rb +2 -3
  183. data/lib/karafka/processing/consumer_groups/jobs/idle.rb +2 -2
  184. data/lib/karafka/processing/consumer_groups/jobs/revoked.rb +2 -4
  185. data/lib/karafka/processing/consumer_groups/jobs/shutdown.rb +2 -2
  186. data/lib/karafka/processing/consumer_groups/partitioner.rb +3 -6
  187. data/lib/karafka/processing/consumer_groups/strategies/aj_dlq_mom.rb +5 -2
  188. data/lib/karafka/processing/consumer_groups/strategies/aj_mom.rb +2 -4
  189. data/lib/karafka/processing/consumer_groups/strategies/base.rb +3 -5
  190. data/lib/karafka/processing/consumer_groups/strategies/default.rb +43 -13
  191. data/lib/karafka/processing/consumer_groups/strategies/dlq.rb +5 -2
  192. data/lib/karafka/processing/consumer_groups/strategies/dlq_mom.rb +5 -2
  193. data/lib/karafka/processing/consumer_groups/strategies/mom.rb +2 -3
  194. data/lib/karafka/processing/consumer_groups/strategy_selector.rb +0 -2
  195. data/lib/karafka/processing/coordinator.rb +0 -2
  196. data/lib/karafka/processing/coordinators_buffer.rb +0 -2
  197. data/lib/karafka/processing/partitioner.rb +0 -2
  198. data/lib/karafka/processing/strategy_selector.rb +0 -2
  199. data/lib/karafka/processing/worker.rb +14 -6
  200. data/lib/karafka/processing/workers_pool.rb +21 -4
  201. data/lib/karafka/railtie.rb +0 -1
  202. data/lib/karafka/routing/contracts/routing.rb +2 -2
  203. data/lib/karafka/routing/features/consumer_groups/active_job/topic.rb +6 -5
  204. data/lib/karafka/routing/features/consumer_groups/manual_offset_management/topic.rb +2 -2
  205. data/lib/karafka/routing/features/deserializers/topic.rb +0 -1
  206. data/lib/karafka/routing/features/pausing/config.rb +27 -0
  207. data/lib/karafka/routing/features/pausing.rb +13 -0
  208. data/lib/karafka/routing/subscription_group.rb +1 -1
  209. data/lib/karafka/routing/topic.rb +20 -7
  210. data/lib/karafka/routing/topics.rb +21 -1
  211. data/lib/karafka/server.rb +9 -0
  212. data/lib/karafka/setup/config.rb +52 -55
  213. data/lib/karafka/setup/config_proxy.rb +2 -1
  214. data/lib/karafka/setup/contracts/config.rb +14 -0
  215. data/lib/karafka/swarm/node.rb +2 -2
  216. data/lib/karafka/version.rb +1 -2
  217. data/lib/karafka.rb +2 -2
  218. metadata +14 -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
  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.
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Routing
5
+ module Features
6
+ module Pausing
7
+ # Per-topic pause (backoff) configuration value object.
8
+ #
9
+ # Defaults to the global `config.pause.*` settings unless overridden on a per-topic basis.
10
+ #
11
+ # Unlike most feature configs, `active` here does not switch the behavior on or off - the
12
+ # pause/backoff always applies. It only marks whether these settings were explicitly set
13
+ # for this topic (`true`) or inherited from the global defaults (`false`).
14
+ Config = Struct.new(
15
+ :active,
16
+ :timeout,
17
+ :max_timeout,
18
+ :with_exponential_backoff,
19
+ keyword_init: true
20
+ ) do
21
+ alias_method :active?, :active
22
+ alias_method :with_exponential_backoff?, :with_exponential_backoff
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Routing
5
+ module Features
6
+ # Namespace holding the pause (backoff) routing configuration. There is no OSS pausing
7
+ # feature to activate here - the backoff behavior is part of the topic itself and its
8
+ # settings default to the global `config.pause.*`.
9
+ module Pausing
10
+ end
11
+ end
12
+ end
13
+ end
@@ -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
@@ -32,9 +32,6 @@ module Karafka
32
32
  max_wait_time
33
33
  initial_offset
34
34
  consumer_persistence
35
- pause_timeout
36
- pause_max_timeout
37
- pause_with_exponential_backoff
38
35
  ].freeze
39
36
 
40
37
  private_constant :INHERITABLE_ATTRIBUTES
@@ -47,9 +44,9 @@ module Karafka
47
44
  @group = group
48
45
  @attributes = {}
49
46
  @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
47
+ # We use identifier related to the group that owns a topic, because from Karafka 0.6 we can
48
+ # handle multiple Kafka instances with the same process and we can have same topic name
49
+ # across multiple groups
53
50
  @id = "#{group.id}_#{@name}"
54
51
  @consumer = nil
55
52
  @active_assigned = false
@@ -58,6 +55,10 @@ module Karafka
58
55
  INHERITABLE_ATTRIBUTES.each do |attribute|
59
56
  instance_variable_set("@#{attribute}", nil)
60
57
  end
58
+
59
+ # Explicit nil initialization for Ruby's object shapes optimization. The per-topic pause
60
+ # config is built lazily on first read, defaulting to the global `config.pause.*` settings.
61
+ @pause = nil
61
62
  end
62
63
 
63
64
  INHERITABLE_ATTRIBUTES.each do |attribute|
@@ -73,6 +74,17 @@ module Karafka
73
74
  RUBY
74
75
  end
75
76
 
77
+ # @return [Karafka::Routing::Features::Pausing::Config] per-topic pause configuration,
78
+ # reflecting the root `config.pause.*` settings.
79
+ def pause
80
+ @pause ||= Features::Pausing::Config.new(
81
+ active: false,
82
+ timeout: Karafka::App.config.pause.timeout,
83
+ max_timeout: Karafka::App.config.pause.max_timeout,
84
+ with_exponential_backoff: Karafka::App.config.pause.with_exponential_backoff
85
+ )
86
+ end
87
+
76
88
  # Often users want to have the same basic cluster setup with small setting alterations
77
89
  # This method allows us to do so by setting `inherit` to `true`. Whe inherit is enabled,
78
90
  # settings will be merged with defaults.
@@ -155,6 +167,7 @@ module Karafka
155
167
  name: name,
156
168
  active: active?,
157
169
  consumer: consumer,
170
+ pause: pause.to_h,
158
171
  group_id: group.id,
159
172
  # Kept as a reference alongside `group_id` for backwards compatibility. Will be removed
160
173
  # in Karafka 3.0.
@@ -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
@@ -22,8 +22,8 @@ module Karafka
22
22
  # option token [String, false] - license token issued when you acquire a Pro license
23
23
  # Leave false if using the LGPL version and all is going to work just fine :)
24
24
  #
25
- # @note By using the commercial components, you accept the LICENSE-COMM commercial license
26
- # terms and conditions
25
+ # By using the commercial components, you accept the LICENSE-COMM commercial license terms
26
+ # and conditions
27
27
  setting :token, default: false
28
28
  # option entity [String] for whom we did issue the license
29
29
  setting :entity, default: ""
@@ -88,8 +88,8 @@ module Karafka
88
88
  setting :token_provider_listener, default: false
89
89
  end
90
90
 
91
- # rdkafka default options
92
- # @see https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
91
+ # rdkafka default options See
92
+ # https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
93
93
  setting :kafka, default: {}
94
94
 
95
95
  # Public configuration for swarm operations
@@ -163,9 +163,8 @@ module Karafka
163
163
  setting :internal do
164
164
  # option status [Karafka::Status] app status
165
165
  setting :status, default: Status.new
166
- # option process [Karafka::Process] process status
167
- # @note In the future, we need to have a single process representation for all the karafka
168
- # instances
166
+ # option process [Karafka::Process] process status. In the future, we need to have a single
167
+ # process representation for all the karafka instances
169
168
  setting :process, default: Process.new
170
169
  # Interval of "ticking". This is used to define the maximum time between consecutive
171
170
  # polling of the main rdkafka queue. It should match also the `statistics.interval.ms`
@@ -314,6 +313,17 @@ module Karafka
314
313
  # option worker_job_call_wrapper [Proc, false] callable object that will be used to wrap
315
314
  # the worker execution of a job or false if no wrapper needed
316
315
  setting :worker_job_call_wrapper, default: false
316
+ # option critical_errors [Array<Class>] errors that indicate that the process integrity
317
+ # is compromised or that termination was explicitly requested. They are not retried
318
+ # in-process when raised from the consumption flow: SystemExit means someone called
319
+ # `exit` and retrying would invert that intent (and loop forever on a deterministic
320
+ # raise), while NoMemoryError means the VM cannot be trusted to execute a retry.
321
+ # Their failure is still recorded (so nothing gets marked, they are never dispatched
322
+ # to a DLQ and the retry pause protects the partition) and a graceful shutdown is
323
+ # initiated instead - redelivery after restart preserves the processing guarantees
324
+ # through the process death. Can be extended with user-specific fatal error classes
325
+ # that should terminate the process rather than be retried.
326
+ setting :critical_errors, default: [SystemExit, SignalException, NoMemoryError].freeze
317
327
 
318
328
  # Consumer-group-specific processing defaults. When share groups land, a parallel
319
329
  # `share_groups` namespace will hold their equivalents.
@@ -343,6 +353,36 @@ module Karafka
343
353
  setting :parser, default: Messages::Parser.new
344
354
  end
345
355
 
356
+ # Things related to librdkafka statistics decoration prior to their emission
357
+ setting :statistics do
358
+ # Consumer groups related statistics settings
359
+ setting :consumer_groups do
360
+ # option decorator_class [Class] class used to decorate raw librdkafka statistics
361
+ # (adds delta/freeze-duration values) before `statistics.emitted` is instrumented.
362
+ # Exposed so it can be replaced with a custom decorator (e.g. one that also
363
+ # enriches or corrects specific values) without touching the callback that uses
364
+ # it.
365
+ setting :decorator_class, default: Instrumentation::Callbacks::ConsumerGroups::Decorator
366
+
367
+ # Active refreshing of watermarks and lags for long-paused partitions (Pro).
368
+ # librdkafka only updates watermark offsets and lags from fetch responses, so
369
+ # partitions paused for a long time report frozen values in statistics. When
370
+ # enabled, they are refreshed via the running consumer connection and overlaid onto
371
+ # the emitted statistics.
372
+ setting :lag_compensation do
373
+ # option interval [Integer] how often (ms) at most to refresh watermarks and lags
374
+ # of long-paused partitions. 0 disables the feature entirely.
375
+ setting :interval, default: 0
376
+
377
+ # option pause_age [Integer] how long (ms) a partition needs to stay paused to
378
+ # qualify for the compensation. Short pause/resume cycles reset the clock and
379
+ # never qualify. Minimum accepted value is 5 seconds as statistics of partitions
380
+ # paused shorter are fresh enough to make compensating them pointless.
381
+ setting :pause_age, default: 30_000
382
+ end
383
+ end
384
+ end
385
+
346
386
  # Karafka components for ActiveJob
347
387
  setting :active_job do
348
388
  # option dispatcher [Karafka::ActiveJob::Dispatcher] default dispatcher for ActiveJob
@@ -365,54 +405,6 @@ module Karafka
365
405
  # Thanks to that we have an initial state out of the box.
366
406
  configure
367
407
 
368
- # Backwards compatibility: Add old flat API methods to the config instance
369
- # These delegate to the new nested pause config
370
- # @deprecated Will be removed in Karafka 2.6
371
- #
372
- # Prior to the introduction of nested pause configuration, pause-related settings were
373
- # accessed directly on the config object (e.g., `config.pause_timeout`). With the nested
374
- # structure introduced, these settings moved to `config.pause.timeout`, etc.
375
- #
376
- # This instance_eval block adds delegation methods to maintain backwards compatibility,
377
- # allowing existing code using the old flat API to continue working without modification.
378
- config.instance_eval do
379
- # @return [Integer] delegated timeout value from pause.timeout
380
- # @deprecated Use config.pause.timeout instead
381
- def pause_timeout
382
- pause.timeout
383
- end
384
-
385
- # @param value [Integer] timeout value to set
386
- # @deprecated Use config.pause.timeout= instead
387
- def pause_timeout=(value)
388
- pause.timeout = value
389
- end
390
-
391
- # @return [Integer] delegated max_timeout value from pause.max_timeout
392
- # @deprecated Use config.pause.max_timeout instead
393
- def pause_max_timeout
394
- pause.max_timeout
395
- end
396
-
397
- # @param value [Integer] max timeout value to set
398
- # @deprecated Use config.pause.max_timeout= instead
399
- def pause_max_timeout=(value)
400
- pause.max_timeout = value
401
- end
402
-
403
- # @return [Boolean] delegated exponential backoff flag from pause.with_exponential_backoff
404
- # @deprecated Use config.pause.with_exponential_backoff instead
405
- def pause_with_exponential_backoff
406
- pause.with_exponential_backoff
407
- end
408
-
409
- # @param value [Boolean] exponential backoff flag to set
410
- # @deprecated Use config.pause.with_exponential_backoff= instead
411
- def pause_with_exponential_backoff=(value)
412
- pause.with_exponential_backoff = value
413
- end
414
- end
415
-
416
408
  class << self
417
409
  # Configuring method
418
410
  def setup(&)
@@ -451,6 +443,11 @@ module Karafka
451
443
  # Post-setup configure all routing features that would need this
452
444
  Routing::Features::Base.post_setup_all(config)
453
445
 
446
+ # Subscribe the critical errors listener so process-critical errors reported anywhere
447
+ # in the framework escalate to a graceful shutdown. Subscribed before any other
448
+ # internal listener (including the Pro post-setup subscriptions) so it reacts first
449
+ config.monitor.subscribe(Instrumentation::CriticalErrorsListener.instance)
450
+
454
451
  # Runs things that need to be executed after config is defined and all the components
455
452
  # are also configured
456
453
  Pro::Loader.post_setup_all(config) if Karafka.pro?
@@ -197,7 +197,8 @@ module Karafka
197
197
  # end
198
198
  #
199
199
  # @example Direct producer assignment
200
- # custom_producer = WaterDrop::Producer.new { |c| c.kafka = { 'bootstrap.servers' => 'localhost:9092' } }
200
+ # custom_producer = WaterDrop::Producer.new { |c|
201
+ # c.kafka = { 'bootstrap.servers' => 'localhost:9092' } }
201
202
  # config.producer = custom_producer
202
203
  def producer(instance = READ, &block)
203
204
  if block
@@ -124,6 +124,9 @@ module Karafka
124
124
  required(:jobs_queue_class) { |val| !val.nil? }
125
125
  required(:scheduler_class) { |val| !val.nil? }
126
126
  required(:worker_job_call_wrapper) { |val| val == false || val.respond_to?(:wrap) }
127
+ required(:critical_errors) do |val|
128
+ val.is_a?(Array) && val.all? { |klass| klass.is_a?(Class) && klass <= Exception }
129
+ end
127
130
 
128
131
  nested(:consumer_groups) do
129
132
  required(:jobs_builder) { |val| !val.nil? }
@@ -136,6 +139,17 @@ module Karafka
136
139
  end
137
140
  end
138
141
 
142
+ nested(:statistics) do
143
+ nested(:consumer_groups) do
144
+ required(:decorator_class) { |val| !val.nil? }
145
+
146
+ nested(:lag_compensation) do
147
+ required(:interval) { |val| val.is_a?(Integer) && val >= 0 }
148
+ required(:pause_age) { |val| val.is_a?(Integer) && val >= 5_000 }
149
+ end
150
+ end
151
+ end
152
+
139
153
  nested(:active_job) do
140
154
  required(:dispatcher) { |val| !val.nil? }
141
155
  required(:job_options_contract) { |val| !val.nil? }