karafka 2.3.2 → 2.4.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci.yml +12 -38
- data/CHANGELOG.md +65 -0
- data/Gemfile +6 -3
- data/Gemfile.lock +25 -23
- data/README.md +2 -2
- data/bin/integrations +1 -1
- data/config/locales/errors.yml +24 -2
- data/config/locales/pro_errors.yml +19 -0
- data/karafka.gemspec +4 -2
- data/lib/active_job/queue_adapters/karafka_adapter.rb +2 -0
- data/lib/karafka/admin/configs/config.rb +81 -0
- data/lib/karafka/admin/configs/resource.rb +88 -0
- data/lib/karafka/admin/configs.rb +103 -0
- data/lib/karafka/admin.rb +200 -89
- data/lib/karafka/base_consumer.rb +2 -2
- data/lib/karafka/cli/info.rb +9 -7
- data/lib/karafka/cli/server.rb +7 -7
- data/lib/karafka/cli/topics/align.rb +109 -0
- data/lib/karafka/cli/topics/base.rb +66 -0
- data/lib/karafka/cli/topics/create.rb +35 -0
- data/lib/karafka/cli/topics/delete.rb +30 -0
- data/lib/karafka/cli/topics/migrate.rb +31 -0
- data/lib/karafka/cli/topics/plan.rb +169 -0
- data/lib/karafka/cli/topics/repartition.rb +41 -0
- data/lib/karafka/cli/topics/reset.rb +18 -0
- data/lib/karafka/cli/topics.rb +13 -123
- data/lib/karafka/connection/client.rb +62 -37
- data/lib/karafka/connection/listener.rb +22 -17
- data/lib/karafka/connection/proxy.rb +93 -4
- data/lib/karafka/connection/status.rb +14 -2
- data/lib/karafka/contracts/config.rb +36 -1
- data/lib/karafka/contracts/topic.rb +1 -1
- data/lib/karafka/deserializers/headers.rb +15 -0
- data/lib/karafka/deserializers/key.rb +15 -0
- data/lib/karafka/deserializers/payload.rb +16 -0
- data/lib/karafka/embedded.rb +2 -0
- data/lib/karafka/helpers/async.rb +5 -2
- data/lib/karafka/helpers/colorize.rb +6 -0
- data/lib/karafka/instrumentation/callbacks/oauthbearer_token_refresh.rb +29 -0
- data/lib/karafka/instrumentation/logger_listener.rb +23 -3
- data/lib/karafka/instrumentation/notifications.rb +10 -0
- data/lib/karafka/instrumentation/vendors/appsignal/client.rb +16 -2
- data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +34 -4
- data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +20 -0
- data/lib/karafka/messages/batch_metadata.rb +1 -1
- data/lib/karafka/messages/builders/batch_metadata.rb +1 -1
- data/lib/karafka/messages/builders/message.rb +10 -6
- data/lib/karafka/messages/message.rb +2 -1
- data/lib/karafka/messages/metadata.rb +20 -4
- data/lib/karafka/messages/parser.rb +1 -1
- data/lib/karafka/pro/base_consumer.rb +12 -23
- data/lib/karafka/pro/encryption/cipher.rb +7 -3
- data/lib/karafka/pro/encryption/contracts/config.rb +1 -0
- data/lib/karafka/pro/encryption/errors.rb +4 -1
- data/lib/karafka/pro/encryption/messages/middleware.rb +13 -11
- data/lib/karafka/pro/encryption/messages/parser.rb +22 -20
- data/lib/karafka/pro/encryption/setup/config.rb +5 -0
- data/lib/karafka/pro/iterator/expander.rb +2 -1
- data/lib/karafka/pro/iterator/tpl_builder.rb +38 -0
- data/lib/karafka/pro/iterator.rb +28 -2
- data/lib/karafka/pro/loader.rb +3 -0
- data/lib/karafka/pro/processing/coordinator.rb +15 -2
- data/lib/karafka/pro/processing/expansions_selector.rb +2 -0
- data/lib/karafka/pro/processing/jobs_queue.rb +122 -5
- data/lib/karafka/pro/processing/periodic_job/consumer.rb +67 -0
- data/lib/karafka/pro/processing/piping/consumer.rb +126 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +2 -0
- data/lib/karafka/pro/processing/strategies/default.rb +5 -1
- data/lib/karafka/pro/processing/strategies/dlq/default.rb +21 -5
- data/lib/karafka/pro/processing/strategies/lrj/default.rb +2 -0
- data/lib/karafka/pro/processing/strategies/lrj/mom.rb +2 -0
- data/lib/karafka/pro/processing/subscription_groups_coordinator.rb +52 -0
- data/lib/karafka/pro/routing/features/direct_assignments/config.rb +27 -0
- data/lib/karafka/pro/routing/features/direct_assignments/contracts/consumer_group.rb +53 -0
- data/lib/karafka/pro/routing/features/direct_assignments/contracts/topic.rb +108 -0
- data/lib/karafka/pro/routing/features/direct_assignments/subscription_group.rb +77 -0
- data/lib/karafka/pro/routing/features/direct_assignments/topic.rb +69 -0
- data/lib/karafka/pro/routing/features/direct_assignments.rb +25 -0
- data/lib/karafka/pro/routing/features/patterns/builder.rb +1 -1
- data/lib/karafka/pro/routing/features/swarm/config.rb +31 -0
- data/lib/karafka/pro/routing/features/swarm/contracts/routing.rb +76 -0
- data/lib/karafka/pro/routing/features/swarm/contracts/topic.rb +78 -0
- data/lib/karafka/pro/routing/features/swarm/topic.rb +77 -0
- data/lib/karafka/pro/routing/features/swarm.rb +36 -0
- data/lib/karafka/pro/swarm/liveness_listener.rb +20 -0
- data/lib/karafka/processing/coordinator.rb +17 -8
- data/lib/karafka/processing/coordinators_buffer.rb +5 -2
- data/lib/karafka/processing/executor.rb +6 -2
- data/lib/karafka/processing/executors_buffer.rb +5 -2
- data/lib/karafka/processing/jobs_queue.rb +9 -4
- data/lib/karafka/processing/strategies/aj_dlq_mom.rb +1 -1
- data/lib/karafka/processing/strategies/default.rb +7 -1
- data/lib/karafka/processing/strategies/dlq.rb +17 -2
- data/lib/karafka/processing/workers_batch.rb +4 -1
- data/lib/karafka/routing/builder.rb +6 -2
- data/lib/karafka/routing/consumer_group.rb +2 -1
- data/lib/karafka/routing/features/dead_letter_queue/config.rb +5 -0
- data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +8 -0
- data/lib/karafka/routing/features/dead_letter_queue/topic.rb +10 -2
- data/lib/karafka/routing/features/deserializers/config.rb +18 -0
- data/lib/karafka/routing/features/deserializers/contracts/topic.rb +31 -0
- data/lib/karafka/routing/features/deserializers/topic.rb +51 -0
- data/lib/karafka/routing/features/deserializers.rb +11 -0
- data/lib/karafka/routing/proxy.rb +9 -14
- data/lib/karafka/routing/router.rb +11 -2
- data/lib/karafka/routing/subscription_group.rb +22 -1
- data/lib/karafka/routing/topic.rb +0 -1
- data/lib/karafka/runner.rb +1 -1
- data/lib/karafka/setup/config.rb +51 -10
- data/lib/karafka/status.rb +7 -8
- data/lib/karafka/swarm/manager.rb +15 -3
- data/lib/karafka/swarm/node.rb +3 -3
- data/lib/karafka/swarm/pidfd.rb +20 -4
- data/lib/karafka/swarm/supervisor.rb +25 -8
- data/lib/karafka/templates/karafka.rb.erb +28 -1
- data/lib/karafka/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +42 -12
- metadata.gz.sig +0 -0
- data/lib/karafka/routing/consumer_mapper.rb +0 -23
- data/lib/karafka/serialization/json/deserializer.rb +0 -19
- data/lib/karafka/time_trackers/partition_usage.rb +0 -56
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module Processing
|
17
|
+
# Uses the jobs queue API to lock (pause) and unlock (resume) operations of a given
|
18
|
+
# subscription group. It is abstracted away from jobs queue on this layer because we do
|
19
|
+
# not want to introduce jobs queue as a concept to the consumers layer
|
20
|
+
class SubscriptionGroupsCoordinator
|
21
|
+
include Singleton
|
22
|
+
|
23
|
+
# @param subscription_group [Karafka::Routing::SubscriptionGroup] subscription group we
|
24
|
+
# want to pause
|
25
|
+
# @param lock_id [Object] key we want to use if we want to set multiple locks on the same
|
26
|
+
# subscription group
|
27
|
+
# @param kwargs [Object] Any keyword arguments accepted by the jobs queue lock.
|
28
|
+
def pause(subscription_group, lock_id = nil, **kwargs)
|
29
|
+
jobs_queue.lock_async(
|
30
|
+
subscription_group.id,
|
31
|
+
lock_id,
|
32
|
+
**kwargs
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param subscription_group [Karafka::Routing::SubscriptionGroup] subscription group we
|
37
|
+
# want to resume
|
38
|
+
# @param lock_id [Object] lock id (if it was used to pause)
|
39
|
+
def resume(subscription_group, lock_id = nil)
|
40
|
+
jobs_queue.unlock_async(subscription_group.id, lock_id)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# @return [Karafka::Pro::Processing::JobsQueue]
|
46
|
+
def jobs_queue
|
47
|
+
@jobs_queue ||= Karafka::Server.jobs_queue
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module Routing
|
17
|
+
module Features
|
18
|
+
class DirectAssignments < Base
|
19
|
+
# Direct assignments feature configuration
|
20
|
+
Config = Struct.new(:active, :partitions, keyword_init: true) do
|
21
|
+
alias_method :active?, :active
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module Routing
|
17
|
+
module Features
|
18
|
+
class DirectAssignments < Base
|
19
|
+
module Contracts
|
20
|
+
# Contract to validate configuration of the direct assignments feature
|
21
|
+
class ConsumerGroup < Karafka::Contracts::Base
|
22
|
+
configure do |config|
|
23
|
+
config.error_messages = YAML.safe_load(
|
24
|
+
File.read(
|
25
|
+
File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
|
26
|
+
)
|
27
|
+
).fetch('en').fetch('validations').fetch('consumer_group')
|
28
|
+
|
29
|
+
virtual do |data, errors|
|
30
|
+
next unless errors.empty?
|
31
|
+
|
32
|
+
active = []
|
33
|
+
|
34
|
+
data[:topics].each do |topic|
|
35
|
+
active << topic[:direct_assignments][:active]
|
36
|
+
end
|
37
|
+
|
38
|
+
# If none active we use standard subscriptions
|
39
|
+
next if active.none?
|
40
|
+
# If all topics from a given CG are using direct assignments that is expected
|
41
|
+
# and allowed
|
42
|
+
next if active.all?
|
43
|
+
|
44
|
+
[[%i[direct_assignments], :homogenous]]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module Routing
|
17
|
+
module Features
|
18
|
+
class DirectAssignments < Base
|
19
|
+
# Namespace for direct assignments feature contracts
|
20
|
+
module Contracts
|
21
|
+
# Contract to validate configuration of the direct assignments topic feature
|
22
|
+
class Topic < Karafka::Contracts::Base
|
23
|
+
configure do |config|
|
24
|
+
config.error_messages = YAML.safe_load(
|
25
|
+
File.read(
|
26
|
+
File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
|
27
|
+
)
|
28
|
+
).fetch('en').fetch('validations').fetch('topic')
|
29
|
+
end
|
30
|
+
|
31
|
+
nested(:direct_assignments) do
|
32
|
+
required(:active) { |val| [true, false].include?(val) }
|
33
|
+
|
34
|
+
required(:partitions) do |val|
|
35
|
+
next true if val == true
|
36
|
+
next false unless val.is_a?(Hash)
|
37
|
+
next false unless val.keys.all? { |part| part.is_a?(Integer) }
|
38
|
+
next false unless val.values.all? { |flag| flag == true }
|
39
|
+
|
40
|
+
true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
virtual do |data, errors|
|
45
|
+
next unless errors.empty?
|
46
|
+
|
47
|
+
direct_assignments = data[:direct_assignments]
|
48
|
+
partitions = direct_assignments[:partitions]
|
49
|
+
|
50
|
+
next unless direct_assignments[:active]
|
51
|
+
next unless partitions.is_a?(Hash)
|
52
|
+
next unless partitions.empty?
|
53
|
+
|
54
|
+
[[%i[direct_assignments], :active_but_empty]]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Make sure that when we use swarm, all requested partitions have allocation
|
58
|
+
# We cannot assign partitions and then not allocate them in swarm mode
|
59
|
+
# If this is the case, they should not be assigned in the first place
|
60
|
+
virtual do |data, errors|
|
61
|
+
next unless errors.empty?
|
62
|
+
|
63
|
+
direct_assignments = data[:direct_assignments]
|
64
|
+
swarm = data[:swarm]
|
65
|
+
|
66
|
+
next unless direct_assignments[:active]
|
67
|
+
next unless swarm[:active]
|
68
|
+
|
69
|
+
nodes = swarm[:nodes]
|
70
|
+
|
71
|
+
next unless nodes.is_a?(Hash)
|
72
|
+
# Can be true for all partitions assignment and in this case we do not check
|
73
|
+
next unless direct_assignments[:partitions].is_a?(Hash)
|
74
|
+
|
75
|
+
direct_partitions = direct_assignments[:partitions].keys
|
76
|
+
swarm_partitions = nodes.values.flatten
|
77
|
+
|
78
|
+
next unless swarm_partitions.all? { |partition| partition.is_a?(Integer) }
|
79
|
+
next if direct_partitions.sort == swarm_partitions.sort
|
80
|
+
|
81
|
+
# If we assigned more partitions than we distributed in swarm
|
82
|
+
if (direct_partitions - swarm_partitions).size.positive?
|
83
|
+
[[%i[direct_assignments], :swarm_not_complete]]
|
84
|
+
else
|
85
|
+
[[%i[direct_assignments], :swarm_overbooked]]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Make sure that direct assignments are not used together with patterns as we
|
90
|
+
# cannot apply patterns on direct assignments
|
91
|
+
virtual do |data, errors|
|
92
|
+
next unless errors.empty?
|
93
|
+
|
94
|
+
direct_assignments = data[:direct_assignments]
|
95
|
+
patterns = data[:patterns]
|
96
|
+
|
97
|
+
next unless direct_assignments[:active]
|
98
|
+
next unless patterns[:active]
|
99
|
+
|
100
|
+
[[%i[direct_assignments], :patterns_active]]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module Routing
|
17
|
+
module Features
|
18
|
+
# Alterations to the direct assignments that allow us to do stable direct assignments
|
19
|
+
# without working with consumer groups dynamic assignments
|
20
|
+
class DirectAssignments < Base
|
21
|
+
# Extension allowing us to select correct subscriptions and assignments based on the
|
22
|
+
# expanded routing setup
|
23
|
+
module SubscriptionGroup
|
24
|
+
# @return [false, Array<String>] false if we do not have any subscriptions or array
|
25
|
+
# with all the subscriptions for given subscription group
|
26
|
+
def subscriptions
|
27
|
+
topics
|
28
|
+
.select(&:active?)
|
29
|
+
.reject { |topic| topic.direct_assignments.active? }
|
30
|
+
.map(&:subscription_name)
|
31
|
+
.then { |subscriptions| subscriptions.empty? ? false : subscriptions }
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param consumer [Karafka::Connection::Proxy] consumer for expanding the partition
|
35
|
+
# knowledge in case of certain topics assignments
|
36
|
+
# @return [Rdkafka::Consumer::TopicPartitionList] final tpl for assignments
|
37
|
+
def assignments(consumer)
|
38
|
+
topics
|
39
|
+
.select(&:active?)
|
40
|
+
.select { |topic| topic.direct_assignments.active? }
|
41
|
+
.map { |topic| build_assignments(topic) }
|
42
|
+
.to_h
|
43
|
+
.tap { |topics| return false if topics.empty? }
|
44
|
+
.then { |topics| Iterator::Expander.new.call(topics) }
|
45
|
+
.then { |topics| Iterator::TplBuilder.new(consumer, topics).call }
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Builds assignments based on all the routing stuff. Takes swarm into consideration.
|
51
|
+
#
|
52
|
+
# @param topic [Karafka::Routing::Topic]
|
53
|
+
# @return [Array<String, Hash>]
|
54
|
+
def build_assignments(topic)
|
55
|
+
node = Karafka::App.config.swarm.node
|
56
|
+
|
57
|
+
standard_setup = [
|
58
|
+
topic.subscription_name,
|
59
|
+
topic.direct_assignments.partitions
|
60
|
+
]
|
61
|
+
|
62
|
+
return standard_setup unless node
|
63
|
+
# Unless user explicitly assigned particular partitions to particular nodes, we just
|
64
|
+
# go with full regular assignments
|
65
|
+
return standard_setup unless topic.swarm.nodes.is_a?(Hash)
|
66
|
+
|
67
|
+
[
|
68
|
+
topic.subscription_name,
|
69
|
+
topic.swarm.nodes.fetch(node.id).map { |partition| [partition, true] }.to_h
|
70
|
+
]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module Routing
|
17
|
+
module Features
|
18
|
+
class DirectAssignments < Base
|
19
|
+
# Topic extensions for direct assignments
|
20
|
+
module Topic
|
21
|
+
# Allows for direct assignment of
|
22
|
+
# @param partitions_or_all [true, Array<Integer>] informs Karafka that we want
|
23
|
+
# to use direct assignments instead of automatic for this topic. It also allows us
|
24
|
+
# to specify which partitions we're interested in or `true` if in all
|
25
|
+
#
|
26
|
+
# @example Assign all available partitions
|
27
|
+
# direct_assignments(true)
|
28
|
+
#
|
29
|
+
# @example Assign partitions 2, 3 and 5
|
30
|
+
# direct_assignments(2, 3, 5)
|
31
|
+
#
|
32
|
+
# @example Assign partitions from 0 to 3
|
33
|
+
# direct_assignments(0..3)
|
34
|
+
def direct_assignments(*partitions_or_all)
|
35
|
+
@direct_assignments ||= if partitions_or_all == [true]
|
36
|
+
Config.new(
|
37
|
+
active: true,
|
38
|
+
partitions: true
|
39
|
+
)
|
40
|
+
elsif partitions_or_all.size == 1 && partitions_or_all.first.is_a?(Range)
|
41
|
+
partitions_or_all = partitions_or_all.first.to_a
|
42
|
+
|
43
|
+
Config.new(
|
44
|
+
active: true,
|
45
|
+
partitions: partitions_or_all.map { |partition| [partition, true] }.to_h
|
46
|
+
)
|
47
|
+
else
|
48
|
+
Config.new(
|
49
|
+
active: !partitions_or_all.empty?,
|
50
|
+
partitions: partitions_or_all.map { |partition| [partition, true] }.to_h
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
alias assign direct_assignments
|
56
|
+
|
57
|
+
# @return [Hash] topic with all its native configuration options plus direct
|
58
|
+
# assignments
|
59
|
+
def to_h
|
60
|
+
super.merge(
|
61
|
+
direct_assignments: direct_assignments.to_h
|
62
|
+
).freeze
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module Routing
|
17
|
+
module Features
|
18
|
+
# Allows for building direct assignments that bypass consumer group auto-assignments
|
19
|
+
# Useful for building complex pipelines that have explicit assignment requirements
|
20
|
+
class DirectAssignments < Base
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module Routing
|
17
|
+
module Features
|
18
|
+
class Swarm < Base
|
19
|
+
# Swarm feature configuration
|
20
|
+
Config = Struct.new(
|
21
|
+
:active,
|
22
|
+
:nodes,
|
23
|
+
keyword_init: true
|
24
|
+
) do
|
25
|
+
alias_method :active?, :active
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module Routing
|
17
|
+
module Features
|
18
|
+
class Swarm < Base
|
19
|
+
module Contracts
|
20
|
+
# Special contract that validates prior to starting swarm that each node has
|
21
|
+
# at least one assignment.
|
22
|
+
#
|
23
|
+
# It is special because we cannot run it during routing definitions, because we can
|
24
|
+
# only run it when all routes are defined and full context is available.
|
25
|
+
#
|
26
|
+
# This is why we use it before warmup when everything is expected to be configured.
|
27
|
+
class Routing < Karafka::Contracts::Base
|
28
|
+
configure do |config|
|
29
|
+
config.error_messages = YAML.safe_load(
|
30
|
+
File.read(
|
31
|
+
File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
|
32
|
+
)
|
33
|
+
).fetch('en').fetch('validations').fetch('routing')
|
34
|
+
end
|
35
|
+
|
36
|
+
# Validates that each node has at least one assignment.
|
37
|
+
#
|
38
|
+
# @param builder [Karafka::Routing::Builder]
|
39
|
+
def validate!(builder)
|
40
|
+
nodes_setup = Hash.new do |h, node_id|
|
41
|
+
h[node_id] = { active: false, node_id: node_id }
|
42
|
+
end
|
43
|
+
|
44
|
+
# Initialize nodes in the hash so we can iterate over them
|
45
|
+
App.config.swarm.nodes.times { |node_id| nodes_setup[node_id] }
|
46
|
+
nodes_setup.freeze
|
47
|
+
|
48
|
+
builder.each do |consumer_group|
|
49
|
+
consumer_group.topics.each do |topic|
|
50
|
+
nodes_setup.each do |node_id, details|
|
51
|
+
next unless topic.active?
|
52
|
+
next unless topic.swarm.nodes.include?(node_id)
|
53
|
+
|
54
|
+
details[:active] = true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
nodes_setup.each_value do |details|
|
60
|
+
super(details)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
virtual do |data, errors|
|
65
|
+
next unless errors.empty?
|
66
|
+
next if data[:active]
|
67
|
+
|
68
|
+
[[%i[swarm_nodes], :not_used]]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
module Routing
|
17
|
+
module Features
|
18
|
+
class Swarm < Base
|
19
|
+
# Namespace for swarm contracts
|
20
|
+
module Contracts
|
21
|
+
# Contract to validate configuration of the swarm feature
|
22
|
+
class Topic < Karafka::Contracts::Base
|
23
|
+
configure do |config|
|
24
|
+
config.error_messages = YAML.safe_load(
|
25
|
+
File.read(
|
26
|
+
File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
|
27
|
+
)
|
28
|
+
).fetch('en').fetch('validations').fetch('topic')
|
29
|
+
end
|
30
|
+
|
31
|
+
nested(:swarm) do
|
32
|
+
required(:active) { |val| val == true }
|
33
|
+
|
34
|
+
required(:nodes) do |val|
|
35
|
+
next true if val.is_a?(Range)
|
36
|
+
next true if val.is_a?(Array) && val.all? { |id| id.is_a?(Integer) }
|
37
|
+
next false unless val.is_a?(Hash)
|
38
|
+
next false unless val.keys.all? { |id| id.is_a?(Integer) }
|
39
|
+
|
40
|
+
values = val.values
|
41
|
+
|
42
|
+
next false unless values.all? { |ps| ps.is_a?(Array) || ps.is_a?(Range) }
|
43
|
+
next true if values.flatten.all? { |part| part.is_a?(Integer) }
|
44
|
+
next true if values.flatten.all? { |part| part.is_a?(Range) }
|
45
|
+
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Make sure that if range is defined it fits number of nodes (except infinity)
|
51
|
+
# As it may be a common error to accidentally define a node that will never be
|
52
|
+
# reached
|
53
|
+
virtual do |data, errors|
|
54
|
+
next unless errors.empty?
|
55
|
+
|
56
|
+
nodes = data[:swarm][:nodes]
|
57
|
+
# Hash can be used when we use direct assignments. In such cases nodes ids are
|
58
|
+
# keys in the hash and values are partitions
|
59
|
+
nodes = nodes.keys if nodes.is_a?(Hash)
|
60
|
+
|
61
|
+
# Defaults
|
62
|
+
next if nodes.first.zero? && nodes.include?(Float::INFINITY)
|
63
|
+
|
64
|
+
# If our expectation towards which node should run things matches at least one
|
65
|
+
# node, then it's ok
|
66
|
+
next if Karafka::App.config.swarm.nodes.times.any? do |node_id|
|
67
|
+
nodes.include?(node_id)
|
68
|
+
end
|
69
|
+
|
70
|
+
[[%i[swarm_nodes], :with_non_existent_nodes]]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|