karafka 1.4.0 → 2.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (172) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +89 -18
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +365 -1
  6. data/CONTRIBUTING.md +10 -19
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +56 -112
  9. data/LICENSE +17 -0
  10. data/LICENSE-COMM +89 -0
  11. data/LICENSE-LGPL +165 -0
  12. data/README.md +61 -68
  13. data/bin/benchmarks +85 -0
  14. data/bin/create_token +22 -0
  15. data/bin/integrations +272 -0
  16. data/bin/karafka +10 -0
  17. data/bin/scenario +29 -0
  18. data/bin/stress_many +13 -0
  19. data/bin/stress_one +13 -0
  20. data/certs/cert_chain.pem +26 -0
  21. data/certs/karafka-pro.pem +11 -0
  22. data/config/errors.yml +59 -38
  23. data/docker-compose.yml +10 -3
  24. data/karafka.gemspec +18 -21
  25. data/lib/active_job/karafka.rb +21 -0
  26. data/lib/active_job/queue_adapters/karafka_adapter.rb +26 -0
  27. data/lib/karafka/active_job/consumer.rb +26 -0
  28. data/lib/karafka/active_job/dispatcher.rb +38 -0
  29. data/lib/karafka/active_job/job_extensions.rb +34 -0
  30. data/lib/karafka/active_job/job_options_contract.rb +21 -0
  31. data/lib/karafka/active_job/routing/extensions.rb +33 -0
  32. data/lib/karafka/admin.rb +63 -0
  33. data/lib/karafka/app.rb +15 -20
  34. data/lib/karafka/base_consumer.rb +197 -31
  35. data/lib/karafka/cli/info.rb +44 -10
  36. data/lib/karafka/cli/install.rb +22 -12
  37. data/lib/karafka/cli/server.rb +17 -42
  38. data/lib/karafka/cli.rb +4 -3
  39. data/lib/karafka/connection/client.rb +379 -89
  40. data/lib/karafka/connection/listener.rb +250 -38
  41. data/lib/karafka/connection/listeners_batch.rb +24 -0
  42. data/lib/karafka/connection/messages_buffer.rb +84 -0
  43. data/lib/karafka/connection/pauses_manager.rb +46 -0
  44. data/lib/karafka/connection/raw_messages_buffer.rb +101 -0
  45. data/lib/karafka/connection/rebalance_manager.rb +78 -0
  46. data/lib/karafka/contracts/base.rb +17 -0
  47. data/lib/karafka/contracts/config.rb +88 -11
  48. data/lib/karafka/contracts/consumer_group.rb +21 -184
  49. data/lib/karafka/contracts/consumer_group_topic.rb +35 -11
  50. data/lib/karafka/contracts/server_cli_options.rb +19 -18
  51. data/lib/karafka/contracts.rb +1 -1
  52. data/lib/karafka/env.rb +46 -0
  53. data/lib/karafka/errors.rb +21 -21
  54. data/lib/karafka/helpers/async.rb +33 -0
  55. data/lib/karafka/helpers/colorize.rb +20 -0
  56. data/lib/karafka/helpers/multi_delegator.rb +2 -2
  57. data/lib/karafka/instrumentation/callbacks/error.rb +40 -0
  58. data/lib/karafka/instrumentation/callbacks/statistics.rb +41 -0
  59. data/lib/karafka/instrumentation/logger.rb +6 -10
  60. data/lib/karafka/instrumentation/logger_listener.rb +174 -0
  61. data/lib/karafka/instrumentation/monitor.rb +13 -61
  62. data/lib/karafka/instrumentation/notifications.rb +53 -0
  63. data/lib/karafka/instrumentation/proctitle_listener.rb +3 -3
  64. data/lib/karafka/instrumentation/vendors/datadog/dashboard.json +1 -0
  65. data/lib/karafka/instrumentation/vendors/datadog/listener.rb +232 -0
  66. data/lib/karafka/instrumentation.rb +21 -0
  67. data/lib/karafka/licenser.rb +75 -0
  68. data/lib/karafka/messages/batch_metadata.rb +45 -0
  69. data/lib/karafka/messages/builders/batch_metadata.rb +39 -0
  70. data/lib/karafka/messages/builders/message.rb +39 -0
  71. data/lib/karafka/messages/builders/messages.rb +34 -0
  72. data/lib/karafka/{params/params.rb → messages/message.rb} +7 -12
  73. data/lib/karafka/messages/messages.rb +64 -0
  74. data/lib/karafka/{params → messages}/metadata.rb +4 -6
  75. data/lib/karafka/messages/seek.rb +9 -0
  76. data/lib/karafka/patches/rdkafka/consumer.rb +22 -0
  77. data/lib/karafka/pro/active_job/consumer.rb +46 -0
  78. data/lib/karafka/pro/active_job/dispatcher.rb +61 -0
  79. data/lib/karafka/pro/active_job/job_options_contract.rb +32 -0
  80. data/lib/karafka/pro/base_consumer.rb +107 -0
  81. data/lib/karafka/pro/contracts/base.rb +21 -0
  82. data/lib/karafka/pro/contracts/consumer_group.rb +34 -0
  83. data/lib/karafka/pro/contracts/consumer_group_topic.rb +69 -0
  84. data/lib/karafka/pro/loader.rb +76 -0
  85. data/lib/karafka/pro/performance_tracker.rb +80 -0
  86. data/lib/karafka/pro/processing/coordinator.rb +85 -0
  87. data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +38 -0
  88. data/lib/karafka/pro/processing/jobs_builder.rb +32 -0
  89. data/lib/karafka/pro/processing/partitioner.rb +58 -0
  90. data/lib/karafka/pro/processing/scheduler.rb +56 -0
  91. data/lib/karafka/pro/routing/builder_extensions.rb +30 -0
  92. data/lib/karafka/pro/routing/topic_extensions.rb +74 -0
  93. data/lib/karafka/pro.rb +13 -0
  94. data/lib/karafka/process.rb +1 -0
  95. data/lib/karafka/processing/coordinator.rb +103 -0
  96. data/lib/karafka/processing/coordinators_buffer.rb +54 -0
  97. data/lib/karafka/processing/executor.rb +126 -0
  98. data/lib/karafka/processing/executors_buffer.rb +88 -0
  99. data/lib/karafka/processing/jobs/base.rb +55 -0
  100. data/lib/karafka/processing/jobs/consume.rb +47 -0
  101. data/lib/karafka/processing/jobs/revoked.rb +22 -0
  102. data/lib/karafka/processing/jobs/shutdown.rb +23 -0
  103. data/lib/karafka/processing/jobs_builder.rb +29 -0
  104. data/lib/karafka/processing/jobs_queue.rb +144 -0
  105. data/lib/karafka/processing/partitioner.rb +22 -0
  106. data/lib/karafka/processing/result.rb +37 -0
  107. data/lib/karafka/processing/scheduler.rb +22 -0
  108. data/lib/karafka/processing/worker.rb +91 -0
  109. data/lib/karafka/processing/workers_batch.rb +27 -0
  110. data/lib/karafka/railtie.rb +127 -0
  111. data/lib/karafka/routing/builder.rb +26 -23
  112. data/lib/karafka/routing/consumer_group.rb +37 -17
  113. data/lib/karafka/routing/consumer_mapper.rb +1 -2
  114. data/lib/karafka/routing/proxy.rb +9 -16
  115. data/lib/karafka/routing/router.rb +1 -1
  116. data/lib/karafka/routing/subscription_group.rb +53 -0
  117. data/lib/karafka/routing/subscription_groups_builder.rb +54 -0
  118. data/lib/karafka/routing/topic.rb +65 -24
  119. data/lib/karafka/routing/topics.rb +38 -0
  120. data/lib/karafka/runner.rb +51 -0
  121. data/lib/karafka/serialization/json/deserializer.rb +6 -15
  122. data/lib/karafka/server.rb +67 -26
  123. data/lib/karafka/setup/config.rb +153 -175
  124. data/lib/karafka/status.rb +14 -5
  125. data/lib/karafka/templates/example_consumer.rb.erb +16 -0
  126. data/lib/karafka/templates/karafka.rb.erb +17 -55
  127. data/lib/karafka/time_trackers/base.rb +19 -0
  128. data/lib/karafka/time_trackers/pause.rb +92 -0
  129. data/lib/karafka/time_trackers/poll.rb +65 -0
  130. data/lib/karafka/version.rb +1 -1
  131. data/lib/karafka.rb +46 -16
  132. data.tar.gz.sig +0 -0
  133. metadata +145 -171
  134. metadata.gz.sig +0 -0
  135. data/.github/FUNDING.yml +0 -3
  136. data/MIT-LICENCE +0 -18
  137. data/certs/mensfeld.pem +0 -25
  138. data/lib/karafka/attributes_map.rb +0 -62
  139. data/lib/karafka/backends/inline.rb +0 -16
  140. data/lib/karafka/base_responder.rb +0 -226
  141. data/lib/karafka/cli/flow.rb +0 -48
  142. data/lib/karafka/code_reloader.rb +0 -67
  143. data/lib/karafka/connection/api_adapter.rb +0 -161
  144. data/lib/karafka/connection/batch_delegator.rb +0 -55
  145. data/lib/karafka/connection/builder.rb +0 -18
  146. data/lib/karafka/connection/message_delegator.rb +0 -36
  147. data/lib/karafka/consumers/batch_metadata.rb +0 -10
  148. data/lib/karafka/consumers/callbacks.rb +0 -71
  149. data/lib/karafka/consumers/includer.rb +0 -64
  150. data/lib/karafka/consumers/responders.rb +0 -24
  151. data/lib/karafka/consumers/single_params.rb +0 -15
  152. data/lib/karafka/contracts/responder_usage.rb +0 -54
  153. data/lib/karafka/fetcher.rb +0 -42
  154. data/lib/karafka/helpers/class_matcher.rb +0 -88
  155. data/lib/karafka/helpers/config_retriever.rb +0 -46
  156. data/lib/karafka/helpers/inflector.rb +0 -26
  157. data/lib/karafka/instrumentation/stdout_listener.rb +0 -140
  158. data/lib/karafka/params/batch_metadata.rb +0 -26
  159. data/lib/karafka/params/builders/batch_metadata.rb +0 -30
  160. data/lib/karafka/params/builders/params.rb +0 -38
  161. data/lib/karafka/params/builders/params_batch.rb +0 -25
  162. data/lib/karafka/params/params_batch.rb +0 -60
  163. data/lib/karafka/patches/ruby_kafka.rb +0 -47
  164. data/lib/karafka/persistence/client.rb +0 -29
  165. data/lib/karafka/persistence/consumers.rb +0 -45
  166. data/lib/karafka/persistence/topics.rb +0 -48
  167. data/lib/karafka/responders/builder.rb +0 -36
  168. data/lib/karafka/responders/topic.rb +0 -55
  169. data/lib/karafka/routing/topic_mapper.rb +0 -53
  170. data/lib/karafka/serialization/json/serializer.rb +0 -31
  171. data/lib/karafka/setup/configurators/water_drop.rb +0 -36
  172. data/lib/karafka/templates/application_responder.rb.erb +0 -11
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Karafka
4
- # Both Karafka and Ruby-Kafka contain a lot of settings that can be applied on multiple
5
- # levels. In Karafka that is on consumer group and on the topic level. In Ruby-Kafka it
6
- # is on consumer, subscription and consumption levels. In order to maintain an order
7
- # in managing those settings, this module was created. It contains details on what setting
8
- # where should go and which layer (both on Karafka and Ruby-Kafka) is responsible for
9
- # setting it and sending it forward
10
- # @note Settings presented here cover all the settings that are being used across Karafka
11
- module AttributesMap
12
- class << self
13
- # What settings should go where in ruby-kafka
14
- # @return [Hash] hash with proper sections on what to proxy where in Ruby-Kafka
15
- # @note All other settings will be passed to Kafka.new method invocation.
16
- # All elements in this hash are just edge cases
17
- def api_adapter
18
- {
19
- consumer: %i[
20
- session_timeout offset_commit_interval offset_commit_threshold
21
- offset_retention_time heartbeat_interval fetcher_max_queue_size
22
- ],
23
- subscribe: %i[start_from_beginning max_bytes_per_partition],
24
- consumption: %i[min_bytes max_bytes max_wait_time],
25
- pause: %i[pause_timeout pause_max_timeout pause_exponential_backoff],
26
- # All the options that are under kafka config namespace, but are not used
27
- # directly with kafka api, but from the Karafka user perspective, they are
28
- # still related to kafka. They should not be proxied anywhere
29
- ignored: %i[reconnect_timeout automatically_mark_as_consumed]
30
- }
31
- end
32
-
33
- # @return [Array<Symbol>] properties that can be set on a per topic level
34
- def topic
35
- (api_adapter[:subscribe] + %i[
36
- backend
37
- name
38
- deserializer
39
- responder
40
- batch_consuming
41
- ]).uniq
42
- end
43
-
44
- # @return [Array<Symbol>] properties that can be set on a per consumer group level
45
- # @note Note that there are settings directly extracted from the config kafka namespace
46
- # I did this that way, so I won't have to repeat same setting keys over and over again
47
- # Thanks to this solution, if any new setting is available for ruby-kafka, we just need
48
- # to add it to our configuration class and it will be handled automatically.
49
- def consumer_group
50
- # @note We don't ignore the api_adapter[:ignored] values as they should be ignored
51
- # only when proxying details go ruby-kafka. We use ignored fields internally in karafka
52
- ignored_settings = api_adapter[:subscribe]
53
- defined_settings = api_adapter.values.flatten
54
- karafka_settings = %i[batch_fetching]
55
-
56
- dynamically_proxied = Karafka::Setup::Config.config.kafka.to_h.keys
57
-
58
- (defined_settings + dynamically_proxied).uniq + karafka_settings - ignored_settings
59
- end
60
- end
61
- end
62
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Karafka
4
- # Namespace for all different backends Karafka supports
5
- module Backends
6
- # Backend that just runs stuff asap without any scheduling
7
- module Inline
8
- private
9
-
10
- # Executes consume code immediately (without enqueuing)
11
- def process
12
- Karafka.monitor.instrument('backends.inline.process', caller: self) { consume }
13
- end
14
- end
15
- end
16
- end
@@ -1,226 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Karafka
4
- # Base responder from which all Karafka responders should inherit
5
- # Similar to Rails responders concept. It allows us to design flow from one app to another
6
- # by isolating what responses should be sent (and where) based on a given action
7
- # It differs from Rails responders in the way it works: in std http request we can have one
8
- # response, here we can have unlimited number of them
9
- #
10
- # It has a simple API for defining where should we respond (and if it is required)
11
- #
12
- # @example Basic usage (each registered topic is required to be used by default)
13
- # class Responder < BaseResponder
14
- # topic :new_action
15
- #
16
- # def respond(data)
17
- # respond_to :new_action, data
18
- # end
19
- # end
20
- #
21
- # @example Responding to a topic with extra options
22
- # class Responder < BaseResponder
23
- # topic :new_action
24
- #
25
- # def respond(data)
26
- # respond_to :new_action, data, partition_key: 'thing'
27
- # end
28
- # end
29
- #
30
- # @example Marking topic as not required (we won't have to use it)
31
- # class Responder < BaseResponder
32
- # topic :required_topic
33
- # topic :new_action, required: false
34
- #
35
- # def respond(data)
36
- # respond_to :required_topic, data
37
- # end
38
- # end
39
- #
40
- # @example Multiple times used topic
41
- # class Responder < BaseResponder
42
- # topic :required_topic
43
- #
44
- # def respond(data)
45
- # data.each do |subset|
46
- # respond_to :required_topic, subset
47
- # end
48
- # end
49
- # end
50
- #
51
- # @example Specify serializer for a topic
52
- # class Responder < BaseResponder
53
- # topic :xml_topic, serializer: MyXMLSerializer
54
- #
55
- # def respond(data)
56
- # data.each do |subset|
57
- # respond_to :xml_topic, subset
58
- # end
59
- # end
60
- # end
61
- #
62
- # @example Accept multiple arguments to a respond method
63
- # class Responder < BaseResponder
64
- # topic :users_actions
65
- # topic :articles_viewed
66
- #
67
- # def respond(user, article)
68
- # respond_to :users_actions, user
69
- # respond_to :articles_viewed, article
70
- # end
71
- # end
72
- class BaseResponder
73
- # Responder usage contract
74
- CONTRACT = Karafka::Contracts::ResponderUsage.new.freeze
75
-
76
- private_constant :CONTRACT
77
-
78
- class << self
79
- # Definitions of all topics that we want to be able to use in this responder should go here
80
- attr_accessor :topics
81
- # Contract that we can use to control and/or require some additional details upon options
82
- # that are being passed to the producer. This can be in particular useful if we want to make
83
- # sure that for example partition_key is always present.
84
- attr_accessor :options_contract
85
-
86
- # Registers a topic as on to which we will be able to respond
87
- # @param topic_name [Symbol, String] name of topic to which we want to respond
88
- # @param options [Hash] hash with optional configuration details
89
- def topic(topic_name, options = {})
90
- options[:serializer] ||= Karafka::App.config.serializer
91
- options[:registered] = true
92
- self.topics ||= {}
93
- topic_obj = Responders::Topic.new(topic_name, options)
94
- self.topics[topic_obj.name] = topic_obj
95
- end
96
-
97
- # A simple alias for easier standalone responder usage.
98
- # Instead of building it with new.call it allows (in case of using JSON serializer)
99
- # to just run it directly from the class level
100
- # @param data Anything that we want to respond with
101
- # @example Send user data with a responder
102
- # UsersCreatedResponder.call(@created_user)
103
- def call(*data)
104
- # Just in case there were no topics defined for a responder, we initialize with
105
- # empty hash not to handle a nil case
106
- self.topics ||= {}
107
- new.call(*data)
108
- end
109
- end
110
-
111
- attr_reader :messages_buffer
112
-
113
- # Creates a responder object
114
- # @return [Karafka::BaseResponder] base responder descendant responder
115
- def initialize
116
- @messages_buffer = {}
117
- end
118
-
119
- # Performs respond and validates that all the response requirement were met
120
- # @param data Anything that we want to respond with
121
- # @note We know that validators should be executed also before sending data to topics, however
122
- # the implementation gets way more complicated then, that's why we check after everything
123
- # was sent using responder
124
- # @example Send user data with a responder
125
- # UsersCreatedResponder.new.call(@created_user)
126
- # @example Send user data with a responder using non default Parser
127
- # UsersCreatedResponder.new(MyParser).call(@created_user)
128
- def call(*data)
129
- respond(*data)
130
- validate_usage!
131
- validate_options!
132
- deliver!
133
- end
134
-
135
- private
136
-
137
- # Checks if we met all the topics requirements. It will fail if we didn't send a message to
138
- # a registered required topic, etc.
139
- def validate_usage!
140
- registered_topics = self.class.topics.map do |name, topic|
141
- topic.to_h.merge!(
142
- usage_count: messages_buffer[name]&.count || 0
143
- )
144
- end
145
-
146
- used_topics = messages_buffer.map do |name, usage|
147
- topic = self.class.topics[name] || Responders::Topic.new(name, registered: false)
148
- topic.to_h.merge!(usage_count: usage.count)
149
- end
150
-
151
- result = CONTRACT.call(
152
- registered_topics: registered_topics,
153
- used_topics: used_topics
154
- )
155
-
156
- return if result.success?
157
-
158
- raise Karafka::Errors::InvalidResponderUsageError, result.errors.to_h
159
- end
160
-
161
- # Checks if we met all the options requirements before sending them to the producer.
162
- def validate_options!
163
- return true unless self.class.options_contract
164
-
165
- messages_buffer.each_value do |messages_set|
166
- messages_set.each do |message_data|
167
- result = self.class.options_contract.call(message_data.last)
168
- next if result.success?
169
-
170
- raise Karafka::Errors::InvalidResponderMessageOptionsError, result.errors.to_h
171
- end
172
- end
173
- end
174
-
175
- # Takes all the messages from the buffer and delivers them one by one
176
- # @note This method is executed after the validation, so we're sure that
177
- # what we send is legit and it will go to a proper topics
178
- def deliver!
179
- messages_buffer.each_value do |data_elements|
180
- data_elements.each do |data, options|
181
- # We map this topic name, so it will match namespaced/etc topic in Kafka
182
- # @note By default will not change topic (if default mapper used)
183
- mapped_topic = Karafka::App.config.topic_mapper.outgoing(options[:topic])
184
- external_options = options.merge(topic: mapped_topic)
185
- producer(options).call(data, external_options)
186
- end
187
- end
188
- end
189
-
190
- # Method that needs to be implemented in a subclass. It should handle responding
191
- # on registered topics
192
- # @param _data [Object] anything that we want to use to send to Kafka
193
- # @raise [NotImplementedError] This method needs to be implemented in a subclass
194
- def respond(*_data)
195
- raise NotImplementedError, 'Implement this in a subclass'
196
- end
197
-
198
- # This method allow us to respond to a single topic with a given data. It can be used
199
- # as many times as we need. Especially when we have 1:n flow
200
- # @param topic [Symbol, String] topic to which we want to respond
201
- # @param data [String, Object] string or object that we want to send
202
- # @param options [Hash] options for waterdrop (e.g. partition_key).
203
- # @note Respond to does not accept multiple data arguments.
204
- def respond_to(topic, data, options = {})
205
- # We normalize the format to string, as WaterDrop and Ruby-Kafka support only
206
- # string topics
207
- topic = topic.to_s
208
-
209
- messages_buffer[topic] ||= []
210
- messages_buffer[topic] << [
211
- self.class.topics[topic].serializer.call(data),
212
- options.merge(topic: topic)
213
- ]
214
- end
215
-
216
- # @param options [Hash] options for waterdrop
217
- # @return [Class] WaterDrop producer (sync or async based on the settings)
218
- def producer(options)
219
- if self.class.topics[options[:topic]].async?
220
- WaterDrop::AsyncProducer
221
- else
222
- WaterDrop::SyncProducer
223
- end
224
- end
225
- end
226
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Karafka
4
- # Karafka framework Cli
5
- class Cli < Thor
6
- # Description of topics flow (incoming/outgoing)
7
- class Flow < Base
8
- desc 'Print application data flow (incoming => outgoing)'
9
-
10
- # Print out all defined routes in alphabetical order
11
- def call
12
- topics.each do |topic|
13
- any_topics = !topic.responder&.topics.nil?
14
- log_messages = []
15
-
16
- if any_topics
17
- log_messages << "#{topic.name} =>"
18
-
19
- topic.responder.topics.each_value do |responder_topic|
20
- features = []
21
- features << (responder_topic.required? ? 'always' : 'conditionally')
22
-
23
- log_messages << format(responder_topic.name, "(#{features.join(', ')})")
24
- end
25
- else
26
- log_messages << "#{topic.name} => (nothing)"
27
- end
28
-
29
- Karafka.logger.info(log_messages.join("\n"))
30
- end
31
- end
32
-
33
- private
34
-
35
- # @return [Array<Karafka::Routing::Topic>] all topics sorted in alphabetical order
36
- def topics
37
- Karafka::App.consumer_groups.map(&:topics).flatten.sort_by(&:name)
38
- end
39
-
40
- # Formats a given value with label in a nice way
41
- # @param label [String] label describing value
42
- # @param value [String] value that should be printed
43
- def format(label, value)
44
- " - #{label}: #{value}"
45
- end
46
- end
47
- end
48
- end
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Karafka
4
- # Special type of a listener, that is not an instrumentation one, but one that triggers
5
- # code reload in the development mode after each fetched batch (or message)
6
- #
7
- # Please refer to the development code reload sections for details on the benefits and downsides
8
- # of the in-process code reloading
9
- class CodeReloader
10
- # This mutex is needed as we might have an application that has multiple consumer groups
11
- # running in separate threads and we should not trigger reload before fully reloading the app
12
- # in previous thread
13
- MUTEX = Mutex.new
14
-
15
- private_constant :MUTEX
16
-
17
- # @param reloaders [Array<Object>] any code loaders that we use in this app. Whether it is
18
- # the Rails loader, Zeitwerk or anything else that allows reloading triggering
19
- # @param block [Proc] yields given block just before reloading. This can be used to hook custom
20
- # reloading stuff, that ain't reloaders (for example for resetting dry-events registry)
21
- def initialize(*reloaders, &block)
22
- @reloaders = reloaders
23
- @block = block
24
- end
25
-
26
- # Binds to the instrumentation events and triggers reload
27
- # @param _event [Dry::Event] empty dry event
28
- # @note Since we de-register all the user defined objects and redraw routes, it means that
29
- # we won't be able to do a multi-batch buffering in the development mode as each of the
30
- # batches will be buffered on a newly created "per fetch" instance.
31
- def on_connection_listener_fetch_loop(_event)
32
- reload
33
- end
34
-
35
- private
36
-
37
- # Triggers reload of both standard and Rails reloaders as well as expires all internals of
38
- # Karafka, so it can be rediscovered and rebuilt
39
- def reload
40
- MUTEX.synchronize do
41
- if @reloaders[0].respond_to?(:execute)
42
- reload_with_rails
43
- else
44
- reload_without_rails
45
- end
46
- end
47
- end
48
-
49
- # Rails reloading procedure
50
- def reload_with_rails
51
- updatable = @reloaders.select(&:updated?)
52
-
53
- return if updatable.empty?
54
-
55
- updatable.each(&:execute)
56
- @block&.call
57
- Karafka::App.reload
58
- end
59
-
60
- # Zeitwerk and other reloaders
61
- def reload_without_rails
62
- @reloaders.each(&:reload)
63
- @block&.call
64
- Karafka::App.reload
65
- end
66
- end
67
- end
@@ -1,161 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Karafka
4
- # Namespace for all the things related to Kafka connection
5
- module Connection
6
- # Mapper used to convert our internal settings into ruby-kafka settings based on their
7
- # API requirements.
8
- # Since ruby-kafka has more and more options and there are few "levels" on which
9
- # we have to apply them (despite the fact, that in Karafka you configure all of it
10
- # in one place), we have to remap it into what ruby-kafka driver requires
11
- # @note The good thing about Kafka.new method is that it ignores all options that
12
- # do nothing. So we don't have to worry about injecting our internal settings
13
- # into the client and breaking stuff
14
- module ApiAdapter
15
- class << self
16
- # Builds all the configuration settings for Kafka.new method
17
- # @param consumer_group [Karafka::Routing::ConsumerGroup] consumer group details
18
- # @return [Array<Hash>] Array with all the client arguments including hash with all
19
- # the settings required by Kafka.new method
20
- # @note We return array, so we can inject any arguments we want, in case of changes in the
21
- # raw driver
22
- def client(consumer_group)
23
- # This one is a default that takes all the settings except special
24
- # cases defined in the map
25
- settings = {
26
- logger: ::Karafka.logger,
27
- client_id: ::Karafka::App.config.client_id
28
- }
29
-
30
- kafka_configs.each_key do |setting_name|
31
- # All options for config adapter should be ignored as we're just interested
32
- # in what is left, as we want to pass all the options that are "typical"
33
- # and not listed in the api_adapter special cases mapping. All the values
34
- # from the api_adapter mapping go somewhere else, not to the client directly
35
- next if AttributesMap.api_adapter.values.flatten.include?(setting_name)
36
-
37
- # Settings for each consumer group are either defined per consumer group or are
38
- # inherited from the global/general settings level, thus we don't have to fetch them
39
- # from the kafka settings as they are already on a consumer group level
40
- settings[setting_name] = consumer_group.public_send(setting_name)
41
- end
42
-
43
- settings_hash = sanitize(settings)
44
-
45
- # Normalization for the way Kafka::Client accepts arguments from 0.5.3
46
- [settings_hash.delete(:seed_brokers), settings_hash]
47
- end
48
-
49
- # Builds all the configuration settings for kafka#consumer method
50
- # @param consumer_group [Karafka::Routing::ConsumerGroup] consumer group details
51
- # @return [Array<Hash>] array with all the consumer arguments including hash with all
52
- # the settings required by Kafka#consumer
53
- def consumer(consumer_group)
54
- settings = { group_id: consumer_group.id }
55
- settings = fetch_for(:consumer, consumer_group, settings)
56
- [sanitize(settings)]
57
- end
58
-
59
- # Builds all the configuration settings for kafka consumer consume_each_batch and
60
- # consume_each_message methods
61
- # @param consumer_group [Karafka::Routing::ConsumerGroup] consumer group details
62
- # @return [Array<Hash>] Array with all the arguments required by consuming method
63
- # including hash with all the settings required by
64
- # Kafka::Consumer#consume_each_message and Kafka::Consumer#consume_each_batch method
65
- def consumption(consumer_group)
66
- [
67
- sanitize(
68
- fetch_for(
69
- :consumption,
70
- consumer_group,
71
- automatically_mark_as_processed: consumer_group.automatically_mark_as_consumed
72
- )
73
- )
74
- ]
75
- end
76
-
77
- # Builds all the configuration settings for kafka consumer#subscribe method
78
- # @param topic [Karafka::Routing::Topic] topic that holds details for a given subscription
79
- # @return [Hash] hash with all the settings required by kafka consumer#subscribe method
80
- def subscribe(topic)
81
- settings = fetch_for(:subscribe, topic)
82
- [Karafka::App.config.topic_mapper.outgoing(topic.name), sanitize(settings)]
83
- end
84
-
85
- # Builds all the configuration settings required by kafka consumer#pause method
86
- # @param topic [String] topic that we want to pause
87
- # @param partition [Integer] number partition that we want to pause
88
- # @param consumer_group [Karafka::Routing::ConsumerGroup] consumer group details
89
- # @return [Array] array with all the details required to pause kafka consumer
90
- def pause(topic, partition, consumer_group)
91
- [
92
- Karafka::App.config.topic_mapper.outgoing(topic),
93
- partition,
94
- {
95
- timeout: consumer_group.pause_timeout,
96
- max_timeout: consumer_group.pause_max_timeout,
97
- exponential_backoff: consumer_group.pause_exponential_backoff
98
- }
99
- ]
100
- end
101
-
102
- # Remaps topic details taking the topic mapper feature into consideration.
103
- # @param params [Karafka::Params::Params] params instance
104
- # @return [Array] array with all the details needed by ruby-kafka to mark message
105
- # as processed
106
- # @note When default empty topic mapper is used, no need for any conversion as the
107
- # internal and external format are exactly the same
108
- def mark_message_as_processed(params)
109
- # Majority of users don't use custom topic mappers. No need to change anything when it
110
- # is a default mapper that does not change anything. Only some cloud providers require
111
- # topics to be remapped
112
- return [params.metadata] if Karafka::App.config.topic_mapper.is_a?(
113
- Karafka::Routing::TopicMapper
114
- )
115
-
116
- # @note We don't use tap as it is around 13% slower than non-dup version
117
- dupped = params.metadata.dup
118
- dupped['topic'] = Karafka::App.config.topic_mapper.outgoing(params.metadata.topic)
119
- [dupped]
120
- end
121
-
122
- private
123
-
124
- # Fetches proper settings for a given map namespace
125
- # @param namespace_key [Symbol] namespace from attributes map config adapter hash
126
- # @param route_layer [Object] route topic or consumer group
127
- # @param preexisting_settings [Hash] hash with some preexisting settings that might have
128
- # been loaded in a different way
129
- def fetch_for(namespace_key, route_layer, preexisting_settings = {})
130
- kafka_configs.each_key do |setting_name|
131
- # Ignore settings that are not related to our namespace
132
- next unless AttributesMap.api_adapter[namespace_key].include?(setting_name)
133
-
134
- # Ignore settings that are already initialized
135
- # In case they are in preexisting settings fetched differently
136
- next if preexisting_settings.key?(setting_name)
137
-
138
- # Fetch all the settings from a given layer object. Objects can handle the fallback
139
- # to the kafka settings, so
140
- preexisting_settings[setting_name] = route_layer.send(setting_name)
141
- end
142
-
143
- preexisting_settings
144
- end
145
-
146
- # Removes nil containing keys from the final settings so it can use Kafkas driver
147
- # defaults for those
148
- # @param settings [Hash] settings that may contain nil values
149
- # @return [Hash] settings without nil using keys (non of karafka options should be nil)
150
- def sanitize(settings)
151
- settings.reject { |_key, value| value.nil? }
152
- end
153
-
154
- # @return [Hash] Kafka config details as a hash
155
- def kafka_configs
156
- ::Karafka::App.config.kafka.to_h
157
- end
158
- end
159
- end
160
- end
161
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Karafka
4
- module Connection
5
- # Class that delegates processing of batch received messages for which we listen to
6
- # a proper processor
7
- module BatchDelegator
8
- class << self
9
- # Delegates messages (does something with them)
10
- # It will either schedule or run a proper processor action for messages
11
- # @param group_id [String] group_id of a group from which a given message came
12
- # @param kafka_batch [<Kafka::FetchedBatch>] raw messages fetched batch
13
- # @note This should be looped to obtain a constant delegating of new messages
14
- def call(group_id, kafka_batch)
15
- topic = Persistence::Topics.fetch(group_id, kafka_batch.topic)
16
- consumer = Persistence::Consumers.fetch(topic, kafka_batch.partition)
17
-
18
- Karafka.monitor.instrument(
19
- 'connection.batch_delegator.call',
20
- caller: self,
21
- consumer: consumer,
22
- kafka_batch: kafka_batch
23
- ) do
24
- # Due to how ruby-kafka is built, we have the metadata that is stored on the batch
25
- # level only available for batch consuming
26
- consumer.batch_metadata = Params::Builders::BatchMetadata.from_kafka_batch(
27
- kafka_batch,
28
- topic
29
- )
30
-
31
- kafka_messages = kafka_batch.messages
32
-
33
- # Depending on a case (persisted or not) we might use new consumer instance per
34
- # each batch, or use the same one for all of them (for implementing buffering, etc.)
35
- if topic.batch_consuming
36
- consumer.params_batch = Params::Builders::ParamsBatch.from_kafka_messages(
37
- kafka_messages,
38
- topic
39
- )
40
- consumer.call
41
- else
42
- kafka_messages.each do |kafka_message|
43
- consumer.params_batch = Params::Builders::ParamsBatch.from_kafka_messages(
44
- [kafka_message],
45
- topic
46
- )
47
- consumer.call
48
- end
49
- end
50
- end
51
- end
52
- end
53
- end
54
- end
55
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Karafka
4
- module Connection
5
- # Builder used to construct Kafka client
6
- module Builder
7
- class << self
8
- # Builds a Kafka::Client instance that we use to work with Kafka cluster
9
- # @param consumer_group [Karafka::Routing::ConsumerGroup] consumer group for which we want
10
- # to have a new Kafka client
11
- # @return [::Kafka::Client] returns a Kafka client
12
- def call(consumer_group)
13
- Kafka.new(*ApiAdapter.client(consumer_group))
14
- end
15
- end
16
- end
17
- end
18
- end