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.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +12 -38
  4. data/CHANGELOG.md +56 -2
  5. data/Gemfile +6 -3
  6. data/Gemfile.lock +25 -23
  7. data/bin/integrations +1 -1
  8. data/config/locales/errors.yml +21 -2
  9. data/config/locales/pro_errors.yml +16 -1
  10. data/karafka.gemspec +4 -2
  11. data/lib/active_job/queue_adapters/karafka_adapter.rb +2 -0
  12. data/lib/karafka/admin/configs/config.rb +81 -0
  13. data/lib/karafka/admin/configs/resource.rb +88 -0
  14. data/lib/karafka/admin/configs.rb +103 -0
  15. data/lib/karafka/admin.rb +201 -100
  16. data/lib/karafka/base_consumer.rb +2 -2
  17. data/lib/karafka/cli/info.rb +9 -7
  18. data/lib/karafka/cli/server.rb +7 -7
  19. data/lib/karafka/cli/topics/align.rb +109 -0
  20. data/lib/karafka/cli/topics/base.rb +66 -0
  21. data/lib/karafka/cli/topics/create.rb +35 -0
  22. data/lib/karafka/cli/topics/delete.rb +30 -0
  23. data/lib/karafka/cli/topics/migrate.rb +31 -0
  24. data/lib/karafka/cli/topics/plan.rb +169 -0
  25. data/lib/karafka/cli/topics/repartition.rb +41 -0
  26. data/lib/karafka/cli/topics/reset.rb +18 -0
  27. data/lib/karafka/cli/topics.rb +13 -123
  28. data/lib/karafka/connection/client.rb +55 -37
  29. data/lib/karafka/connection/listener.rb +22 -17
  30. data/lib/karafka/connection/proxy.rb +93 -4
  31. data/lib/karafka/connection/status.rb +14 -2
  32. data/lib/karafka/contracts/config.rb +14 -1
  33. data/lib/karafka/contracts/topic.rb +1 -1
  34. data/lib/karafka/deserializers/headers.rb +15 -0
  35. data/lib/karafka/deserializers/key.rb +15 -0
  36. data/lib/karafka/deserializers/payload.rb +16 -0
  37. data/lib/karafka/embedded.rb +2 -0
  38. data/lib/karafka/helpers/async.rb +5 -2
  39. data/lib/karafka/helpers/colorize.rb +6 -0
  40. data/lib/karafka/instrumentation/callbacks/oauthbearer_token_refresh.rb +29 -0
  41. data/lib/karafka/instrumentation/logger_listener.rb +23 -3
  42. data/lib/karafka/instrumentation/notifications.rb +10 -0
  43. data/lib/karafka/instrumentation/vendors/appsignal/client.rb +16 -2
  44. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +20 -0
  45. data/lib/karafka/messages/batch_metadata.rb +1 -1
  46. data/lib/karafka/messages/builders/batch_metadata.rb +1 -1
  47. data/lib/karafka/messages/builders/message.rb +10 -6
  48. data/lib/karafka/messages/message.rb +2 -1
  49. data/lib/karafka/messages/metadata.rb +20 -4
  50. data/lib/karafka/messages/parser.rb +1 -1
  51. data/lib/karafka/pro/base_consumer.rb +12 -23
  52. data/lib/karafka/pro/encryption/cipher.rb +7 -3
  53. data/lib/karafka/pro/encryption/contracts/config.rb +1 -0
  54. data/lib/karafka/pro/encryption/errors.rb +4 -1
  55. data/lib/karafka/pro/encryption/messages/middleware.rb +13 -11
  56. data/lib/karafka/pro/encryption/messages/parser.rb +22 -20
  57. data/lib/karafka/pro/encryption/setup/config.rb +5 -0
  58. data/lib/karafka/pro/iterator/expander.rb +2 -1
  59. data/lib/karafka/pro/iterator/tpl_builder.rb +38 -0
  60. data/lib/karafka/pro/iterator.rb +28 -2
  61. data/lib/karafka/pro/loader.rb +3 -0
  62. data/lib/karafka/pro/processing/coordinator.rb +15 -2
  63. data/lib/karafka/pro/processing/expansions_selector.rb +2 -0
  64. data/lib/karafka/pro/processing/jobs_queue.rb +122 -5
  65. data/lib/karafka/pro/processing/periodic_job/consumer.rb +67 -0
  66. data/lib/karafka/pro/processing/piping/consumer.rb +126 -0
  67. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +1 -1
  68. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +1 -1
  69. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom.rb +1 -1
  70. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +1 -1
  71. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +1 -1
  72. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +1 -1
  73. data/lib/karafka/pro/processing/strategies/aj/dlq_mom.rb +1 -1
  74. data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +1 -1
  75. data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +2 -0
  76. data/lib/karafka/pro/processing/strategies/default.rb +5 -1
  77. data/lib/karafka/pro/processing/strategies/dlq/default.rb +21 -5
  78. data/lib/karafka/pro/processing/strategies/lrj/default.rb +2 -0
  79. data/lib/karafka/pro/processing/strategies/lrj/mom.rb +2 -0
  80. data/lib/karafka/pro/processing/subscription_groups_coordinator.rb +52 -0
  81. data/lib/karafka/pro/routing/features/direct_assignments/config.rb +27 -0
  82. data/lib/karafka/pro/routing/features/direct_assignments/contracts/consumer_group.rb +53 -0
  83. data/lib/karafka/pro/routing/features/direct_assignments/contracts/topic.rb +108 -0
  84. data/lib/karafka/pro/routing/features/direct_assignments/subscription_group.rb +77 -0
  85. data/lib/karafka/pro/routing/features/direct_assignments/topic.rb +69 -0
  86. data/lib/karafka/pro/routing/features/direct_assignments.rb +25 -0
  87. data/lib/karafka/pro/routing/features/patterns/builder.rb +1 -1
  88. data/lib/karafka/pro/routing/features/swarm/contracts/routing.rb +76 -0
  89. data/lib/karafka/pro/routing/features/swarm/contracts/topic.rb +16 -5
  90. data/lib/karafka/pro/routing/features/swarm/topic.rb +25 -2
  91. data/lib/karafka/pro/routing/features/swarm.rb +11 -0
  92. data/lib/karafka/pro/swarm/liveness_listener.rb +20 -0
  93. data/lib/karafka/processing/coordinator.rb +17 -8
  94. data/lib/karafka/processing/coordinators_buffer.rb +5 -2
  95. data/lib/karafka/processing/executor.rb +6 -2
  96. data/lib/karafka/processing/executors_buffer.rb +5 -2
  97. data/lib/karafka/processing/jobs_queue.rb +9 -4
  98. data/lib/karafka/processing/strategies/aj_dlq_mom.rb +1 -1
  99. data/lib/karafka/processing/strategies/default.rb +7 -1
  100. data/lib/karafka/processing/strategies/dlq.rb +17 -2
  101. data/lib/karafka/processing/workers_batch.rb +4 -1
  102. data/lib/karafka/routing/builder.rb +6 -2
  103. data/lib/karafka/routing/consumer_group.rb +2 -1
  104. data/lib/karafka/routing/features/dead_letter_queue/config.rb +5 -0
  105. data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +8 -0
  106. data/lib/karafka/routing/features/dead_letter_queue/topic.rb +10 -2
  107. data/lib/karafka/routing/features/deserializers/config.rb +18 -0
  108. data/lib/karafka/routing/features/deserializers/contracts/topic.rb +31 -0
  109. data/lib/karafka/routing/features/deserializers/topic.rb +51 -0
  110. data/lib/karafka/routing/features/deserializers.rb +11 -0
  111. data/lib/karafka/routing/proxy.rb +9 -14
  112. data/lib/karafka/routing/router.rb +11 -2
  113. data/lib/karafka/routing/subscription_group.rb +9 -1
  114. data/lib/karafka/routing/topic.rb +0 -1
  115. data/lib/karafka/runner.rb +1 -1
  116. data/lib/karafka/setup/config.rb +50 -9
  117. data/lib/karafka/status.rb +7 -8
  118. data/lib/karafka/swarm/supervisor.rb +16 -2
  119. data/lib/karafka/templates/karafka.rb.erb +28 -1
  120. data/lib/karafka/version.rb +1 -1
  121. data.tar.gz.sig +0 -0
  122. metadata +38 -12
  123. metadata.gz.sig +0 -0
  124. data/lib/karafka/routing/consumer_mapper.rb +0 -23
  125. data/lib/karafka/serialization/json/deserializer.rb +0 -19
  126. 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
@@ -26,7 +26,7 @@ module Karafka
26
26
  # the regexp if we used named patterns
27
27
  # @param block [Proc]
28
28
  def pattern(regexp_or_name, regexp = nil, &block)
29
- consumer_group('app') do
29
+ consumer_group(default_group_id) do
30
30
  pattern(regexp_or_name, regexp, &block)
31
31
  end
32
32
  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
- val.is_a?(Array) &&
37
- val.all? { |id| id.is_a?(Integer) }
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.last == Float::INFINITY
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 which we
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
- def increment
74
- synchronize { @running_jobs += 1 }
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
- def decrement
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
- @coordinator_class.new(
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 = ::Karafka::App.config.internal.processing.strategy_selector.find(topic)
148
- expansions = ::Karafka::App.config.internal.processing.expansions_selector.find(topic)
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