karafka 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/FUNDING.yml +1 -0
  4. data/.github/workflows/ci.yml +40 -16
  5. data/.rspec +4 -0
  6. data/.ruby-version +1 -1
  7. data/CHANGELOG.md +459 -796
  8. data/Gemfile +3 -2
  9. data/Gemfile.lock +44 -32
  10. data/LICENSE +1 -1
  11. data/README.md +22 -17
  12. data/bin/integrations +86 -26
  13. data/bin/karafka +1 -13
  14. data/bin/rspecs +6 -0
  15. data/bin/verify_license_integrity +37 -0
  16. data/certs/cert_chain.pem +26 -0
  17. data/config/{errors.yml → locales/errors.yml} +34 -6
  18. data/config/locales/pro_errors.yml +34 -0
  19. data/docker-compose.yml +4 -30
  20. data/karafka.gemspec +17 -10
  21. data/lib/active_job/karafka.rb +3 -7
  22. data/lib/active_job/queue_adapters/karafka_adapter.rb +7 -1
  23. data/lib/karafka/active_job/consumer.rb +29 -6
  24. data/lib/karafka/active_job/current_attributes/loading.rb +36 -0
  25. data/lib/karafka/active_job/current_attributes/persistence.rb +28 -0
  26. data/lib/karafka/active_job/current_attributes.rb +42 -0
  27. data/lib/karafka/active_job/dispatcher.rb +34 -3
  28. data/lib/karafka/active_job/job_options_contract.rb +13 -2
  29. data/lib/karafka/admin.rb +246 -0
  30. data/lib/karafka/app.rb +30 -7
  31. data/lib/karafka/base_consumer.rb +124 -82
  32. data/lib/karafka/cli/base.rb +20 -0
  33. data/lib/karafka/cli/console.rb +13 -8
  34. data/lib/karafka/cli/info.rb +3 -2
  35. data/lib/karafka/cli/server.rb +55 -8
  36. data/lib/karafka/cli/topics.rb +146 -0
  37. data/lib/karafka/connection/client.rb +118 -61
  38. data/lib/karafka/connection/consumer_group_coordinator.rb +48 -0
  39. data/lib/karafka/connection/listener.rb +78 -30
  40. data/lib/karafka/connection/listeners_batch.rb +18 -2
  41. data/lib/karafka/connection/rebalance_manager.rb +3 -0
  42. data/lib/karafka/contracts/config.rb +1 -1
  43. data/lib/karafka/contracts/consumer_group.rb +22 -9
  44. data/lib/karafka/contracts/server_cli_options.rb +71 -11
  45. data/lib/karafka/contracts/topic.rb +65 -0
  46. data/lib/karafka/contracts.rb +1 -1
  47. data/lib/karafka/embedded.rb +36 -0
  48. data/lib/karafka/errors.rb +6 -4
  49. data/lib/karafka/helpers/colorize.rb +6 -0
  50. data/lib/karafka/instrumentation/callbacks/error.rb +3 -4
  51. data/lib/karafka/instrumentation/callbacks/statistics.rb +14 -4
  52. data/lib/karafka/instrumentation/logger_listener.rb +151 -22
  53. data/lib/karafka/instrumentation/notifications.rb +26 -7
  54. data/lib/karafka/instrumentation/proctitle_listener.rb +7 -16
  55. data/lib/karafka/instrumentation/vendors/datadog/dashboard.json +1 -1
  56. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +153 -0
  57. data/lib/karafka/instrumentation/vendors/datadog/{listener.rb → metrics_listener.rb} +62 -35
  58. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +166 -0
  59. data/lib/karafka/licenser.rb +53 -50
  60. data/lib/karafka/messages/builders/batch_metadata.rb +8 -8
  61. data/lib/karafka/messages/builders/messages.rb +6 -2
  62. data/lib/karafka/messages/message.rb +14 -2
  63. data/lib/karafka/messages/messages.rb +5 -0
  64. data/lib/karafka/messages/parser.rb +14 -0
  65. data/lib/karafka/patches/rdkafka/bindings.rb +132 -0
  66. data/lib/karafka/pro/active_job/consumer.rb +10 -9
  67. data/lib/karafka/pro/active_job/dispatcher.rb +28 -3
  68. data/lib/karafka/pro/active_job/job_options_contract.rb +16 -3
  69. data/lib/karafka/pro/encryption/cipher.rb +58 -0
  70. data/lib/karafka/pro/encryption/contracts/config.rb +79 -0
  71. data/lib/karafka/pro/encryption/errors.rb +24 -0
  72. data/lib/karafka/pro/encryption/messages/middleware.rb +46 -0
  73. data/lib/karafka/pro/encryption/messages/parser.rb +56 -0
  74. data/lib/karafka/pro/encryption/setup/config.rb +48 -0
  75. data/lib/karafka/pro/encryption.rb +47 -0
  76. data/lib/karafka/pro/iterator.rb +253 -0
  77. data/lib/karafka/pro/loader.rb +52 -26
  78. data/lib/karafka/pro/performance_tracker.rb +10 -6
  79. data/lib/karafka/pro/processing/collapser.rb +62 -0
  80. data/lib/karafka/pro/processing/coordinator.rb +92 -16
  81. data/lib/karafka/pro/processing/filters/base.rb +61 -0
  82. data/lib/karafka/pro/processing/filters/delayer.rb +70 -0
  83. data/lib/karafka/pro/processing/filters/expirer.rb +51 -0
  84. data/lib/karafka/pro/processing/filters/throttler.rb +84 -0
  85. data/lib/karafka/pro/processing/filters/virtual_limiter.rb +52 -0
  86. data/lib/karafka/pro/processing/filters_applier.rb +104 -0
  87. data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +7 -5
  88. data/lib/karafka/pro/processing/jobs/revoked_non_blocking.rb +37 -0
  89. data/lib/karafka/pro/processing/jobs_builder.rb +22 -4
  90. data/lib/karafka/pro/processing/partitioner.rb +33 -24
  91. data/lib/karafka/pro/processing/scheduler.rb +27 -8
  92. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +68 -0
  93. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +74 -0
  94. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom.rb +72 -0
  95. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +76 -0
  96. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +64 -0
  97. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +68 -0
  98. data/lib/karafka/pro/processing/strategies/aj/dlq_mom.rb +64 -0
  99. data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +69 -0
  100. data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom.rb +38 -0
  101. data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom_vp.rb +64 -0
  102. data/lib/karafka/pro/processing/strategies/aj/ftr_mom.rb +38 -0
  103. data/lib/karafka/pro/processing/strategies/aj/ftr_mom_vp.rb +58 -0
  104. data/lib/karafka/pro/processing/strategies/aj/lrj_mom.rb +37 -0
  105. data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +79 -0
  106. data/lib/karafka/pro/processing/strategies/aj/mom.rb +36 -0
  107. data/lib/karafka/pro/processing/strategies/aj/mom_vp.rb +52 -0
  108. data/lib/karafka/pro/processing/strategies/base.rb +26 -0
  109. data/lib/karafka/pro/processing/strategies/default.rb +105 -0
  110. data/lib/karafka/pro/processing/strategies/dlq/default.rb +131 -0
  111. data/lib/karafka/pro/processing/strategies/dlq/ftr.rb +61 -0
  112. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj.rb +75 -0
  113. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +71 -0
  114. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom_vp.rb +43 -0
  115. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_vp.rb +41 -0
  116. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +69 -0
  117. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom_vp.rb +41 -0
  118. data/lib/karafka/pro/processing/strategies/dlq/ftr_vp.rb +40 -0
  119. data/lib/karafka/pro/processing/strategies/dlq/lrj.rb +63 -0
  120. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +63 -0
  121. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom_vp.rb +36 -0
  122. data/lib/karafka/pro/processing/strategies/dlq/lrj_vp.rb +39 -0
  123. data/lib/karafka/pro/processing/strategies/dlq/mom.rb +68 -0
  124. data/lib/karafka/pro/processing/strategies/dlq/mom_vp.rb +37 -0
  125. data/lib/karafka/pro/processing/strategies/dlq/vp.rb +40 -0
  126. data/lib/karafka/pro/processing/strategies/ftr/default.rb +104 -0
  127. data/lib/karafka/pro/processing/strategies/ftr/vp.rb +40 -0
  128. data/lib/karafka/pro/processing/strategies/lrj/default.rb +87 -0
  129. data/lib/karafka/pro/processing/strategies/lrj/ftr.rb +69 -0
  130. data/lib/karafka/pro/processing/strategies/lrj/ftr_mom.rb +67 -0
  131. data/lib/karafka/pro/processing/strategies/lrj/ftr_mom_vp.rb +40 -0
  132. data/lib/karafka/pro/processing/strategies/lrj/ftr_vp.rb +39 -0
  133. data/lib/karafka/pro/processing/strategies/lrj/mom.rb +80 -0
  134. data/lib/karafka/pro/processing/strategies/lrj/mom_vp.rb +38 -0
  135. data/lib/karafka/pro/processing/strategies/lrj/vp.rb +36 -0
  136. data/lib/karafka/pro/processing/strategies/mom/default.rb +46 -0
  137. data/lib/karafka/pro/processing/strategies/mom/ftr.rb +53 -0
  138. data/lib/karafka/pro/processing/strategies/mom/ftr_vp.rb +37 -0
  139. data/lib/karafka/pro/processing/strategies/mom/vp.rb +35 -0
  140. data/lib/karafka/pro/processing/strategies/vp/default.rb +104 -0
  141. data/lib/karafka/pro/{contracts/base.rb → processing/strategies.rb} +6 -5
  142. data/lib/karafka/pro/processing/strategy_selector.rb +84 -0
  143. data/lib/karafka/pro/processing/virtual_offset_manager.rb +147 -0
  144. data/lib/karafka/pro/routing/features/base.rb +24 -0
  145. data/lib/karafka/pro/routing/features/dead_letter_queue/contract.rb +50 -0
  146. data/lib/karafka/pro/routing/features/dead_letter_queue.rb +27 -0
  147. data/lib/karafka/pro/routing/{builder_extensions.rb → features/delaying/config.rb} +8 -11
  148. data/lib/karafka/pro/routing/features/delaying/contract.rb +38 -0
  149. data/lib/karafka/pro/routing/features/delaying/topic.rb +59 -0
  150. data/lib/karafka/pro/routing/features/delaying.rb +29 -0
  151. data/lib/karafka/pro/routing/features/expiring/config.rb +27 -0
  152. data/lib/karafka/pro/routing/features/expiring/contract.rb +38 -0
  153. data/lib/karafka/pro/routing/features/expiring/topic.rb +59 -0
  154. data/lib/karafka/pro/routing/features/expiring.rb +27 -0
  155. data/lib/karafka/pro/routing/features/filtering/config.rb +40 -0
  156. data/lib/karafka/pro/routing/features/filtering/contract.rb +41 -0
  157. data/lib/karafka/pro/routing/features/filtering/topic.rb +51 -0
  158. data/lib/karafka/pro/routing/features/filtering.rb +27 -0
  159. data/lib/karafka/pro/routing/features/long_running_job/config.rb +28 -0
  160. data/lib/karafka/pro/routing/features/long_running_job/contract.rb +37 -0
  161. data/lib/karafka/pro/routing/features/long_running_job/topic.rb +42 -0
  162. data/lib/karafka/pro/routing/features/long_running_job.rb +28 -0
  163. data/lib/karafka/pro/routing/features/throttling/config.rb +32 -0
  164. data/lib/karafka/pro/routing/features/throttling/contract.rb +41 -0
  165. data/lib/karafka/pro/routing/features/throttling/topic.rb +69 -0
  166. data/lib/karafka/pro/routing/features/throttling.rb +30 -0
  167. data/lib/karafka/pro/routing/features/virtual_partitions/config.rb +30 -0
  168. data/lib/karafka/pro/routing/features/virtual_partitions/contract.rb +52 -0
  169. data/lib/karafka/pro/routing/features/virtual_partitions/topic.rb +56 -0
  170. data/lib/karafka/pro/routing/features/virtual_partitions.rb +27 -0
  171. data/lib/karafka/process.rb +23 -8
  172. data/lib/karafka/processing/coordinator.rb +102 -23
  173. data/lib/karafka/processing/coordinators_buffer.rb +5 -1
  174. data/lib/karafka/processing/executor.rb +51 -21
  175. data/lib/karafka/processing/executors_buffer.rb +10 -26
  176. data/lib/karafka/processing/jobs/base.rb +6 -2
  177. data/lib/karafka/processing/jobs/consume.rb +8 -5
  178. data/lib/karafka/processing/jobs/idle.rb +24 -0
  179. data/lib/karafka/processing/jobs_builder.rb +2 -3
  180. data/lib/karafka/processing/jobs_queue.rb +14 -8
  181. data/lib/karafka/processing/partitioner.rb +3 -1
  182. data/lib/karafka/processing/result.rb +14 -1
  183. data/lib/karafka/processing/strategies/aj_dlq_mom.rb +44 -0
  184. data/lib/karafka/processing/strategies/aj_mom.rb +21 -0
  185. data/lib/karafka/processing/strategies/base.rb +52 -0
  186. data/lib/karafka/processing/strategies/default.rb +142 -0
  187. data/lib/karafka/processing/strategies/dlq.rb +88 -0
  188. data/lib/karafka/processing/strategies/dlq_mom.rb +49 -0
  189. data/lib/karafka/processing/strategies/mom.rb +29 -0
  190. data/lib/karafka/processing/strategy_selector.rb +47 -0
  191. data/lib/karafka/processing/worker.rb +8 -3
  192. data/lib/karafka/railtie.rb +24 -12
  193. data/lib/karafka/routing/activity_manager.rb +84 -0
  194. data/lib/karafka/routing/builder.rb +20 -0
  195. data/lib/karafka/routing/consumer_group.rb +38 -4
  196. data/lib/karafka/routing/consumer_mapper.rb +0 -10
  197. data/lib/karafka/routing/features/active_job/builder.rb +33 -0
  198. data/lib/karafka/routing/features/active_job/config.rb +15 -0
  199. data/lib/karafka/routing/features/active_job/contract.rb +41 -0
  200. data/lib/karafka/routing/features/active_job/topic.rb +33 -0
  201. data/lib/karafka/routing/features/active_job.rb +13 -0
  202. data/lib/karafka/routing/features/base/expander.rb +53 -0
  203. data/lib/karafka/routing/features/base.rb +34 -0
  204. data/lib/karafka/routing/features/dead_letter_queue/config.rb +19 -0
  205. data/lib/karafka/routing/features/dead_letter_queue/contract.rb +42 -0
  206. data/lib/karafka/routing/features/dead_letter_queue/topic.rb +41 -0
  207. data/lib/karafka/routing/features/dead_letter_queue.rb +16 -0
  208. data/lib/karafka/routing/features/declaratives/config.rb +18 -0
  209. data/lib/karafka/routing/features/declaratives/contract.rb +30 -0
  210. data/lib/karafka/routing/features/declaratives/topic.rb +44 -0
  211. data/lib/karafka/routing/features/declaratives.rb +14 -0
  212. data/lib/karafka/routing/features/manual_offset_management/config.rb +15 -0
  213. data/lib/karafka/routing/features/manual_offset_management/contract.rb +24 -0
  214. data/lib/karafka/routing/features/manual_offset_management/topic.rb +35 -0
  215. data/lib/karafka/routing/features/manual_offset_management.rb +18 -0
  216. data/lib/karafka/routing/proxy.rb +18 -20
  217. data/lib/karafka/routing/router.rb +27 -2
  218. data/lib/karafka/routing/subscription_group.rb +43 -5
  219. data/lib/karafka/routing/subscription_groups_builder.rb +6 -1
  220. data/lib/karafka/routing/topic.rb +17 -5
  221. data/lib/karafka/routing/topics.rb +8 -0
  222. data/lib/karafka/runner.rb +1 -0
  223. data/lib/karafka/serialization/json/deserializer.rb +1 -0
  224. data/lib/karafka/server.rb +58 -28
  225. data/lib/karafka/setup/attributes_map.rb +344 -0
  226. data/lib/karafka/setup/config.rb +48 -19
  227. data/lib/karafka/status.rb +35 -7
  228. data/lib/karafka/templates/karafka.rb.erb +14 -6
  229. data/lib/karafka/time_trackers/base.rb +1 -6
  230. data/lib/karafka/time_trackers/pause.rb +49 -19
  231. data/lib/karafka/time_trackers/poll.rb +7 -3
  232. data/lib/karafka/version.rb +1 -1
  233. data/lib/karafka.rb +56 -3
  234. data/renovate.json +6 -0
  235. data.tar.gz.sig +0 -0
  236. metadata +189 -61
  237. metadata.gz.sig +0 -0
  238. data/bin/wait_for_kafka +0 -20
  239. data/certs/mensfeld.pem +0 -25
  240. data/lib/karafka/active_job/routing/extensions.rb +0 -31
  241. data/lib/karafka/contracts/consumer_group_topic.rb +0 -42
  242. data/lib/karafka/instrumentation.rb +0 -21
  243. data/lib/karafka/pro/base_consumer.rb +0 -82
  244. data/lib/karafka/pro/contracts/consumer_group.rb +0 -34
  245. data/lib/karafka/pro/contracts/consumer_group_topic.rb +0 -33
  246. data/lib/karafka/pro/routing/topic_extensions.rb +0 -38
