karafka 2.3.4 → 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 +56 -2
- data/Gemfile +6 -3
- data/Gemfile.lock +25 -23
- 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 +201 -100
- 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/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,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)
|
@@ -22,6 +22,10 @@ module Karafka
|
|
22
22
|
# - shutdown - runs when process is going to shutdown
|
23
23
|
class Executor
|
24
24
|
extend Forwardable
|
25
|
+
include Helpers::ConfigImporter.new(
|
26
|
+
strategy_selector: %i[internal processing strategy_selector],
|
27
|
+
expansions_selector: %i[internal processing expansions_selector]
|
28
|
+
)
|
25
29
|
|
26
30
|
def_delegators :@coordinator, :topic, :partition
|
27
31
|
|
@@ -144,8 +148,8 @@ module Karafka
|
|
144
148
|
@consumer ||= begin
|
145
149
|
topic = @coordinator.topic
|
146
150
|
|
147
|
-
strategy =
|
148
|
-
expansions =
|
151
|
+
strategy = strategy_selector.find(topic)
|
152
|
+
expansions = expansions_selector.find(topic)
|
149
153
|
|
150
154
|
consumer = topic.consumer_class.new
|
151
155
|
# We use singleton class as the same consumer class may be used to process different
|