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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/ci.yml +40 -16
- data/.rspec +4 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +459 -796
- data/Gemfile +3 -2
- data/Gemfile.lock +44 -32
- data/LICENSE +1 -1
- data/README.md +22 -17
- data/bin/integrations +86 -26
- data/bin/karafka +1 -13
- data/bin/rspecs +6 -0
- data/bin/verify_license_integrity +37 -0
- data/certs/cert_chain.pem +26 -0
- data/config/{errors.yml → locales/errors.yml} +34 -6
- data/config/locales/pro_errors.yml +34 -0
- data/docker-compose.yml +4 -30
- data/karafka.gemspec +17 -10
- data/lib/active_job/karafka.rb +3 -7
- data/lib/active_job/queue_adapters/karafka_adapter.rb +7 -1
- data/lib/karafka/active_job/consumer.rb +29 -6
- data/lib/karafka/active_job/current_attributes/loading.rb +36 -0
- data/lib/karafka/active_job/current_attributes/persistence.rb +28 -0
- data/lib/karafka/active_job/current_attributes.rb +42 -0
- data/lib/karafka/active_job/dispatcher.rb +34 -3
- data/lib/karafka/active_job/job_options_contract.rb +13 -2
- data/lib/karafka/admin.rb +246 -0
- data/lib/karafka/app.rb +30 -7
- data/lib/karafka/base_consumer.rb +124 -82
- data/lib/karafka/cli/base.rb +20 -0
- data/lib/karafka/cli/console.rb +13 -8
- data/lib/karafka/cli/info.rb +3 -2
- data/lib/karafka/cli/server.rb +55 -8
- data/lib/karafka/cli/topics.rb +146 -0
- data/lib/karafka/connection/client.rb +118 -61
- data/lib/karafka/connection/consumer_group_coordinator.rb +48 -0
- data/lib/karafka/connection/listener.rb +78 -30
- data/lib/karafka/connection/listeners_batch.rb +18 -2
- data/lib/karafka/connection/rebalance_manager.rb +3 -0
- data/lib/karafka/contracts/config.rb +1 -1
- data/lib/karafka/contracts/consumer_group.rb +22 -9
- data/lib/karafka/contracts/server_cli_options.rb +71 -11
- data/lib/karafka/contracts/topic.rb +65 -0
- data/lib/karafka/contracts.rb +1 -1
- data/lib/karafka/embedded.rb +36 -0
- data/lib/karafka/errors.rb +6 -4
- data/lib/karafka/helpers/colorize.rb +6 -0
- data/lib/karafka/instrumentation/callbacks/error.rb +3 -4
- data/lib/karafka/instrumentation/callbacks/statistics.rb +14 -4
- data/lib/karafka/instrumentation/logger_listener.rb +151 -22
- data/lib/karafka/instrumentation/notifications.rb +26 -7
- data/lib/karafka/instrumentation/proctitle_listener.rb +7 -16
- data/lib/karafka/instrumentation/vendors/datadog/dashboard.json +1 -1
- data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +153 -0
- data/lib/karafka/instrumentation/vendors/datadog/{listener.rb → metrics_listener.rb} +62 -35
- data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +166 -0
- data/lib/karafka/licenser.rb +53 -50
- data/lib/karafka/messages/builders/batch_metadata.rb +8 -8
- data/lib/karafka/messages/builders/messages.rb +6 -2
- data/lib/karafka/messages/message.rb +14 -2
- data/lib/karafka/messages/messages.rb +5 -0
- data/lib/karafka/messages/parser.rb +14 -0
- data/lib/karafka/patches/rdkafka/bindings.rb +132 -0
- data/lib/karafka/pro/active_job/consumer.rb +10 -9
- data/lib/karafka/pro/active_job/dispatcher.rb +28 -3
- data/lib/karafka/pro/active_job/job_options_contract.rb +16 -3
- data/lib/karafka/pro/encryption/cipher.rb +58 -0
- data/lib/karafka/pro/encryption/contracts/config.rb +79 -0
- data/lib/karafka/pro/encryption/errors.rb +24 -0
- data/lib/karafka/pro/encryption/messages/middleware.rb +46 -0
- data/lib/karafka/pro/encryption/messages/parser.rb +56 -0
- data/lib/karafka/pro/encryption/setup/config.rb +48 -0
- data/lib/karafka/pro/encryption.rb +47 -0
- data/lib/karafka/pro/iterator.rb +253 -0
- data/lib/karafka/pro/loader.rb +52 -26
- data/lib/karafka/pro/performance_tracker.rb +10 -6
- data/lib/karafka/pro/processing/collapser.rb +62 -0
- data/lib/karafka/pro/processing/coordinator.rb +92 -16
- data/lib/karafka/pro/processing/filters/base.rb +61 -0
- data/lib/karafka/pro/processing/filters/delayer.rb +70 -0
- data/lib/karafka/pro/processing/filters/expirer.rb +51 -0
- data/lib/karafka/pro/processing/filters/throttler.rb +84 -0
- data/lib/karafka/pro/processing/filters/virtual_limiter.rb +52 -0
- data/lib/karafka/pro/processing/filters_applier.rb +104 -0
- data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +7 -5
- data/lib/karafka/pro/processing/jobs/revoked_non_blocking.rb +37 -0
- data/lib/karafka/pro/processing/jobs_builder.rb +22 -4
- data/lib/karafka/pro/processing/partitioner.rb +33 -24
- data/lib/karafka/pro/processing/scheduler.rb +27 -8
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +68 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +74 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom.rb +72 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +76 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +64 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +68 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_mom.rb +64 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +69 -0
- data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom.rb +38 -0
- data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom_vp.rb +64 -0
- data/lib/karafka/pro/processing/strategies/aj/ftr_mom.rb +38 -0
- data/lib/karafka/pro/processing/strategies/aj/ftr_mom_vp.rb +58 -0
- data/lib/karafka/pro/processing/strategies/aj/lrj_mom.rb +37 -0
- data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +79 -0
- data/lib/karafka/pro/processing/strategies/aj/mom.rb +36 -0
- data/lib/karafka/pro/processing/strategies/aj/mom_vp.rb +52 -0
- data/lib/karafka/pro/processing/strategies/base.rb +26 -0
- data/lib/karafka/pro/processing/strategies/default.rb +105 -0
- data/lib/karafka/pro/processing/strategies/dlq/default.rb +131 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr.rb +61 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj.rb +75 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +71 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom_vp.rb +43 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_vp.rb +41 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +69 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_mom_vp.rb +41 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_vp.rb +40 -0
- data/lib/karafka/pro/processing/strategies/dlq/lrj.rb +63 -0
- data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +63 -0
- data/lib/karafka/pro/processing/strategies/dlq/lrj_mom_vp.rb +36 -0
- data/lib/karafka/pro/processing/strategies/dlq/lrj_vp.rb +39 -0
- data/lib/karafka/pro/processing/strategies/dlq/mom.rb +68 -0
- data/lib/karafka/pro/processing/strategies/dlq/mom_vp.rb +37 -0
- data/lib/karafka/pro/processing/strategies/dlq/vp.rb +40 -0
- data/lib/karafka/pro/processing/strategies/ftr/default.rb +104 -0
- data/lib/karafka/pro/processing/strategies/ftr/vp.rb +40 -0
- data/lib/karafka/pro/processing/strategies/lrj/default.rb +87 -0
- data/lib/karafka/pro/processing/strategies/lrj/ftr.rb +69 -0
- data/lib/karafka/pro/processing/strategies/lrj/ftr_mom.rb +67 -0
- data/lib/karafka/pro/processing/strategies/lrj/ftr_mom_vp.rb +40 -0
- data/lib/karafka/pro/processing/strategies/lrj/ftr_vp.rb +39 -0
- data/lib/karafka/pro/processing/strategies/lrj/mom.rb +80 -0
- data/lib/karafka/pro/processing/strategies/lrj/mom_vp.rb +38 -0
- data/lib/karafka/pro/processing/strategies/lrj/vp.rb +36 -0
- data/lib/karafka/pro/processing/strategies/mom/default.rb +46 -0
- data/lib/karafka/pro/processing/strategies/mom/ftr.rb +53 -0
- data/lib/karafka/pro/processing/strategies/mom/ftr_vp.rb +37 -0
- data/lib/karafka/pro/processing/strategies/mom/vp.rb +35 -0
- data/lib/karafka/pro/processing/strategies/vp/default.rb +104 -0
- data/lib/karafka/pro/{contracts/base.rb → processing/strategies.rb} +6 -5
- data/lib/karafka/pro/processing/strategy_selector.rb +84 -0
- data/lib/karafka/pro/processing/virtual_offset_manager.rb +147 -0
- data/lib/karafka/pro/routing/features/base.rb +24 -0
- data/lib/karafka/pro/routing/features/dead_letter_queue/contract.rb +50 -0
- data/lib/karafka/pro/routing/features/dead_letter_queue.rb +27 -0
- data/lib/karafka/pro/routing/{builder_extensions.rb → features/delaying/config.rb} +8 -11
- data/lib/karafka/pro/routing/features/delaying/contract.rb +38 -0
- data/lib/karafka/pro/routing/features/delaying/topic.rb +59 -0
- data/lib/karafka/pro/routing/features/delaying.rb +29 -0
- data/lib/karafka/pro/routing/features/expiring/config.rb +27 -0
- data/lib/karafka/pro/routing/features/expiring/contract.rb +38 -0
- data/lib/karafka/pro/routing/features/expiring/topic.rb +59 -0
- data/lib/karafka/pro/routing/features/expiring.rb +27 -0
- data/lib/karafka/pro/routing/features/filtering/config.rb +40 -0
- data/lib/karafka/pro/routing/features/filtering/contract.rb +41 -0
- data/lib/karafka/pro/routing/features/filtering/topic.rb +51 -0
- data/lib/karafka/pro/routing/features/filtering.rb +27 -0
- data/lib/karafka/pro/routing/features/long_running_job/config.rb +28 -0
- data/lib/karafka/pro/routing/features/long_running_job/contract.rb +37 -0
- data/lib/karafka/pro/routing/features/long_running_job/topic.rb +42 -0
- data/lib/karafka/pro/routing/features/long_running_job.rb +28 -0
- data/lib/karafka/pro/routing/features/throttling/config.rb +32 -0
- data/lib/karafka/pro/routing/features/throttling/contract.rb +41 -0
- data/lib/karafka/pro/routing/features/throttling/topic.rb +69 -0
- data/lib/karafka/pro/routing/features/throttling.rb +30 -0
- data/lib/karafka/pro/routing/features/virtual_partitions/config.rb +30 -0
- data/lib/karafka/pro/routing/features/virtual_partitions/contract.rb +52 -0
- data/lib/karafka/pro/routing/features/virtual_partitions/topic.rb +56 -0
- data/lib/karafka/pro/routing/features/virtual_partitions.rb +27 -0
- data/lib/karafka/process.rb +23 -8
- data/lib/karafka/processing/coordinator.rb +102 -23
- data/lib/karafka/processing/coordinators_buffer.rb +5 -1
- data/lib/karafka/processing/executor.rb +51 -21
- data/lib/karafka/processing/executors_buffer.rb +10 -26
- data/lib/karafka/processing/jobs/base.rb +6 -2
- data/lib/karafka/processing/jobs/consume.rb +8 -5
- data/lib/karafka/processing/jobs/idle.rb +24 -0
- data/lib/karafka/processing/jobs_builder.rb +2 -3
- data/lib/karafka/processing/jobs_queue.rb +14 -8
- data/lib/karafka/processing/partitioner.rb +3 -1
- data/lib/karafka/processing/result.rb +14 -1
- data/lib/karafka/processing/strategies/aj_dlq_mom.rb +44 -0
- data/lib/karafka/processing/strategies/aj_mom.rb +21 -0
- data/lib/karafka/processing/strategies/base.rb +52 -0
- data/lib/karafka/processing/strategies/default.rb +142 -0
- data/lib/karafka/processing/strategies/dlq.rb +88 -0
- data/lib/karafka/processing/strategies/dlq_mom.rb +49 -0
- data/lib/karafka/processing/strategies/mom.rb +29 -0
- data/lib/karafka/processing/strategy_selector.rb +47 -0
- data/lib/karafka/processing/worker.rb +8 -3
- data/lib/karafka/railtie.rb +24 -12
- data/lib/karafka/routing/activity_manager.rb +84 -0
- data/lib/karafka/routing/builder.rb +20 -0
- data/lib/karafka/routing/consumer_group.rb +38 -4
- data/lib/karafka/routing/consumer_mapper.rb +0 -10
- data/lib/karafka/routing/features/active_job/builder.rb +33 -0
- data/lib/karafka/routing/features/active_job/config.rb +15 -0
- data/lib/karafka/routing/features/active_job/contract.rb +41 -0
- data/lib/karafka/routing/features/active_job/topic.rb +33 -0
- data/lib/karafka/routing/features/active_job.rb +13 -0
- data/lib/karafka/routing/features/base/expander.rb +53 -0
- data/lib/karafka/routing/features/base.rb +34 -0
- data/lib/karafka/routing/features/dead_letter_queue/config.rb +19 -0
- data/lib/karafka/routing/features/dead_letter_queue/contract.rb +42 -0
- data/lib/karafka/routing/features/dead_letter_queue/topic.rb +41 -0
- data/lib/karafka/routing/features/dead_letter_queue.rb +16 -0
- data/lib/karafka/routing/features/declaratives/config.rb +18 -0
- data/lib/karafka/routing/features/declaratives/contract.rb +30 -0
- data/lib/karafka/routing/features/declaratives/topic.rb +44 -0
- data/lib/karafka/routing/features/declaratives.rb +14 -0
- data/lib/karafka/routing/features/manual_offset_management/config.rb +15 -0
- data/lib/karafka/routing/features/manual_offset_management/contract.rb +24 -0
- data/lib/karafka/routing/features/manual_offset_management/topic.rb +35 -0
- data/lib/karafka/routing/features/manual_offset_management.rb +18 -0
- data/lib/karafka/routing/proxy.rb +18 -20
- data/lib/karafka/routing/router.rb +27 -2
- data/lib/karafka/routing/subscription_group.rb +43 -5
- data/lib/karafka/routing/subscription_groups_builder.rb +6 -1
- data/lib/karafka/routing/topic.rb +17 -5
- data/lib/karafka/routing/topics.rb +8 -0
- data/lib/karafka/runner.rb +1 -0
- data/lib/karafka/serialization/json/deserializer.rb +1 -0
- data/lib/karafka/server.rb +58 -28
- data/lib/karafka/setup/attributes_map.rb +344 -0
- data/lib/karafka/setup/config.rb +48 -19
- data/lib/karafka/status.rb +35 -7
- data/lib/karafka/templates/karafka.rb.erb +14 -6
- data/lib/karafka/time_trackers/base.rb +1 -6
- data/lib/karafka/time_trackers/pause.rb +49 -19
- data/lib/karafka/time_trackers/poll.rb +7 -3
- data/lib/karafka/version.rb +1 -1
- data/lib/karafka.rb +56 -3
- data/renovate.json +6 -0
- data.tar.gz.sig +0 -0
- metadata +189 -61
- metadata.gz.sig +0 -0
- data/bin/wait_for_kafka +0 -20
- data/certs/mensfeld.pem +0 -25
- data/lib/karafka/active_job/routing/extensions.rb +0 -31
- data/lib/karafka/contracts/consumer_group_topic.rb +0 -42
- data/lib/karafka/instrumentation.rb +0 -21
- data/lib/karafka/pro/base_consumer.rb +0 -82
- data/lib/karafka/pro/contracts/consumer_group.rb +0 -34
- data/lib/karafka/pro/contracts/consumer_group_topic.rb +0 -33
- data/lib/karafka/pro/routing/topic_extensions.rb +0 -38
|
@@ -14,17 +14,25 @@ module Karafka
|
|
|
14
14
|
# @note Consumer name may change in case we regenerate it
|
|
15
15
|
attr_reader :name
|
|
16
16
|
|
|
17
|
+
# @return [String] id of the client
|
|
18
|
+
attr_reader :id
|
|
19
|
+
|
|
17
20
|
# How many times should we retry polling in case of a failure
|
|
18
|
-
MAX_POLL_RETRIES =
|
|
21
|
+
MAX_POLL_RETRIES = 20
|
|
22
|
+
|
|
23
|
+
# We want to make sure we never close several clients in the same moment to prevent
|
|
24
|
+
# potential race conditions and other issues
|
|
25
|
+
SHUTDOWN_MUTEX = Mutex.new
|
|
19
26
|
|
|
20
|
-
private_constant :MAX_POLL_RETRIES
|
|
27
|
+
private_constant :MAX_POLL_RETRIES, :SHUTDOWN_MUTEX
|
|
21
28
|
|
|
22
29
|
# Creates a new consumer instance.
|
|
23
30
|
#
|
|
24
31
|
# @param subscription_group [Karafka::Routing::SubscriptionGroup] subscription group
|
|
25
32
|
# with all the configuration details needed for us to create a client
|
|
26
|
-
# @return [Karafka::Connection::
|
|
33
|
+
# @return [Karafka::Connection::Client]
|
|
27
34
|
def initialize(subscription_group)
|
|
35
|
+
@id = SecureRandom.hex(6)
|
|
28
36
|
# Name is set when we build consumer
|
|
29
37
|
@name = ''
|
|
30
38
|
@mutex = Mutex.new
|
|
@@ -153,7 +161,7 @@ module Karafka
|
|
|
153
161
|
|
|
154
162
|
pause_msg = Messages::Seek.new(topic, partition, offset)
|
|
155
163
|
|
|
156
|
-
internal_commit_offsets(async:
|
|
164
|
+
internal_commit_offsets(async: true)
|
|
157
165
|
|
|
158
166
|
# Here we do not use our cached tpls because we should not try to pause something we do
|
|
159
167
|
# not own anymore.
|
|
@@ -161,6 +169,15 @@ module Karafka
|
|
|
161
169
|
|
|
162
170
|
return unless tpl
|
|
163
171
|
|
|
172
|
+
Karafka.monitor.instrument(
|
|
173
|
+
'client.pause',
|
|
174
|
+
caller: self,
|
|
175
|
+
subscription_group: @subscription_group,
|
|
176
|
+
topic: topic,
|
|
177
|
+
partition: partition,
|
|
178
|
+
offset: offset
|
|
179
|
+
)
|
|
180
|
+
|
|
164
181
|
@paused_tpls[topic][partition] = tpl
|
|
165
182
|
|
|
166
183
|
@kafka.pause(tpl)
|
|
@@ -179,19 +196,26 @@ module Karafka
|
|
|
179
196
|
|
|
180
197
|
return if @closed
|
|
181
198
|
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
# We can skip performance penalty since resuming should not happen too often
|
|
185
|
-
internal_commit_offsets(async: false)
|
|
199
|
+
# We now commit offsets on rebalances, thus we can do it async just to make sure
|
|
200
|
+
internal_commit_offsets(async: true)
|
|
186
201
|
|
|
187
202
|
# If we were not able, let's try to reuse the one we have (if we have)
|
|
188
203
|
tpl = topic_partition_list(topic, partition) || @paused_tpls[topic][partition]
|
|
189
204
|
|
|
190
205
|
return unless tpl
|
|
206
|
+
|
|
191
207
|
# If we did not have it, it means we never paused this partition, thus no resume should
|
|
192
208
|
# happen in the first place
|
|
193
209
|
return unless @paused_tpls[topic].delete(partition)
|
|
194
210
|
|
|
211
|
+
Karafka.monitor.instrument(
|
|
212
|
+
'client.resume',
|
|
213
|
+
caller: self,
|
|
214
|
+
subscription_group: @subscription_group,
|
|
215
|
+
topic: topic,
|
|
216
|
+
partition: partition
|
|
217
|
+
)
|
|
218
|
+
|
|
195
219
|
@kafka.resume(tpl)
|
|
196
220
|
ensure
|
|
197
221
|
@mutex.unlock
|
|
@@ -238,6 +262,17 @@ module Karafka
|
|
|
238
262
|
end
|
|
239
263
|
end
|
|
240
264
|
|
|
265
|
+
# Runs a single poll ignoring all the potential errors
|
|
266
|
+
# This is used as a keep-alive in the shutdown stage and any errors that happen here are
|
|
267
|
+
# irrelevant from the shutdown process perspective
|
|
268
|
+
#
|
|
269
|
+
# This is used only to trigger rebalance callbacks
|
|
270
|
+
def ping
|
|
271
|
+
poll(100)
|
|
272
|
+
rescue Rdkafka::RdkafkaError
|
|
273
|
+
nil
|
|
274
|
+
end
|
|
275
|
+
|
|
241
276
|
private
|
|
242
277
|
|
|
243
278
|
# When we cannot store an offset, it means we no longer own the partition
|
|
@@ -267,32 +302,41 @@ module Karafka
|
|
|
267
302
|
|
|
268
303
|
true
|
|
269
304
|
rescue Rdkafka::RdkafkaError => e
|
|
270
|
-
|
|
271
|
-
|
|
305
|
+
case e.code
|
|
306
|
+
when :assignment_lost
|
|
307
|
+
return false
|
|
308
|
+
when :no_offset
|
|
309
|
+
return true
|
|
310
|
+
when :coordinator_load_in_progress
|
|
311
|
+
sleep(1)
|
|
312
|
+
retry
|
|
313
|
+
end
|
|
272
314
|
|
|
273
315
|
raise e
|
|
274
316
|
end
|
|
275
317
|
|
|
276
318
|
# Commits the stored offsets in a sync way and closes the consumer.
|
|
277
319
|
def close
|
|
278
|
-
#
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
320
|
+
# Allow only one client to be closed at the same time
|
|
321
|
+
SHUTDOWN_MUTEX.synchronize do
|
|
322
|
+
# Make sure that no other operations are happening on this client when we close it
|
|
323
|
+
@mutex.synchronize do
|
|
324
|
+
# Once client is closed, we should not close it again
|
|
325
|
+
# This could only happen in case of a race-condition when forceful shutdown happens
|
|
326
|
+
# and triggers this from a different thread
|
|
327
|
+
return if @closed
|
|
328
|
+
|
|
329
|
+
@closed = true
|
|
330
|
+
|
|
331
|
+
# Remove callbacks runners that were registered
|
|
332
|
+
::Karafka::Core::Instrumentation.statistics_callbacks.delete(@subscription_group.id)
|
|
333
|
+
::Karafka::Core::Instrumentation.error_callbacks.delete(@subscription_group.id)
|
|
334
|
+
|
|
335
|
+
@kafka.close
|
|
336
|
+
@buffer.clear
|
|
337
|
+
# @note We do not clear rebalance manager here as we may still have revocation info
|
|
338
|
+
# here that we want to consider valid prior to running another reconnection
|
|
339
|
+
end
|
|
296
340
|
end
|
|
297
341
|
end
|
|
298
342
|
|
|
@@ -310,7 +354,7 @@ module Karafka
|
|
|
310
354
|
Rdkafka::Consumer::TopicPartitionList.new({ topic => [rdkafka_partition] })
|
|
311
355
|
end
|
|
312
356
|
|
|
313
|
-
# Performs a single poll operation
|
|
357
|
+
# Performs a single poll operation and handles retries and error
|
|
314
358
|
#
|
|
315
359
|
# @param timeout [Integer] timeout for a single poll
|
|
316
360
|
# @return [Rdkafka::Consumer::Message, nil] fetched message or nil if nothing polled
|
|
@@ -323,41 +367,56 @@ module Karafka
|
|
|
323
367
|
|
|
324
368
|
@kafka.poll(timeout)
|
|
325
369
|
rescue ::Rdkafka::RdkafkaError => e
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
#
|
|
331
|
-
#
|
|
370
|
+
early_report = false
|
|
371
|
+
|
|
372
|
+
retryable = time_poll.attempts <= MAX_POLL_RETRIES && time_poll.retryable?
|
|
373
|
+
|
|
374
|
+
# There are retryable issues on which we want to report fast as they are source of
|
|
375
|
+
# problems and can mean some bigger system instabilities
|
|
376
|
+
# Those are mainly network issues and exceeding the max poll interval
|
|
377
|
+
# We want to report early on max poll interval exceeding because it may mean that the
|
|
378
|
+
# underlying processing is taking too much time and it is not LRJ
|
|
332
379
|
case e.code
|
|
333
380
|
when :max_poll_exceeded # -147
|
|
334
|
-
|
|
335
|
-
return nil
|
|
336
|
-
when :transport # -195
|
|
337
|
-
reset
|
|
338
|
-
return nil
|
|
339
|
-
when :rebalance_in_progress # -27
|
|
340
|
-
reset
|
|
341
|
-
return nil
|
|
342
|
-
when :not_coordinator # 16
|
|
343
|
-
reset
|
|
344
|
-
return nil
|
|
381
|
+
early_report = true
|
|
345
382
|
when :network_exception # 13
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
383
|
+
early_report = true
|
|
384
|
+
when :transport # -195
|
|
385
|
+
early_report = true
|
|
386
|
+
# @see
|
|
387
|
+
# https://github.com/confluentinc/confluent-kafka-dotnet/issues/1366#issuecomment-821842990
|
|
388
|
+
# This will be raised each time poll detects a non-existing topic. When auto creation is
|
|
389
|
+
# on, we can safely ignore it
|
|
390
|
+
when :unknown_topic_or_part # 3
|
|
391
|
+
return nil if @subscription_group.kafka[:'allow.auto.create.topics']
|
|
392
|
+
|
|
393
|
+
early_report = true
|
|
394
|
+
|
|
395
|
+
# No sense in retrying when no topic/partition and we're no longer running
|
|
396
|
+
retryable = false unless Karafka::App.running?
|
|
351
397
|
end
|
|
352
398
|
|
|
353
|
-
|
|
354
|
-
|
|
399
|
+
if early_report || !retryable
|
|
400
|
+
Karafka.monitor.instrument(
|
|
401
|
+
'error.occurred',
|
|
402
|
+
caller: self,
|
|
403
|
+
error: e,
|
|
404
|
+
type: 'connection.client.poll.error'
|
|
405
|
+
)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
raise unless retryable
|
|
409
|
+
|
|
410
|
+
# Most of the errors can be safely ignored as librdkafka will recover from them
|
|
411
|
+
# @see https://github.com/edenhill/librdkafka/issues/1987#issuecomment-422008750
|
|
412
|
+
# @see https://github.com/edenhill/librdkafka/wiki/Error-handling
|
|
355
413
|
|
|
356
414
|
time_poll.checkpoint
|
|
357
415
|
time_poll.backoff
|
|
358
416
|
|
|
359
|
-
#
|
|
360
|
-
|
|
417
|
+
# poll may not only return message but also can run callbacks and if they changed,
|
|
418
|
+
# despite the errors we need to delegate to the other app parts
|
|
419
|
+
@rebalance_manager.changed? ? nil : retry
|
|
361
420
|
end
|
|
362
421
|
|
|
363
422
|
# Builds a new rdkafka consumer instance based on the subscription group configuration
|
|
@@ -370,24 +429,22 @@ module Karafka
|
|
|
370
429
|
@name = consumer.name
|
|
371
430
|
|
|
372
431
|
# Register statistics runner for this particular type of callbacks
|
|
373
|
-
::Karafka::Instrumentation.statistics_callbacks.add(
|
|
432
|
+
::Karafka::Core::Instrumentation.statistics_callbacks.add(
|
|
374
433
|
@subscription_group.id,
|
|
375
434
|
Instrumentation::Callbacks::Statistics.new(
|
|
376
435
|
@subscription_group.id,
|
|
377
436
|
@subscription_group.consumer_group_id,
|
|
378
|
-
@name
|
|
379
|
-
::Karafka::App.config.monitor
|
|
437
|
+
@name
|
|
380
438
|
)
|
|
381
439
|
)
|
|
382
440
|
|
|
383
441
|
# Register error tracking callback
|
|
384
|
-
::Karafka::Instrumentation.error_callbacks.add(
|
|
442
|
+
::Karafka::Core::Instrumentation.error_callbacks.add(
|
|
385
443
|
@subscription_group.id,
|
|
386
444
|
Instrumentation::Callbacks::Error.new(
|
|
387
445
|
@subscription_group.id,
|
|
388
446
|
@subscription_group.consumer_group_id,
|
|
389
|
-
@name
|
|
390
|
-
::Karafka::App.config.monitor
|
|
447
|
+
@name
|
|
391
448
|
)
|
|
392
449
|
)
|
|
393
450
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Connection
|
|
5
|
+
# This object represents a collective status of execution of group of listeners running inside
|
|
6
|
+
# of one consumer group but in separate subscription groups.
|
|
7
|
+
#
|
|
8
|
+
# There are cases when we do not want to close a given client when others from the same
|
|
9
|
+
# consumer group are running because it can cause instabilities due to early shutdown of some
|
|
10
|
+
# of the clients out of same consumer group.
|
|
11
|
+
#
|
|
12
|
+
# We also want to make sure, we close one consumer at a time while others can continue polling.
|
|
13
|
+
#
|
|
14
|
+
# This prevents a scenario, where a rebalance is not acknowledged and we loose assignment
|
|
15
|
+
# without having a chance to commit changes.
|
|
16
|
+
class ConsumerGroupCoordinator
|
|
17
|
+
# @param group_size [Integer] number of separate subscription groups in a consumer group
|
|
18
|
+
def initialize(group_size)
|
|
19
|
+
@shutdown_lock = Mutex.new
|
|
20
|
+
@group_size = group_size
|
|
21
|
+
@finished = Set.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [Boolean] true if all the subscription groups from a given consumer group are
|
|
25
|
+
# finished
|
|
26
|
+
def finished?
|
|
27
|
+
@finished.size == @group_size
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @return [Boolean] can we start shutdown on a given listener
|
|
31
|
+
# @note If true, will also obtain a lock so no-one else will be closing the same time we do
|
|
32
|
+
def shutdown?
|
|
33
|
+
finished? && @shutdown_lock.try_lock
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Unlocks the shutdown lock
|
|
37
|
+
def unlock
|
|
38
|
+
@shutdown_lock.unlock if @shutdown_lock.owned?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Marks given listener as finished
|
|
42
|
+
# @param listener_id [String]
|
|
43
|
+
def finish_work(listener_id)
|
|
44
|
+
@finished << listener_id
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -14,16 +14,18 @@ module Karafka
|
|
|
14
14
|
# @return [String] id of this listener
|
|
15
15
|
attr_reader :id
|
|
16
16
|
|
|
17
|
+
# @param consumer_group_coordinator [Karafka::Connection::ConsumerGroupCoordinator]
|
|
17
18
|
# @param subscription_group [Karafka::Routing::SubscriptionGroup]
|
|
18
19
|
# @param jobs_queue [Karafka::Processing::JobsQueue] queue where we should push work
|
|
19
20
|
# @return [Karafka::Connection::Listener] listener instance
|
|
20
|
-
def initialize(subscription_group, jobs_queue)
|
|
21
|
+
def initialize(consumer_group_coordinator, subscription_group, jobs_queue)
|
|
21
22
|
proc_config = ::Karafka::App.config.internal.processing
|
|
22
23
|
|
|
23
|
-
@id = SecureRandom.
|
|
24
|
+
@id = SecureRandom.hex(6)
|
|
25
|
+
@consumer_group_coordinator = consumer_group_coordinator
|
|
24
26
|
@subscription_group = subscription_group
|
|
25
27
|
@jobs_queue = jobs_queue
|
|
26
|
-
@coordinators = Processing::CoordinatorsBuffer.new
|
|
28
|
+
@coordinators = Processing::CoordinatorsBuffer.new(subscription_group.topics)
|
|
27
29
|
@client = Client.new(@subscription_group)
|
|
28
30
|
@executors = Processing::ExecutorsBuffer.new(@client, subscription_group)
|
|
29
31
|
@jobs_builder = proc_config.jobs_builder
|
|
@@ -46,8 +48,8 @@ module Karafka
|
|
|
46
48
|
Karafka.monitor.instrument(
|
|
47
49
|
'connection.listener.before_fetch_loop',
|
|
48
50
|
caller: self,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
client: @client,
|
|
52
|
+
subscription_group: @subscription_group
|
|
51
53
|
)
|
|
52
54
|
|
|
53
55
|
fetch_loop
|
|
@@ -67,7 +69,6 @@ module Karafka
|
|
|
67
69
|
@stopped = true
|
|
68
70
|
@executors.clear
|
|
69
71
|
@coordinators.reset
|
|
70
|
-
@client.commit_offsets!
|
|
71
72
|
@client.stop
|
|
72
73
|
end
|
|
73
74
|
end
|
|
@@ -83,11 +84,13 @@ module Karafka
|
|
|
83
84
|
# Kafka connections / Internet connection issues / Etc. Business logic problems should not
|
|
84
85
|
# propagate this far.
|
|
85
86
|
def fetch_loop
|
|
86
|
-
|
|
87
|
+
# Run the main loop as long as we are not stopping or moving into quiet mode
|
|
88
|
+
until Karafka::App.stopping? || Karafka::App.quieting? || Karafka::App.quiet?
|
|
87
89
|
Karafka.monitor.instrument(
|
|
88
90
|
'connection.listener.fetch_loop',
|
|
89
91
|
caller: self,
|
|
90
|
-
client: @client
|
|
92
|
+
client: @client,
|
|
93
|
+
subscription_group: @subscription_group
|
|
91
94
|
)
|
|
92
95
|
|
|
93
96
|
resume_paused_partitions
|
|
@@ -95,6 +98,8 @@ module Karafka
|
|
|
95
98
|
Karafka.monitor.instrument(
|
|
96
99
|
'connection.listener.fetch_loop.received',
|
|
97
100
|
caller: self,
|
|
101
|
+
client: @client,
|
|
102
|
+
subscription_group: @subscription_group,
|
|
98
103
|
messages_buffer: @messages_buffer
|
|
99
104
|
) do
|
|
100
105
|
# We need to fetch data before we revoke lost partitions details as during the polling
|
|
@@ -118,13 +123,9 @@ module Karafka
|
|
|
118
123
|
build_and_schedule_consumption_jobs
|
|
119
124
|
|
|
120
125
|
wait
|
|
121
|
-
|
|
122
|
-
# We don't use the `#commit_offsets!` here for performance reasons. This can be achieved
|
|
123
|
-
# if needed by using manual offset management.
|
|
124
|
-
@client.commit_offsets
|
|
125
126
|
end
|
|
126
127
|
|
|
127
|
-
# If we are stopping we will no longer schedule any jobs despite polling.
|
|
128
|
+
# If we are stopping we will no longer schedule any regular jobs despite polling.
|
|
128
129
|
# We need to keep polling not to exceed the `max.poll.interval` for long-running
|
|
129
130
|
# non-blocking jobs and we need to allow them to finish. We however do not want to
|
|
130
131
|
# enqueue any new jobs. It's worth keeping in mind that it is the end user responsibility
|
|
@@ -133,7 +134,14 @@ module Karafka
|
|
|
133
134
|
#
|
|
134
135
|
# We do not care about resuming any partitions or lost jobs as we do not plan to do
|
|
135
136
|
# anything with them as we're in the shutdown phase.
|
|
136
|
-
|
|
137
|
+
#
|
|
138
|
+
# What we do care however is the ability to still run revocation jobs in case anything
|
|
139
|
+
# would change in the cluster. We still want to notify the long-running jobs about changes
|
|
140
|
+
# that occurred in the cluster.
|
|
141
|
+
wait_pinging(
|
|
142
|
+
wait_until: -> { @jobs_queue.empty?(@subscription_group.id) },
|
|
143
|
+
after_ping: -> { build_and_schedule_revoke_lost_partitions_jobs }
|
|
144
|
+
)
|
|
137
145
|
|
|
138
146
|
# We do not want to schedule the shutdown jobs prior to finishing all the jobs
|
|
139
147
|
# (including non-blocking) as there might be a long-running job with a shutdown and then
|
|
@@ -141,7 +149,25 @@ module Karafka
|
|
|
141
149
|
# as it could create a race-condition.
|
|
142
150
|
build_and_schedule_shutdown_jobs
|
|
143
151
|
|
|
144
|
-
|
|
152
|
+
# Wait until all the shutdown jobs are done
|
|
153
|
+
wait_pinging(wait_until: -> { @jobs_queue.empty?(@subscription_group.id) })
|
|
154
|
+
|
|
155
|
+
# Once all the work is done, we need to decrement counter of active subscription groups
|
|
156
|
+
# within this consumer group
|
|
157
|
+
@consumer_group_coordinator.finish_work(id)
|
|
158
|
+
|
|
159
|
+
# Wait if we're in the process of finishing started work or finished all the work and
|
|
160
|
+
# just sitting and being quiet
|
|
161
|
+
wait_pinging(wait_until: -> { !(Karafka::App.quieting? || Karafka::App.quiet?) })
|
|
162
|
+
|
|
163
|
+
# We need to wait until all the work in the whole consumer group (local to the process)
|
|
164
|
+
# is done. Otherwise we may end up with locks and `Timed out LeaveGroupRequest in flight`
|
|
165
|
+
# warning notifications.
|
|
166
|
+
wait_pinging(wait_until: -> { @consumer_group_coordinator.shutdown? })
|
|
167
|
+
|
|
168
|
+
# This extra ping will make sure we've refreshed the rebalance state after other instances
|
|
169
|
+
# potentially shutdown. This will prevent us from closing with a dangling callback
|
|
170
|
+
@client.ping
|
|
145
171
|
|
|
146
172
|
shutdown
|
|
147
173
|
|
|
@@ -159,6 +185,8 @@ module Karafka
|
|
|
159
185
|
restart
|
|
160
186
|
|
|
161
187
|
sleep(1) && retry
|
|
188
|
+
ensure
|
|
189
|
+
@consumer_group_coordinator.unlock
|
|
162
190
|
end
|
|
163
191
|
|
|
164
192
|
# Resumes processing of partitions that were paused due to an error.
|
|
@@ -185,7 +213,9 @@ module Karafka
|
|
|
185
213
|
# processed (if it was assigned and revoked really fast), thus we may not have it
|
|
186
214
|
# here. In cases like this, we do not run a revocation job
|
|
187
215
|
@executors.find_all(topic, partition).each do |executor|
|
|
188
|
-
|
|
216
|
+
job = @jobs_builder.revoked(executor)
|
|
217
|
+
job.before_enqueue
|
|
218
|
+
jobs << job
|
|
189
219
|
end
|
|
190
220
|
|
|
191
221
|
# We need to remove all the executors of a given topic partition that we have lost, so
|
|
@@ -204,8 +234,10 @@ module Karafka
|
|
|
204
234
|
def build_and_schedule_shutdown_jobs
|
|
205
235
|
jobs = []
|
|
206
236
|
|
|
207
|
-
@executors.each do |
|
|
208
|
-
|
|
237
|
+
@executors.each do |executor|
|
|
238
|
+
job = @jobs_builder.shutdown(executor)
|
|
239
|
+
job.before_enqueue
|
|
240
|
+
jobs << job
|
|
209
241
|
end
|
|
210
242
|
|
|
211
243
|
@scheduler.schedule_shutdown(@jobs_queue, jobs)
|
|
@@ -231,20 +263,25 @@ module Karafka
|
|
|
231
263
|
|
|
232
264
|
@messages_buffer.each do |topic, partition, messages|
|
|
233
265
|
coordinator = @coordinators.find_or_create(topic, partition)
|
|
234
|
-
|
|
235
266
|
# Start work coordination for this topic partition
|
|
236
267
|
coordinator.start(messages)
|
|
237
268
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
269
|
+
# We do not increment coordinator for idle job because it's not a user related one
|
|
270
|
+
# and it will not go through a standard lifecycle. Same applies to revoked and shutdown
|
|
271
|
+
if messages.empty?
|
|
272
|
+
executor = @executors.find_or_create(topic, partition, 0, coordinator)
|
|
273
|
+
jobs << @jobs_builder.idle(executor)
|
|
274
|
+
else
|
|
275
|
+
@partitioner.call(topic, messages, coordinator) do |group_id, partition_messages|
|
|
276
|
+
executor = @executors.find_or_create(topic, partition, group_id, coordinator)
|
|
277
|
+
coordinator.increment
|
|
278
|
+
jobs << @jobs_builder.consume(executor, partition_messages)
|
|
279
|
+
end
|
|
245
280
|
end
|
|
246
281
|
end
|
|
247
282
|
|
|
283
|
+
jobs.each(&:before_enqueue)
|
|
284
|
+
|
|
248
285
|
@scheduler.schedule_consumption(@jobs_queue, jobs)
|
|
249
286
|
end
|
|
250
287
|
|
|
@@ -254,10 +291,21 @@ module Karafka
|
|
|
254
291
|
end
|
|
255
292
|
|
|
256
293
|
# Waits without blocking the polling
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
|
|
260
|
-
|
|
294
|
+
#
|
|
295
|
+
# This should be used only when we no longer plan to use any incoming messages data and we
|
|
296
|
+
# can safely discard it. We can however use the rebalance information if needed.
|
|
297
|
+
#
|
|
298
|
+
# @param wait_until [Proc] until this evaluates to true, we will poll data
|
|
299
|
+
# @param after_ping [Proc] code that we want to run after each ping (if any)
|
|
300
|
+
#
|
|
301
|
+
# @note Performance of this is not relevant (in regards to blocks) because it is used only
|
|
302
|
+
# on shutdown and quiet, hence not in the running mode
|
|
303
|
+
def wait_pinging(wait_until:, after_ping: -> {})
|
|
304
|
+
until wait_until.call
|
|
305
|
+
@client.ping
|
|
306
|
+
after_ping.call
|
|
307
|
+
sleep(0.2)
|
|
308
|
+
end
|
|
261
309
|
end
|
|
262
310
|
|
|
263
311
|
# We can stop client without a problem, as it will reinitialize itself when running the
|
|
@@ -6,11 +6,27 @@ module Karafka
|
|
|
6
6
|
class ListenersBatch
|
|
7
7
|
include Enumerable
|
|
8
8
|
|
|
9
|
+
attr_reader :coordinators
|
|
10
|
+
|
|
9
11
|
# @param jobs_queue [JobsQueue]
|
|
10
12
|
# @return [ListenersBatch]
|
|
11
13
|
def initialize(jobs_queue)
|
|
12
|
-
@
|
|
13
|
-
|
|
14
|
+
@coordinators = []
|
|
15
|
+
|
|
16
|
+
@batch = App.subscription_groups.flat_map do |_consumer_group, subscription_groups|
|
|
17
|
+
consumer_group_coordinator = Connection::ConsumerGroupCoordinator.new(
|
|
18
|
+
subscription_groups.size
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
@coordinators << consumer_group_coordinator
|
|
22
|
+
|
|
23
|
+
subscription_groups.map do |subscription_group|
|
|
24
|
+
Connection::Listener.new(
|
|
25
|
+
consumer_group_coordinator,
|
|
26
|
+
subscription_group,
|
|
27
|
+
jobs_queue
|
|
28
|
+
)
|
|
29
|
+
end
|
|
14
30
|
end
|
|
15
31
|
end
|
|
16
32
|
|
|
@@ -14,6 +14,9 @@ module Karafka
|
|
|
14
14
|
# that are lost, are those that got revoked but did not get re-assigned back. We do not
|
|
15
15
|
# expose this concept outside and we normalize to have them revoked, as it is irrelevant
|
|
16
16
|
# from the rest of the code perspective as only those that are lost are truly revoked.
|
|
17
|
+
#
|
|
18
|
+
# @note For cooperative-sticky `#assigned_partitions` holds only the recently assigned
|
|
19
|
+
# partitions, not all the partitions that are owned
|
|
17
20
|
class RebalanceManager
|
|
18
21
|
# Empty array for internal usage not to create new objects
|
|
19
22
|
EMPTY_ARRAY = [].freeze
|
|
@@ -12,7 +12,7 @@ module Karafka
|
|
|
12
12
|
configure do |config|
|
|
13
13
|
config.error_messages = YAML.safe_load(
|
|
14
14
|
File.read(
|
|
15
|
-
File.join(Karafka.gem_root, 'config', 'errors.yml')
|
|
15
|
+
File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
|
|
16
16
|
)
|
|
17
17
|
).fetch('en').fetch('validations').fetch('config')
|
|
18
18
|
end
|
|
@@ -7,13 +7,13 @@ module Karafka
|
|
|
7
7
|
configure do |config|
|
|
8
8
|
config.error_messages = YAML.safe_load(
|
|
9
9
|
File.read(
|
|
10
|
-
File.join(Karafka.gem_root, 'config', 'errors.yml')
|
|
10
|
+
File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
|
|
11
11
|
)
|
|
12
12
|
).fetch('en').fetch('validations').fetch('consumer_group')
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
required(:id) { |
|
|
16
|
-
required(:topics) { |
|
|
15
|
+
required(:id) { |val| val.is_a?(String) && Contracts::TOPIC_REGEXP.match?(val) }
|
|
16
|
+
required(:topics) { |val| val.is_a?(Array) && !val.empty? }
|
|
17
17
|
|
|
18
18
|
virtual do |data, errors|
|
|
19
19
|
next unless errors.empty?
|
|
@@ -27,16 +27,29 @@ module Karafka
|
|
|
27
27
|
|
|
28
28
|
virtual do |data, errors|
|
|
29
29
|
next unless errors.empty?
|
|
30
|
+
next unless ::Karafka::App.config.strict_topics_namespacing
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
names = data.fetch(:topics).map { |topic| topic[:name] }
|
|
33
|
+
names_hash = names.each_with_object({}) { |n, h| h[n] = true }
|
|
34
|
+
error_occured = false
|
|
35
|
+
names.each do |n|
|
|
36
|
+
# Skip topic names that are not namespaced
|
|
37
|
+
next unless n.chars.find { |c| ['.', '_'].include?(c) }
|
|
38
|
+
|
|
39
|
+
if n.chars.include?('.')
|
|
40
|
+
# Check underscore styled topic
|
|
41
|
+
underscored_topic = n.tr('.', '_')
|
|
42
|
+
error_occured = names_hash[underscored_topic] ? true : false
|
|
43
|
+
else
|
|
44
|
+
# Check dot styled topic
|
|
45
|
+
dot_topic = n.tr('_', '.')
|
|
46
|
+
error_occured = names_hash[dot_topic] ? true : false
|
|
36
47
|
end
|
|
37
48
|
end
|
|
38
49
|
|
|
39
|
-
|
|
50
|
+
next unless error_occured
|
|
51
|
+
|
|
52
|
+
[[%i[topics], :topics_namespaced_names_not_unique]]
|
|
40
53
|
end
|
|
41
54
|
end
|
|
42
55
|
end
|