karafka 2.3.2 → 2.4.0.beta1

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