karafka 2.3.3 → 2.4.0.beta2
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/workflows/ci.yml +12 -38
- data/CHANGELOG.md +59 -0
- data/Gemfile +6 -3
- data/Gemfile.lock +29 -27
- data/bin/integrations +1 -1
- data/config/locales/errors.yml +21 -2
- data/config/locales/pro_errors.yml +16 -1
- 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 +211 -90
- 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 +55 -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/constraints.rb +3 -3
- data/lib/karafka/contracts/config.rb +14 -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/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/contracts/routing.rb +76 -0
- data/lib/karafka/pro/routing/features/swarm/contracts/topic.rb +16 -5
- data/lib/karafka/pro/routing/features/swarm/topic.rb +25 -2
- data/lib/karafka/pro/routing/features/swarm.rb +11 -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 +9 -1
- data/lib/karafka/routing/topic.rb +0 -1
- data/lib/karafka/runner.rb +1 -1
- data/lib/karafka/setup/config.rb +50 -9
- data/lib/karafka/status.rb +7 -8
- data/lib/karafka/swarm/supervisor.rb +16 -2
- data/lib/karafka/templates/karafka.rb.erb +28 -1
- data/lib/karafka/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +38 -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,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,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
|
|
@@ -32,10 +32,18 @@ module Karafka
|
|
|
32
32
|
required(:active) { |val| val == true }
|
|
33
33
|
|
|
34
34
|
required(:nodes) do |val|
|
|
35
|
-
val.is_a?(Range)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
)
|
|
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
|
|
39
47
|
end
|
|
40
48
|
end
|
|
41
49
|
|
|
@@ -46,9 +54,12 @@ module Karafka
|
|
|
46
54
|
next unless errors.empty?
|
|
47
55
|
|
|
48
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)
|
|
49
60
|
|
|
50
61
|
# Defaults
|
|
51
|
-
next if nodes.first.zero? && nodes.
|
|
62
|
+
next if nodes.first.zero? && nodes.include?(Float::INFINITY)
|
|
52
63
|
|
|
53
64
|
# If our expectation towards which node should run things matches at least one
|
|
54
65
|
# node, then it's ok
|
|
@@ -19,8 +19,31 @@ module Karafka
|
|
|
19
19
|
# Topic swarm API extensions
|
|
20
20
|
module Topic
|
|
21
21
|
# Allows defining swarm routing topic settings
|
|
22
|
-
# @param nodes [Range, Array] range of nodes ids or array with nodes ids for
|
|
23
|
-
# should run given topic
|
|
22
|
+
# @param nodes [Range, Array, Hash] range of nodes ids or array with nodes ids for
|
|
23
|
+
# which we should run given topic or hash with nodes expected partition assignments
|
|
24
|
+
# for the direct assignments API.
|
|
25
|
+
#
|
|
26
|
+
# @example Assign given topic only to node 1
|
|
27
|
+
# swarm(nodes: [1])
|
|
28
|
+
#
|
|
29
|
+
# @example Assign given topic to nodes from 1 to 3
|
|
30
|
+
# swarm(nodes: 1..3)
|
|
31
|
+
#
|
|
32
|
+
# @example Assign partitions 2 and 3 to node 0 and partitions 0, 1 to node 1
|
|
33
|
+
# swarm(
|
|
34
|
+
# nodes: {
|
|
35
|
+
# 0 => [2, 3],
|
|
36
|
+
# 1 => [0, 1]
|
|
37
|
+
# }
|
|
38
|
+
# )
|
|
39
|
+
#
|
|
40
|
+
# @example Assign partitions in ranges to nodes
|
|
41
|
+
# swarm(
|
|
42
|
+
# nodes: {
|
|
43
|
+
# 0 => (0..2),
|
|
44
|
+
# 1 => (3..5)
|
|
45
|
+
# }
|
|
46
|
+
# )
|
|
24
47
|
def swarm(nodes: (0...Karafka::App.config.swarm.nodes))
|
|
25
48
|
@swarm ||= Config.new(active: true, nodes: nodes)
|
|
26
49
|
end
|
|
@@ -18,6 +18,17 @@ module Karafka
|
|
|
18
18
|
# Karafka Pro Swarm extensions to the routing
|
|
19
19
|
# They allow for more granular work assignment in the swarm
|
|
20
20
|
class Swarm < Base
|
|
21
|
+
class << self
|
|
22
|
+
# Binds our routing validation contract prior to warmup in the supervisor, so we can
|
|
23
|
+
# run it when all the context should be there (config + full routing)
|
|
24
|
+
#
|
|
25
|
+
# @param config [Karafka::Core::Configurable::Node] app config
|
|
26
|
+
def post_setup(config)
|
|
27
|
+
config.monitor.subscribe('app.before_warmup') do
|
|
28
|
+
Contracts::Routing.new.validate!(config.internal.routing.builder)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
21
32
|
end
|
|
22
33
|
end
|
|
23
34
|
end
|
|
@@ -103,6 +103,26 @@ module Karafka
|
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
105
|
|
|
106
|
+
# Deregister the polling tracker for given listener
|
|
107
|
+
# @param _event [Karafka::Core::Monitoring::Event]
|
|
108
|
+
def on_connection_listener_stopping(_event)
|
|
109
|
+
# We are interested in disabling tracking for given listener only if it was requested
|
|
110
|
+
# when karafka was running. If we would always clear, it would not catch the shutdown
|
|
111
|
+
# polling requirements. The "running" listener shutdown operations happen only when
|
|
112
|
+
# the manager requests it for downscaling.
|
|
113
|
+
return if Karafka::App.done?
|
|
114
|
+
|
|
115
|
+
clear_polling_tick
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Deregister the polling tracker for given listener
|
|
119
|
+
# @param _event [Karafka::Core::Monitoring::Event]
|
|
120
|
+
def on_connection_listener_stopped(_event)
|
|
121
|
+
return if Karafka::App.done?
|
|
122
|
+
|
|
123
|
+
clear_polling_tick
|
|
124
|
+
end
|
|
125
|
+
|
|
106
126
|
private
|
|
107
127
|
|
|
108
128
|
# @return [Integer] object id of the current thread
|
|
@@ -11,6 +11,7 @@ module Karafka
|
|
|
11
11
|
# future mistakes.
|
|
12
12
|
class Coordinator
|
|
13
13
|
extend Forwardable
|
|
14
|
+
include Core::Helpers::Time
|
|
14
15
|
|
|
15
16
|
attr_reader :pause_tracker, :seek_offset, :topic, :partition
|
|
16
17
|
|
|
@@ -25,12 +26,13 @@ module Karafka
|
|
|
25
26
|
@pause_tracker = pause_tracker
|
|
26
27
|
@revoked = false
|
|
27
28
|
@consumptions = {}
|
|
28
|
-
@running_jobs = 0
|
|
29
|
+
@running_jobs = Hash.new { |h, k| h[k] = 0 }
|
|
29
30
|
@manual_pause = false
|
|
30
31
|
@manual_seek = false
|
|
31
32
|
@mutex = Mutex.new
|
|
32
33
|
@marked = false
|
|
33
34
|
@failure = false
|
|
35
|
+
@changed_at = monotonic_now
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
# Starts the coordinator for given consumption jobs
|
|
@@ -38,7 +40,7 @@ module Karafka
|
|
|
38
40
|
# going to coordinate work. Not used with regular coordinator.
|
|
39
41
|
def start(messages)
|
|
40
42
|
@failure = false
|
|
41
|
-
@running_jobs = 0
|
|
43
|
+
@running_jobs[:consume] = 0
|
|
42
44
|
# We need to clear the consumption results hash here, otherwise we could end up storing
|
|
43
45
|
# consumption results of consumer instances we no longer control
|
|
44
46
|
@consumptions.clear
|
|
@@ -70,16 +72,22 @@ module Karafka
|
|
|
70
72
|
end
|
|
71
73
|
|
|
72
74
|
# Increases number of jobs that we handle with this coordinator
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
# @param job_type [Symbol] type of job that we want to increment
|
|
76
|
+
def increment(job_type)
|
|
77
|
+
synchronize do
|
|
78
|
+
@running_jobs[job_type] += 1
|
|
79
|
+
@changed_at = monotonic_now
|
|
80
|
+
end
|
|
75
81
|
end
|
|
76
82
|
|
|
77
83
|
# Decrements number of jobs we handle at the moment
|
|
78
|
-
|
|
84
|
+
# @param job_type [Symbol] type of job that we want to decrement
|
|
85
|
+
def decrement(job_type)
|
|
79
86
|
synchronize do
|
|
80
|
-
@running_jobs -= 1
|
|
87
|
+
@running_jobs[job_type] -= 1
|
|
88
|
+
@changed_at = monotonic_now
|
|
81
89
|
|
|
82
|
-
return @running_jobs unless @running_jobs.negative?
|
|
90
|
+
return @running_jobs[job_type] unless @running_jobs[job_type].negative?
|
|
83
91
|
|
|
84
92
|
# This should never happen. If it does, something is heavily out of sync. Please reach
|
|
85
93
|
# out to us if you encounter this
|
|
@@ -90,9 +98,10 @@ module Karafka
|
|
|
90
98
|
# Is all the consumption done and finished successfully for this coordinator
|
|
91
99
|
# We do not say we're successful until all work is done, because running work may still
|
|
92
100
|
# crash.
|
|
101
|
+
# @note This is only used for consume synchronization
|
|
93
102
|
def success?
|
|
94
103
|
synchronize do
|
|
95
|
-
@running_jobs.zero? && @consumptions.values.all?(&:success?)
|
|
104
|
+
@running_jobs[:consume].zero? && @consumptions.values.all?(&:success?)
|
|
96
105
|
end
|
|
97
106
|
end
|
|
98
107
|
|
|
@@ -9,10 +9,13 @@ module Karafka
|
|
|
9
9
|
# @note This buffer operates only from the listener loop, thus we do not have to make it
|
|
10
10
|
# thread-safe.
|
|
11
11
|
class CoordinatorsBuffer
|
|
12
|
+
include Helpers::ConfigImporter.new(
|
|
13
|
+
coordinator_class: %i[internal processing coordinator_class]
|
|
14
|
+
)
|
|
15
|
+
|
|
12
16
|
# @param topics [Karafka::Routing::Topics]
|
|
13
17
|
def initialize(topics)
|
|
14
18
|
@pauses_manager = Connection::PausesManager.new
|
|
15
|
-
@coordinator_class = ::Karafka::App.config.internal.processing.coordinator_class
|
|
16
19
|
@coordinators = Hash.new { |h, k| h[k] = {} }
|
|
17
20
|
@topics = topics
|
|
18
21
|
end
|
|
@@ -23,7 +26,7 @@ module Karafka
|
|
|
23
26
|
@coordinators[topic_name][partition] ||= begin
|
|
24
27
|
routing_topic = @topics.find(topic_name)
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
coordinator_class.new(
|
|
27
30
|
routing_topic,
|
|
28
31
|
partition,
|
|
29
32
|
@pauses_manager.fetch(routing_topic, partition)
|