@@ -7,15 +7,6 @@ module Karafka
7
7
  class Proxy
8
8
  attr_reader :target
9
9
 
10
- # We should proxy only non ? and = methods as we want to have a regular dsl
11
- IGNORED_POSTFIXES = %w[
12
- ?
13
- =
14
- !
15
- ].freeze
16
-
17
- private_constant :IGNORED_POSTFIXES
18
-
19
10
  # @param target [Object] target object to which we proxy any DSL call
20
11
  # @param block [Proc] block that we want to evaluate in the proxy context
21
12
  def initialize(target, &block)
@@ -23,23 +14,30 @@ module Karafka
23
14
  instance_eval(&block)
24
15
  end
25
16
 
26
- # Translates the no "=" DSL of routing into elements assignments on target
27
- # @param method_name [Symbol] name of the missing method
28
- # @param arguments [Array] array with it's arguments
29
- # @param block [Proc] block provided to the method
30
- def method_missing(method_name, *arguments, &block)
31
- return super unless respond_to_missing?(method_name)
17
+ # Ruby 2.7.0 to 2.7.2 do not have arg forwarding, so we fallback to the old way
18
+ arg_forwarding = RUBY_VERSION < '3.0' ? '*args, &block' : '...'
32
19
 
33
- @target.public_send(:"#{method_name}=", *arguments, &block)
34
- end
20
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
21
+ # Translates the no "=" DSL of routing into elements assignments on target
22
+ # @param method_name [Symbol] name of the missing method
23
+ def method_missing(method_name, #{arg_forwarding})
24
+ return super unless respond_to_missing?(method_name)
25
+
26
+ if @target.respond_to?(:"\#{method_name}=")
27
+ @target.public_send(:"\#{method_name}=", #{arg_forwarding})
28
+ else
29
+ @target.public_send(method_name, #{arg_forwarding})
30
+ end
31
+ end
32
+ RUBY
35
33
 
36
34
  # Tells whether or not a given element exists on the target
37
35
  # @param method_name [Symbol] name of the missing method
38
36
  # @param include_private [Boolean] should we include private in the check as well
39
37
  def respond_to_missing?(method_name, include_private = false)
40
- return false if IGNORED_POSTFIXES.any? { |postfix| method_name.to_s.end_with?(postfix) }
41
-
42
- @target.respond_to?(:"#{method_name}=", include_private) || super
38
+ @target.respond_to?(:"#{method_name}=", include_private) ||
39
+ @target.respond_to?(method_name, include_private) ||
40
+ super
43
41
  end
44
42
  end
45
43
  end
@@ -14,16 +14,41 @@ module Karafka
14
14
  # @raise [Karafka::Topic::NonMatchingTopicError] raised if topic name does not match
15
15
  # any route defined by user using routes.draw
16
16
  def find(topic_id)
17
+ find_by(id: topic_id) || raise(Errors::NonMatchingRouteError, topic_id)
18
+ end
19
+
20
+ # Finds first reference of a given topic based on provided lookup attribute
21
+ # @param lookup [Hash<Symbol, String>] hash with attribute - value key pairs
22
+ # @return [Karafka::Routing::Topic, nil] proper route details or nil if not found
23
+ def find_by(lookup)
17
24
  App.consumer_groups.each do |consumer_group|
18
25
  consumer_group.topics.each do |topic|
19
- return topic if topic.id == topic_id
26
+ return topic if lookup.all? do |attribute, value|
27
+ topic.public_send(attribute) == value
28
+ end
20
29
  end
21
30
  end
22
31
 
23
- raise(Errors::NonMatchingRouteError, topic_id)
32
+ nil
33
+ end
34
+
35
+ # Finds the topic by name (in any consumer group) and if not present, will built a new
36
+ # representation of the topic with the defaults and default deserializer.
37
+ #
38
+ # This is used in places where we may operate on topics that are not part of the routing
39
+ # but we want to do something on them (display data, iterate over, etc)
40
+ # @param name [String] name of the topic we are looking for
41
+ # @return [Karafka::Routing::Topic]
42
+ #
43
+ # @note Please note, that in case of a new topic, it will have a newly built consumer group
44
+ # as well, that is not part of the routing.
45
+ def find_or_initialize_by_name(name)
46
+ find_by(name: name) || Topic.new(name, ConsumerGroup.new(name))
24
47
  end
25
48
 
26
49
  module_function :find
50
+ module_function :find_by
51
+ module_function :find_or_initialize_by_name
27
52
  end
28
53
  end
29
54
  end
@@ -8,13 +8,36 @@ module Karafka
8
8
  # @note One subscription group will always belong to one consumer group, but one consumer
9
9
  # group can have multiple subscription groups.
10
10
  class SubscriptionGroup
11
- attr_reader :id, :topics
11
+ attr_reader :id, :name, :topics, :kafka, :consumer_group
12
12
 
13
+ # Numeric for counting groups
14
+ GROUP_COUNT = Concurrent::AtomicFixnum.new
15
+
16
+ private_constant :GROUP_COUNT
17
+
18
+ class << self
19
+ # Generates new subscription group id that will be used in case of anonymous subscription
20
+ # groups
21
+ # @return [String] hex(6) compatible reproducible id
22
+ def id
23
+ ::Digest::MD5.hexdigest(
24
+ GROUP_COUNT.increment.to_s
25
+ )[0..11]
26
+ end
27
+ end
28
+
29
+ # @param position [Integer] position of this subscription group in all the subscriptions
30
+ # groups array. We need to have this value for sake of static group memberships, where
31
+ # we need a "in-between" restarts unique identifier
13
32
  # @param topics [Karafka::Routing::Topics] all the topics that share the same key settings
14
33
  # @return [SubscriptionGroup] built subscription group
15
- def initialize(topics)
16
- @id = SecureRandom.uuid
34
+ def initialize(position, topics)
35
+ @name = topics.first.subscription_group
36
+ @consumer_group = topics.first.consumer_group
37
+ @id = "#{@name}_#{position}"
38
+ @position = position
17
39
  @topics = topics
40
+ @kafka = build_kafka
18
41
  freeze
19
42
  end
20
43
 
@@ -33,12 +56,27 @@ module Karafka
33
56
  @topics.first.max_wait_time
34
57
  end
35
58
 
59
+ # @return [Boolean] is this subscription group one of active once
60
+ def active?
61
+ Karafka::App.config.internal.routing.activity_manager.active?(:subscription_groups, name)
62
+ end
63
+
64
+ private
65
+
36
66
  # @return [Hash] kafka settings are a bit special. They are exactly the same for all of the
37
67
  # topics but they lack the group.id (unless explicitly) provided. To make it compatible
38
68
  # with our routing engine, we inject it before it will go to the consumer
39
- def kafka
40
- kafka = @topics.first.kafka.dup
69
+ def build_kafka
70
+ kafka = Setup::AttributesMap.consumer(@topics.first.kafka.dup)
71
+
72
+ # If we use static group memberships, there can be a case, where same instance id would
73
+ # be set on many subscription groups as the group instance id from Karafka perspective is
74
+ # set per config. Each instance even if they are subscribed to different topics needs to
75
+ # have if fully unique. To make sure of that, we just add extra postfix at the end that
76
+ # increments.
77
+ group_instance_id = kafka.fetch(:'group.instance.id', false)
41
78
 
79
+ kafka[:'group.instance.id'] = "#{group_instance_id}_#{@position}" if group_instance_id
42
80
  kafka[:'client.id'] ||= Karafka::App.config.client_id
43
81
  kafka[:'group.id'] ||= @topics.first.consumer_group.id
44
82
  kafka[:'auto.offset.reset'] ||= @topics.first.initial_offset
@@ -19,10 +19,15 @@ module Karafka
19
19
  max_messages
20
20
  max_wait_time
21
21
  initial_offset
22
+ subscription_group
22
23
  ].freeze
23
24
 
24
25
  private_constant :DISTRIBUTION_KEYS
25
26
 
27
+ def initialize
28
+ @position = -1
29
+ end
30
+
26
31
  # @param topics [Karafka::Routing::Topics] all the topics based on which we want to build
27
32
  # subscription groups
28
33
  # @return [Array<SubscriptionGroup>] all subscription groups we need in separate threads
@@ -33,7 +38,7 @@ module Karafka
33
38
  .values
34
39
  .map { |value| value.map(&:last) }
35
40
  .map { |topics_array| Routing::Topics.new(topics_array) }
36
- .map { |grouped_topics| SubscriptionGroup.new(grouped_topics) }
41
+ .map { |grouped_topics| SubscriptionGroup.new(@position += 1, grouped_topics) }
37
42
  end
38
43
 
39
44
  private
@@ -8,12 +8,12 @@ module Karafka
8
8
  class Topic
9
9
  attr_reader :id, :name, :consumer_group
10
10
  attr_writer :consumer
11
+ attr_accessor :subscription_group
11
12
 
12
13
  # Attributes we can inherit from the root unless they were defined on this level
13
14
  INHERITABLE_ATTRIBUTES = %i[
14
15
  kafka
15
16
  deserializer
16
- manual_offset_management
17
17
  max_messages
18
18
  max_wait_time
19
19
  initial_offset
@@ -27,6 +27,7 @@ module Karafka
27
27
  @name = name.to_s
28
28
  @consumer_group = consumer_group
29
29
  @attributes = {}
30
+ @active = true
30
31
  # @note We use identifier related to the consumer group that owns a topic, because from
31
32
  # Karafka 0.6 we can handle multiple Kafka instances with the same process and we can
32
33
  # have same topic name across multiple consumer groups
@@ -66,6 +67,12 @@ module Karafka
66
67
  end
67
68
  end
68
69
 
70
+ # Allows to disable topic by invoking this method and setting it to `false`.
71
+ # @param active [Boolean] should this topic be consumed or not
72
+ def active(active)
73
+ @active = active
74
+ end
75
+
69
76
  # @return [Class] consumer class that we should use
70
77
  # @note This is just an alias to the `#consumer` method. We however want to use it internally
71
78
  # instead of referencing the `#consumer`. We use this to indicate that this method returns
@@ -75,9 +82,12 @@ module Karafka
75
82
  consumer
76
83
  end
77
84
 
78
- # @return [Boolean] true if this topic offset is handled by the end user
79
- def manual_offset_management?
80
- manual_offset_management
85
+ # @return [Boolean] should this topic be in use
86
+ def active?
87
+ # Never active if disabled via routing
88
+ return false unless @active
89
+
90
+ Karafka::App.config.internal.routing.activity_manager.active?(:topics, name)
81
91
  end
82
92
 
83
93
  # @return [Hash] hash with all the topic attributes
@@ -90,8 +100,10 @@ module Karafka
90
100
  Hash[map].merge!(
91
101
  id: id,
92
102
  name: name,
103
+ active: active?,
93
104
  consumer: consumer,
94
- consumer_group_id: consumer_group.id
105
+ consumer_group_id: consumer_group.id,
106
+ subscription_group: subscription_group
95
107
  ).freeze
96
108
  end
97
109
  end
@@ -23,6 +23,14 @@ module Karafka
23
23
  @accumulator.each(&block)
24
24
  end
25
25
 
26
+ # Allows us to remove elements from the topics
27
+ #
28
+ # Block to decide what to delete
29
+ # @param block [Proc]
30
+ def delete_if(&block)
31
+ @accumulator.delete_if(&block)
32
+ end
33
+
26
34
  # Finds topic by its name
27
35
  #
28
36
  # @param topic_name [String] topic name
@@ -19,6 +19,7 @@ module Karafka
19
19
  # We aggregate threads here for a supervised shutdown process
20
20
  Karafka::Server.workers = workers
21
21
  Karafka::Server.listeners = listeners
22
+ Karafka::Server.jobs_queue = jobs_queue
22
23
 
23
24
  # All the listener threads need to finish
24
25
  listeners.each(&:join)
@@ -10,6 +10,7 @@ module Karafka
10
10
  # @param message [Karafka::Messages::Message] Message object that we want to deserialize
11
11
  # @return [Hash] hash with deserialized JSON data
12
12
  def call(message)
13
+ # nil payload can be present for example for tombstone messages
13
14
  message.raw_payload.nil? ? nil : ::JSON.parse(message.raw_payload)
14
15
  end
15
16
  end
@@ -20,17 +20,27 @@ module Karafka
20
20
  # Set of workers
21
21
  attr_accessor :workers
22
22
 
23
- # Writer for list of consumer groups that we want to consume in our current process context
24
- attr_writer :consumer_groups
23
+ # Jobs queue
24
+ attr_accessor :jobs_queue
25
25
 
26
26
  # Method which runs app
27
27
  def run
28
- # Since we do a lot of threading and queuing, we don't want to stop from the trap context
29
- # as some things may not work there as expected, that is why we spawn a separate thread to
30
- # handle the stopping process
31
- process.on_sigint { Thread.new { stop } }
32
- process.on_sigquit { Thread.new { stop } }
33
- process.on_sigterm { Thread.new { stop } }
28
+ self.listeners = []
29
+ self.workers = []
30
+
31
+ # We need to validate this prior to running because it may be executed also from the
32
+ # embedded
33
+ # We cannot validate this during the start because config needs to be populated and routes
34
+ # need to be defined.
35
+ Contracts::ServerCliOptions.new.validate!(
36
+ Karafka::App.config.internal.routing.activity_manager.to_h
37
+ )
38
+
39
+ process.on_sigint { stop }
40
+ process.on_sigquit { stop }
41
+ process.on_sigterm { stop }
42
+ process.on_sigtstp { quiet }
43
+ process.supervise
34
44
 
35
45
  # Start is blocking until stop is called and when we stop, it will wait until
36
46
  # all of the things are ready to stop
@@ -38,9 +48,9 @@ module Karafka
38
48
 
39
49
  # We always need to wait for Karafka to stop here since we should wait for the stop running
40
50
  # in a separate thread (or trap context) to indicate everything is closed
41
- # Since `#start` is blocking, we were get here only after the runner is done. This will
51
+ # Since `#start` is blocking, we will get here only after the runner is done. This will
42
52
  # not add any performance degradation because of that.
43
- Thread.pass until Karafka::App.stopped?
53
+ sleep(0.1) until Karafka::App.terminated?
44
54
  # Try its best to shutdown underlying components before re-raising
45
55
  # rubocop:disable Lint/RescueException
46
56
  rescue Exception => e
@@ -50,18 +60,10 @@ module Karafka
50
60
  raise e
51
61
  end
52
62
 
53
- # @return [Array<String>] array with names of consumer groups that should be consumed in a
54
- # current server context
55
- def consumer_groups
56
- # If not specified, a server will listen on all the topics
57
- @consumer_groups ||= Karafka::App.consumer_groups.map(&:name).freeze
58
- end
59
-
60
63
  # Starts Karafka with a supervision
61
64
  # @note We don't need to sleep because Karafka::Fetcher is locking and waiting to
62
65
  # finish loop (and it won't happen until we explicitly want to stop)
63
66
  def start
64
- process.supervise
65
67
  Karafka::App.run!
66
68
  Karafka::Runner.new.call
67
69
  end
@@ -73,6 +75,11 @@ module Karafka
73
75
  # lock them forever. If you need to run Karafka shutdown from within workers threads,
74
76
  # please start a separate thread to do so.
75
77
  def stop
78
+ # Initialize the stopping process only if Karafka was running
79
+ return if Karafka::App.stopping?
80
+ return if Karafka::App.stopped?
81
+ return if Karafka::App.terminated?
82
+
76
83
  Karafka::App.stop!
77
84
 
78
85
  timeout = Karafka::App.config.shutdown_timeout
@@ -81,13 +88,7 @@ module Karafka
81
88
  # their work and if so, we can just return and normal shutdown process will take place
82
89
  # We divide it by 1000 because we use time in ms.
83
90
  ((timeout / 1_000) * SUPERVISION_CHECK_FACTOR).to_i.times do
84
- if listeners.count(&:alive?).zero? &&
85
- workers.count(&:alive?).zero?
86
-
87
- Karafka::App.producer.close
88
-
89
- return
90
- end
91
+ return if listeners.count(&:alive?).zero? && workers.count(&:alive?).zero?
91
92
 
92
93
  sleep SUPERVISION_SLEEP
93
94
  end
@@ -108,12 +109,41 @@ module Karafka
108
109
  # This can cause memory leaks and crashes.
109
110
  listeners.each(&:shutdown)
110
111
 
111
- Karafka::App.producer.close
112
+ # We also do not forcefully terminate everything when running in the embedded mode,
113
+ # otherwise we would overwrite the shutdown process of the process that started Karafka
114
+ return unless process.supervised?
112
115
 
113
116
  # exit! is not within the instrumentation as it would not trigger due to exit
114
- Kernel.exit! FORCEFUL_EXIT_CODE
117
+ Kernel.exit!(FORCEFUL_EXIT_CODE)
115
118
  ensure
116
- Karafka::App.stopped!
119
+ # We need to check if it wasn't an early exit to make sure that only on stop invocation
120
+ # can change the status after everything is closed
121
+ if timeout
122
+ Karafka::App.stopped!
123
+
124
+ # We close producer as the last thing as it can be used in the notification pipeline
125
+ # to dispatch state changes, etc
126
+ Karafka::App.producer.close
127
+
128
+ Karafka::App.terminate!
129
+ end
130
+ end
131
+
132
+ # Quiets the Karafka server.
133
+ #
134
+ # Karafka will stop processing but won't quit the consumer group, so no rebalance will be
135
+ # triggered until final shutdown.
136
+ def quiet
137
+ # We don't have to safe-guard it with check states as the state transitions work only
138
+ # in one direction
139
+ Karafka::App.quiet!
140
+
141
+ # We need one more thread to monitor the process and move to quieted once everything
142
+ # is quiet and no processing is happening anymore
143
+ Thread.new do
144
+ sleep(0.1) until listeners.coordinators.all?(&:finished?)
145
+ Karafka::App.quieted!
146
+ end
117
147
  end
118
148
 
119
149
  private