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
|
@@ -10,19 +10,28 @@ module Karafka
|
|
|
10
10
|
class ConsumerGroup
|
|
11
11
|
attr_reader :id, :topics, :name
|
|
12
12
|
|
|
13
|
+
# This is a "virtual" attribute that is not building subscription groups.
|
|
14
|
+
# It allows us to store the "current" subscription group defined in the routing
|
|
15
|
+
# This subscription group id is then injected into topics, so we can compute the subscription
|
|
16
|
+
# groups
|
|
17
|
+
attr_accessor :current_subscription_group_id
|
|
18
|
+
|
|
13
19
|
# @param name [String, Symbol] raw name of this consumer group. Raw means, that it does not
|
|
14
20
|
# yet have an application client_id namespace, this will be added here by default.
|
|
15
21
|
# We add it to make a multi-system development easier for people that don't use
|
|
16
22
|
# kafka and don't understand the concept of consumer groups.
|
|
17
23
|
def initialize(name)
|
|
18
|
-
@name = name
|
|
24
|
+
@name = name.to_s
|
|
19
25
|
@id = Karafka::App.config.consumer_mapper.call(name)
|
|
20
26
|
@topics = Topics.new([])
|
|
27
|
+
# Initialize the subscription group so there's always a value for it, since even if not
|
|
28
|
+
# defined directly, a subscription group will be created
|
|
29
|
+
@current_subscription_group_id = SubscriptionGroup.id
|
|
21
30
|
end
|
|
22
31
|
|
|
23
32
|
# @return [Boolean] true if this consumer group should be active in our current process
|
|
24
33
|
def active?
|
|
25
|
-
Karafka::
|
|
34
|
+
Karafka::App.config.internal.routing.activity_manager.active?(:consumer_groups, name)
|
|
26
35
|
end
|
|
27
36
|
|
|
28
37
|
# Builds a topic representation inside of a current consumer group route
|
|
@@ -32,13 +41,38 @@ module Karafka
|
|
|
32
41
|
def topic=(name, &block)
|
|
33
42
|
topic = Topic.new(name, self)
|
|
34
43
|
@topics << Proxy.new(topic, &block).target
|
|
35
|
-
@topics.last
|
|
44
|
+
built_topic = @topics.last
|
|
45
|
+
# We overwrite it conditionally in case it was not set by the user inline in the topic
|
|
46
|
+
# block definition
|
|
47
|
+
built_topic.subscription_group ||= current_subscription_group_id
|
|
48
|
+
built_topic
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Assigns the current subscription group id based on the defined one and allows for further
|
|
52
|
+
# topic definition
|
|
53
|
+
# @param name [String, Symbol] name of the current subscription group
|
|
54
|
+
# @param block [Proc] block that may include topics definitions
|
|
55
|
+
def subscription_group=(name = SubscriptionGroup.id, &block)
|
|
56
|
+
# We cast it here, so the routing supports symbol based but that's anyhow later on
|
|
57
|
+
# validated as a string
|
|
58
|
+
@current_subscription_group_id = name
|
|
59
|
+
|
|
60
|
+
Proxy.new(self, &block)
|
|
61
|
+
|
|
62
|
+
# We need to reset the current subscription group after it is used, so it won't leak
|
|
63
|
+
# outside to other topics that would be defined without a defined subscription group
|
|
64
|
+
@current_subscription_group_id = SubscriptionGroup.id
|
|
36
65
|
end
|
|
37
66
|
|
|
38
67
|
# @return [Array<Routing::SubscriptionGroup>] all the subscription groups build based on
|
|
39
68
|
# the consumer group topics
|
|
40
69
|
def subscription_groups
|
|
41
|
-
App
|
|
70
|
+
@subscription_groups ||= App
|
|
71
|
+
.config
|
|
72
|
+
.internal
|
|
73
|
+
.routing
|
|
74
|
+
.subscription_groups_builder
|
|
75
|
+
.call(topics)
|
|
42
76
|
end
|
|
43
77
|
|
|
44
78
|
# Hashed version of consumer group that can be used for validation purposes
|
|
@@ -12,16 +12,6 @@ module Karafka
|
|
|
12
12
|
# raw_consumer_group_name
|
|
13
13
|
# end
|
|
14
14
|
# end
|
|
15
|
-
#
|
|
16
|
-
# @example Mapper for replacing "_" with "." in topic names
|
|
17
|
-
# class MyMapper
|
|
18
|
-
# def call(raw_consumer_group_name)
|
|
19
|
-
# [
|
|
20
|
-
# Karafka::Helpers::Inflector.map(Karafka::App.config.client_id.to_s),
|
|
21
|
-
# raw_consumer_group_name
|
|
22
|
-
# ].join('_').gsub('_', '.')
|
|
23
|
-
# end
|
|
24
|
-
# end
|
|
25
15
|
class ConsumerMapper
|
|
26
16
|
# @param raw_consumer_group_name [String, Symbol] string or symbolized consumer group name
|
|
27
17
|
# @return [String] remapped final consumer group name
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
class ActiveJob < Base
|
|
7
|
+
# Routing extensions for ActiveJob
|
|
8
|
+
module Builder
|
|
9
|
+
# This method simplifies routes definition for ActiveJob topics / queues by
|
|
10
|
+
# auto-injecting the consumer class
|
|
11
|
+
#
|
|
12
|
+
# @param name [String, Symbol] name of the topic where ActiveJobs jobs should go
|
|
13
|
+
# @param block [Proc] block that we can use for some extra configuration
|
|
14
|
+
def active_job_topic(name, &block)
|
|
15
|
+
topic(name) do
|
|
16
|
+
consumer App.config.internal.active_job.consumer_class
|
|
17
|
+
active_job true
|
|
18
|
+
|
|
19
|
+
# This is handled by our custom ActiveJob consumer
|
|
20
|
+
# Without this, default behaviour would cause messages to skip upon shutdown as the
|
|
21
|
+
# offset would be committed for the last message
|
|
22
|
+
manual_offset_management true
|
|
23
|
+
|
|
24
|
+
next unless block
|
|
25
|
+
|
|
26
|
+
instance_eval(&block)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
class ActiveJob < Base
|
|
7
|
+
# Config for ActiveJob usage
|
|
8
|
+
Config = Struct.new(
|
|
9
|
+
:active,
|
|
10
|
+
keyword_init: true
|
|
11
|
+
) { alias_method :active?, :active }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
class ActiveJob < Base
|
|
7
|
+
# Rules around using ActiveJob routing - basically you need to have ActiveJob available
|
|
8
|
+
# in order to be able to use active job routing
|
|
9
|
+
class Contract < Contracts::Base
|
|
10
|
+
configure do |config|
|
|
11
|
+
config.error_messages = YAML.safe_load(
|
|
12
|
+
File.read(
|
|
13
|
+
File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
|
|
14
|
+
)
|
|
15
|
+
).fetch('en').fetch('validations').fetch('topic')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
virtual do |data, errors|
|
|
19
|
+
next unless errors.empty?
|
|
20
|
+
next unless data[:active_job][:active]
|
|
21
|
+
# One should not define active job jobs without ActiveJob being available for usage
|
|
22
|
+
next if Object.const_defined?('ActiveJob::Base')
|
|
23
|
+
|
|
24
|
+
[[%i[consumer], :active_job_missing]]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# ActiveJob needs to always run with manual offset management
|
|
28
|
+
# Automatic offset management cannot work with ActiveJob. Otherwise we could mark as
|
|
29
|
+
# consumed jobs that did not run because of shutdown.
|
|
30
|
+
virtual do |data, errors|
|
|
31
|
+
next unless errors.empty?
|
|
32
|
+
next unless data[:active_job][:active]
|
|
33
|
+
next if data[:manual_offset_management][:active]
|
|
34
|
+
|
|
35
|
+
[[%i[manual_offset_management], :must_be_enabled]]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
class ActiveJob < Base
|
|
7
|
+
# Topic extensions to be able to check if given topic is ActiveJob topic
|
|
8
|
+
module Topic
|
|
9
|
+
# @param active [Boolean] should this topic be considered one working with ActiveJob
|
|
10
|
+
#
|
|
11
|
+
# @note Since this feature supports only one setting (active), we can use the old API
|
|
12
|
+
# where the boolean would be an argument
|
|
13
|
+
def active_job(active = false)
|
|
14
|
+
@active_job ||= Config.new(active: active)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [Boolean] is this an ActiveJob topic
|
|
18
|
+
def active_job?
|
|
19
|
+
active_job.active?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @return [Hash] topic with all its native configuration options plus active job
|
|
23
|
+
# namespace settings
|
|
24
|
+
def to_h
|
|
25
|
+
super.merge(
|
|
26
|
+
active_job: active_job.to_h
|
|
27
|
+
).freeze
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
# Active-Job related components
|
|
7
|
+
# @note We can load it always, despite someone not using ActiveJob as it just adds a method
|
|
8
|
+
# to the routing, without actually breaking anything.
|
|
9
|
+
class ActiveJob < Base
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
class Base
|
|
7
|
+
# Routing builder expander that injects feature related drawing operations into it
|
|
8
|
+
class Expander < Module
|
|
9
|
+
# @param scope [Module] feature scope in which contract and other things should be
|
|
10
|
+
# @return [Expander] builder expander instance
|
|
11
|
+
def initialize(scope)
|
|
12
|
+
super()
|
|
13
|
+
@scope = scope
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Builds anonymous module that alters how `#draw` behaves allowing the feature contracts
|
|
17
|
+
# to run.
|
|
18
|
+
# @param mod [::Karafka::Routing::Builder] builder we will prepend to
|
|
19
|
+
def prepended(mod)
|
|
20
|
+
super
|
|
21
|
+
|
|
22
|
+
mod.prepend(prepended_module)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# @return [Module] builds an anonymous module with `#draw` that will alter the builder
|
|
28
|
+
# `#draw` allowing to run feature context aware code.
|
|
29
|
+
def prepended_module
|
|
30
|
+
scope = @scope
|
|
31
|
+
|
|
32
|
+
Module.new do
|
|
33
|
+
# Runs validations related to this feature on a topic
|
|
34
|
+
#
|
|
35
|
+
# @param block [Proc] routing defining block
|
|
36
|
+
define_method :draw do |&block|
|
|
37
|
+
result = super(&block)
|
|
38
|
+
|
|
39
|
+
each do |consumer_group|
|
|
40
|
+
consumer_group.topics.each do |topic|
|
|
41
|
+
scope::Contract.new.validate!(topic.to_h)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
result
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
# Namespace for all the topic related features we support
|
|
6
|
+
#
|
|
7
|
+
# @note Not all the Karafka features need to be defined here as only those that have routing
|
|
8
|
+
# or other extensions need to be here. That is why we keep (for now) features under the
|
|
9
|
+
# routing namespace.
|
|
10
|
+
module Features
|
|
11
|
+
# Base for all the features
|
|
12
|
+
class Base
|
|
13
|
+
class << self
|
|
14
|
+
# Extends topic and builder with given feature API
|
|
15
|
+
def activate
|
|
16
|
+
Topic.prepend(self::Topic) if const_defined?('Topic', false)
|
|
17
|
+
Proxy.prepend(self::Builder) if const_defined?('Builder', false)
|
|
18
|
+
Builder.prepend(self::Builder) if const_defined?('Builder', false)
|
|
19
|
+
Builder.prepend(Base::Expander.new(self)) if const_defined?('Contract', false)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Loads all the features and activates them
|
|
23
|
+
def load_all
|
|
24
|
+
ObjectSpace
|
|
25
|
+
.each_object(Class)
|
|
26
|
+
.select { |klass| klass < self }
|
|
27
|
+
.sort_by(&:to_s)
|
|
28
|
+
.each(&:activate)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
class DeadLetterQueue < Base
|
|
7
|
+
# Config for dead letter queue feature
|
|
8
|
+
Config = Struct.new(
|
|
9
|
+
:active,
|
|
10
|
+
# We add skip variants but in regular we support only `:one`
|
|
11
|
+
:max_retries,
|
|
12
|
+
# To what topic the skipped messages should be moved
|
|
13
|
+
:topic,
|
|
14
|
+
keyword_init: true
|
|
15
|
+
) { alias_method :active?, :active }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
class DeadLetterQueue < Base
|
|
7
|
+
# Rules around dead letter queue settings
|
|
8
|
+
class Contract < Contracts::Base
|
|
9
|
+
configure do |config|
|
|
10
|
+
config.error_messages = YAML.safe_load(
|
|
11
|
+
File.read(
|
|
12
|
+
File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
|
|
13
|
+
)
|
|
14
|
+
).fetch('en').fetch('validations').fetch('topic')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
nested :dead_letter_queue do
|
|
18
|
+
required(:active) { |val| [true, false].include?(val) }
|
|
19
|
+
required(:max_retries) { |val| val.is_a?(Integer) && val >= 0 }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Validate topic name only if dlq is active
|
|
23
|
+
virtual do |data, errors|
|
|
24
|
+
next unless errors.empty?
|
|
25
|
+
|
|
26
|
+
dead_letter_queue = data[:dead_letter_queue]
|
|
27
|
+
|
|
28
|
+
next unless dead_letter_queue[:active]
|
|
29
|
+
|
|
30
|
+
topic = dead_letter_queue[:topic]
|
|
31
|
+
|
|
32
|
+
# When topic is set to false, it means we just want to skip dispatch on DLQ
|
|
33
|
+
next if topic == false
|
|
34
|
+
next if topic.is_a?(String) && Contracts::TOPIC_REGEXP.match?(topic)
|
|
35
|
+
|
|
36
|
+
[[%i[dead_letter_queue topic], :format]]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
class DeadLetterQueue < Base
|
|
7
|
+
# DLQ topic extensions
|
|
8
|
+
module Topic
|
|
9
|
+
# After how many retries should be move data to DLQ
|
|
10
|
+
DEFAULT_MAX_RETRIES = 3
|
|
11
|
+
|
|
12
|
+
private_constant :DEFAULT_MAX_RETRIES
|
|
13
|
+
|
|
14
|
+
# @param max_retries [Integer] after how many retries should we move data to dlq
|
|
15
|
+
# @param topic [String, false] where the messages should be moved if failing or false
|
|
16
|
+
# if we do not want to move it anywhere and just skip
|
|
17
|
+
# @return [Config] defined config
|
|
18
|
+
def dead_letter_queue(max_retries: DEFAULT_MAX_RETRIES, topic: nil)
|
|
19
|
+
@dead_letter_queue ||= Config.new(
|
|
20
|
+
active: !topic.nil?,
|
|
21
|
+
max_retries: max_retries,
|
|
22
|
+
topic: topic
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [Boolean] is the dlq active or not
|
|
27
|
+
def dead_letter_queue?
|
|
28
|
+
dead_letter_queue.active?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [Hash] topic with all its native configuration options plus dlq settings
|
|
32
|
+
def to_h
|
|
33
|
+
super.merge(
|
|
34
|
+
dead_letter_queue: dead_letter_queue.to_h
|
|
35
|
+
).freeze
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
# This feature allows to continue processing when encountering errors.
|
|
7
|
+
# After certain number of retries, given messages will be moved to alternative topic,
|
|
8
|
+
# unclogging processing.
|
|
9
|
+
#
|
|
10
|
+
# @note This feature has an expanded version in the Pro mode. We do not use a new feature
|
|
11
|
+
# injection in Pro (topic settings)
|
|
12
|
+
class DeadLetterQueue < Base
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
class Declaratives < Base
|
|
7
|
+
# Config for declarative topics feature
|
|
8
|
+
Config = Struct.new(
|
|
9
|
+
:active,
|
|
10
|
+
:partitions,
|
|
11
|
+
:replication_factor,
|
|
12
|
+
:details,
|
|
13
|
+
keyword_init: true
|
|
14
|
+
) { alias_method :active?, :active }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
class Declaratives < Base
|
|
7
|
+
# Basic validation of the Kafka expected config details
|
|
8
|
+
class Contract < Contracts::Base
|
|
9
|
+
configure do |config|
|
|
10
|
+
config.error_messages = YAML.safe_load(
|
|
11
|
+
File.read(
|
|
12
|
+
File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
|
|
13
|
+
)
|
|
14
|
+
).fetch('en').fetch('validations').fetch('topic')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
nested :declaratives do
|
|
18
|
+
required(:active) { |val| [true, false].include?(val) }
|
|
19
|
+
required(:partitions) { |val| val.is_a?(Integer) && val.positive? }
|
|
20
|
+
required(:replication_factor) { |val| val.is_a?(Integer) && val.positive? }
|
|
21
|
+
required(:details) do |val|
|
|
22
|
+
val.is_a?(Hash) &&
|
|
23
|
+
val.keys.all? { |key| key.is_a?(Symbol) }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
class Declaratives < Base
|
|
7
|
+
# Extension for managing Kafka topic configuration
|
|
8
|
+
module Topic
|
|
9
|
+
# @param active [Boolean] is the topic structure management feature active
|
|
10
|
+
# @param partitions [Integer]
|
|
11
|
+
# @param replication_factor [Integer]
|
|
12
|
+
# @param details [Hash] extra configuration for the topic
|
|
13
|
+
# @return [Config] defined structure
|
|
14
|
+
def config(active: true, partitions: 1, replication_factor: 1, **details)
|
|
15
|
+
@declaratives ||= Config.new(
|
|
16
|
+
active: active,
|
|
17
|
+
partitions: partitions,
|
|
18
|
+
replication_factor: replication_factor,
|
|
19
|
+
details: details
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [Config] config details
|
|
24
|
+
def declaratives
|
|
25
|
+
config
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [true] declaratives is always active
|
|
29
|
+
def declaratives?
|
|
30
|
+
declaratives.active?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Hash] topic with all its native configuration options plus declaratives
|
|
34
|
+
# settings
|
|
35
|
+
def to_h
|
|
36
|
+
super.merge(
|
|
37
|
+
declaratives: declaratives.to_h
|
|
38
|
+
).freeze
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
# This feature allows to store per topic structure that can be later on used to bootstrap
|
|
7
|
+
# topics structure for test/development, etc. This allows to share the same set of settings
|
|
8
|
+
# for topics despite the environment. Pretty much similar to how the `structure.sql` or
|
|
9
|
+
# `schema.rb` operate for SQL dbs.
|
|
10
|
+
class Declaratives < Base
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
class ManualOffsetManagement < Base
|
|
7
|
+
# Config for manual offset management feature
|
|
8
|
+
Config = Struct.new(
|
|
9
|
+
:active,
|
|
10
|
+
keyword_init: true
|
|
11
|
+
) { alias_method :active?, :active }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
class ManualOffsetManagement < Base
|
|
7
|
+
# Rules around manual offset management settings
|
|
8
|
+
class Contract < Contracts::Base
|
|
9
|
+
configure do |config|
|
|
10
|
+
config.error_messages = YAML.safe_load(
|
|
11
|
+
File.read(
|
|
12
|
+
File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
|
|
13
|
+
)
|
|
14
|
+
).fetch('en').fetch('validations').fetch('topic')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
nested :manual_offset_management do
|
|
18
|
+
required(:active) { |val| [true, false].include?(val) }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
class ManualOffsetManagement < Base
|
|
7
|
+
# Topic extensions to be able to manage manual offset management settings
|
|
8
|
+
module Topic
|
|
9
|
+
# @param active [Boolean] should we stop managing the offset in Karafka and make the user
|
|
10
|
+
# responsible for marking messages as consumed.
|
|
11
|
+
# @return [Config] defined config
|
|
12
|
+
#
|
|
13
|
+
# @note Since this feature supports only one setting (active), we can use the old API
|
|
14
|
+
# where the boolean would be an argument
|
|
15
|
+
def manual_offset_management(active = false)
|
|
16
|
+
@manual_offset_management ||= Config.new(active: active)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [Boolean] is manual offset management enabled for a given topic
|
|
20
|
+
def manual_offset_management?
|
|
21
|
+
manual_offset_management.active?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [Hash] topic with all its native configuration options plus manual offset
|
|
25
|
+
# management namespace settings
|
|
26
|
+
def to_h
|
|
27
|
+
super.merge(
|
|
28
|
+
manual_offset_management: manual_offset_management.to_h
|
|
29
|
+
).freeze
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Routing
|
|
5
|
+
module Features
|
|
6
|
+
# All the things needed to be able to manage manual offset management from the routing
|
|
7
|
+
# perspective.
|
|
8
|
+
#
|
|
9
|
+
# Manual offset management allows users to completely disable automatic management of the
|
|
10
|
+
# offset. This can be used for implementing long-living window operations and other things
|
|
11
|
+
# where we do not want to commit the offset with each batch.
|
|
12
|
+
#
|
|
13
|
+
# Not all the Karafka and Karafka Pro features may be compatible with this feature being on.
|
|
14
|
+
class ManualOffsetManagement < Base
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|