karafka 2.2.13 → 2.3.0.alpha1

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.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +38 -12
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +161 -125
  6. data/Gemfile.lock +12 -12
  7. data/README.md +0 -2
  8. data/SECURITY.md +23 -0
  9. data/config/locales/errors.yml +7 -1
  10. data/config/locales/pro_errors.yml +22 -0
  11. data/docker-compose.yml +3 -1
  12. data/karafka.gemspec +2 -2
  13. data/lib/karafka/admin/acl.rb +287 -0
  14. data/lib/karafka/admin.rb +118 -16
  15. data/lib/karafka/app.rb +12 -3
  16. data/lib/karafka/base_consumer.rb +32 -31
  17. data/lib/karafka/cli/base.rb +1 -1
  18. data/lib/karafka/connection/client.rb +94 -84
  19. data/lib/karafka/connection/conductor.rb +28 -0
  20. data/lib/karafka/connection/listener.rb +165 -46
  21. data/lib/karafka/connection/listeners_batch.rb +5 -11
  22. data/lib/karafka/connection/manager.rb +72 -0
  23. data/lib/karafka/connection/messages_buffer.rb +12 -0
  24. data/lib/karafka/connection/proxy.rb +17 -0
  25. data/lib/karafka/connection/status.rb +75 -0
  26. data/lib/karafka/contracts/config.rb +14 -10
  27. data/lib/karafka/contracts/consumer_group.rb +9 -1
  28. data/lib/karafka/contracts/topic.rb +3 -1
  29. data/lib/karafka/errors.rb +13 -0
  30. data/lib/karafka/instrumentation/assignments_tracker.rb +96 -0
  31. data/lib/karafka/instrumentation/callbacks/rebalance.rb +10 -7
  32. data/lib/karafka/instrumentation/logger_listener.rb +3 -9
  33. data/lib/karafka/instrumentation/notifications.rb +19 -9
  34. data/lib/karafka/instrumentation/vendors/appsignal/metrics_listener.rb +31 -28
  35. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +22 -3
  36. data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +15 -12
  37. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +39 -36
  38. data/lib/karafka/pro/base_consumer.rb +47 -0
  39. data/lib/karafka/pro/connection/manager.rb +300 -0
  40. data/lib/karafka/pro/connection/multiplexing/listener.rb +40 -0
  41. data/lib/karafka/pro/instrumentation/performance_tracker.rb +85 -0
  42. data/lib/karafka/pro/iterator/tpl_builder.rb +1 -1
  43. data/lib/karafka/pro/iterator.rb +1 -6
  44. data/lib/karafka/pro/loader.rb +16 -2
  45. data/lib/karafka/pro/processing/coordinator.rb +2 -1
  46. data/lib/karafka/pro/processing/executor.rb +37 -0
  47. data/lib/karafka/pro/processing/expansions_selector.rb +32 -0
  48. data/lib/karafka/pro/processing/jobs/periodic.rb +41 -0
  49. data/lib/karafka/pro/processing/jobs/periodic_non_blocking.rb +32 -0
  50. data/lib/karafka/pro/processing/jobs_builder.rb +14 -3
  51. data/lib/karafka/pro/processing/offset_metadata/consumer.rb +44 -0
  52. data/lib/karafka/pro/processing/offset_metadata/fetcher.rb +131 -0
  53. data/lib/karafka/pro/processing/offset_metadata/listener.rb +46 -0
  54. data/lib/karafka/pro/processing/schedulers/base.rb +143 -0
  55. data/lib/karafka/pro/processing/schedulers/default.rb +107 -0
  56. data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +1 -1
  57. data/lib/karafka/pro/processing/strategies/default.rb +136 -3
  58. data/lib/karafka/pro/processing/strategies/dlq/default.rb +35 -0
  59. data/lib/karafka/pro/processing/strategies/lrj/default.rb +1 -1
  60. data/lib/karafka/pro/processing/strategies/lrj/mom.rb +1 -1
  61. data/lib/karafka/pro/processing/strategies/vp/default.rb +60 -26
  62. data/lib/karafka/pro/processing/virtual_offset_manager.rb +41 -11
  63. data/lib/karafka/pro/routing/features/long_running_job/topic.rb +2 -0
  64. data/lib/karafka/pro/routing/features/multiplexing/config.rb +38 -0
  65. data/lib/karafka/pro/routing/features/multiplexing/contracts/topic.rb +114 -0
  66. data/lib/karafka/pro/routing/features/multiplexing/patches/contracts/consumer_group.rb +42 -0
  67. data/lib/karafka/pro/routing/features/multiplexing/proxy.rb +38 -0
  68. data/lib/karafka/pro/routing/features/multiplexing/subscription_group.rb +42 -0
  69. data/lib/karafka/pro/routing/features/multiplexing/subscription_groups_builder.rb +40 -0
  70. data/lib/karafka/pro/routing/features/multiplexing.rb +59 -0
  71. data/lib/karafka/pro/routing/features/non_blocking_job/topic.rb +32 -0
  72. data/lib/karafka/pro/routing/features/non_blocking_job.rb +37 -0
  73. data/lib/karafka/pro/routing/features/offset_metadata/config.rb +33 -0
  74. data/lib/karafka/pro/routing/features/offset_metadata/contracts/topic.rb +42 -0
  75. data/lib/karafka/pro/routing/features/offset_metadata/topic.rb +65 -0
  76. data/lib/karafka/pro/routing/features/offset_metadata.rb +40 -0
  77. data/lib/karafka/pro/routing/features/patterns/contracts/consumer_group.rb +4 -0
  78. data/lib/karafka/pro/routing/features/patterns/detector.rb +18 -10
  79. data/lib/karafka/pro/routing/features/periodic_job/config.rb +37 -0
  80. data/lib/karafka/pro/routing/features/periodic_job/contracts/topic.rb +44 -0
  81. data/lib/karafka/pro/routing/features/periodic_job/topic.rb +94 -0
  82. data/lib/karafka/pro/routing/features/periodic_job.rb +27 -0
  83. data/lib/karafka/pro/routing/features/virtual_partitions/config.rb +1 -0
  84. data/lib/karafka/pro/routing/features/virtual_partitions/contracts/topic.rb +1 -0
  85. data/lib/karafka/pro/routing/features/virtual_partitions/topic.rb +7 -2
  86. data/lib/karafka/process.rb +5 -3
  87. data/lib/karafka/processing/coordinator.rb +5 -1
  88. data/lib/karafka/processing/executor.rb +43 -13
  89. data/lib/karafka/processing/executors_buffer.rb +22 -7
  90. data/lib/karafka/processing/jobs/base.rb +19 -2
  91. data/lib/karafka/processing/jobs/consume.rb +3 -3
  92. data/lib/karafka/processing/jobs/idle.rb +5 -0
  93. data/lib/karafka/processing/jobs/revoked.rb +5 -0
  94. data/lib/karafka/processing/jobs/shutdown.rb +5 -0
  95. data/lib/karafka/processing/jobs_queue.rb +19 -8
  96. data/lib/karafka/processing/schedulers/default.rb +42 -0
  97. data/lib/karafka/processing/strategies/base.rb +13 -4
  98. data/lib/karafka/processing/strategies/default.rb +23 -7
  99. data/lib/karafka/processing/strategies/dlq.rb +36 -0
  100. data/lib/karafka/processing/worker.rb +4 -1
  101. data/lib/karafka/routing/builder.rb +12 -2
  102. data/lib/karafka/routing/consumer_group.rb +5 -5
  103. data/lib/karafka/routing/features/base.rb +44 -8
  104. data/lib/karafka/routing/features/dead_letter_queue/config.rb +6 -1
  105. data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +1 -0
  106. data/lib/karafka/routing/features/dead_letter_queue/topic.rb +9 -2
  107. data/lib/karafka/routing/proxy.rb +4 -3
  108. data/lib/karafka/routing/subscription_group.rb +2 -2
  109. data/lib/karafka/routing/subscription_groups_builder.rb +11 -2
  110. data/lib/karafka/routing/topic.rb +8 -10
  111. data/lib/karafka/routing/topics.rb +1 -1
  112. data/lib/karafka/runner.rb +13 -3
  113. data/lib/karafka/server.rb +5 -9
  114. data/lib/karafka/setup/config.rb +21 -1
  115. data/lib/karafka/status.rb +23 -14
  116. data/lib/karafka/templates/karafka.rb.erb +7 -0
  117. data/lib/karafka/time_trackers/partition_usage.rb +56 -0
  118. data/lib/karafka/version.rb +1 -1
  119. data.tar.gz.sig +0 -0
  120. metadata +47 -13
  121. metadata.gz.sig +0 -0
  122. data/lib/karafka/connection/consumer_group_coordinator.rb +0 -48
  123. data/lib/karafka/pro/performance_tracker.rb +0 -84
  124. data/lib/karafka/pro/processing/scheduler.rb +0 -74
  125. data/lib/karafka/processing/scheduler.rb +0 -38
@@ -26,6 +26,41 @@ module Karafka
26
26
  dead_letter_queue
27
27
  ].freeze
28
28
 
29
+ # Override of the standard `#mark_as_consumed` in order to handle the pause tracker
30
+ # reset in case DLQ is marked as fully independent. When DLQ is marked independent,
31
+ # any offset marking causes the pause count tracker to reset. This is useful when
32
+ # the error is not due to the collective batch operations state but due to intermediate
33
+ # "crawling" errors that move with it
34
+ #
35
+ # @see `Strategies::Default#mark_as_consumed` for more details
36
+ # @param message [Messages::Message]
37
+ # @param offset_metadata [String, nil]
38
+ def mark_as_consumed(message, offset_metadata = nil)
39
+ return super unless retrying?
40
+ return super unless topic.dead_letter_queue.independent?
41
+ return false unless super
42
+
43
+ coordinator.pause_tracker.reset
44
+
45
+ true
46
+ end
47
+
48
+ # Override of the standard `#mark_as_consumed!`. Resets the pause tracker count in case
49
+ # DLQ was configured with the `independent` flag.
50
+ #
51
+ # @see `Strategies::Default#mark_as_consumed!` for more details
52
+ # @param message [Messages::Message]
53
+ # @param offset_metadata [String, nil]
54
+ def mark_as_consumed!(message, offset_metadata = nil)
55
+ return super unless retrying?
56
+ return super unless topic.dead_letter_queue.independent?
57
+ return false unless super
58
+
59
+ coordinator.pause_tracker.reset
60
+
61
+ true
62
+ end
63
+
29
64
  # When we encounter non-recoverable message, we skip it and go on with our lives
30
65
  def handle_after_consume
31
66
  coordinator.on_finished do |last_group_message|
@@ -29,7 +29,7 @@ module Karafka
29
29
  ].freeze
30
30
 
31
31
  # We always need to pause prior to doing any jobs for LRJ
32
- def handle_before_enqueue
32
+ def handle_before_schedule_consume
33
33
  super
34
34
 
35
35
  # This ensures that when running LRJ with VP, things operate as expected run only
@@ -29,7 +29,7 @@ module Karafka
29
29
  ].freeze
30
30
 
31
31
  # We always need to pause prior to doing any jobs for LRJ
32
- def handle_before_enqueue
32
+ def handle_before_schedule_consume
33
33
  super
34
34
 
35
35
  # This ensures that when running LRJ with VP, things operate as expected run only
@@ -29,40 +29,74 @@ module Karafka
29
29
  ].freeze
30
30
 
31
31
  # @param message [Karafka::Messages::Message] marks message as consumed
32
+ # @param offset_metadata [String, nil]
32
33
  # @note This virtual offset management uses a regular default marking API underneath.
33
34
  # We do not alter the "real" marking API, as VPs are just one of many cases we want
34
35
  # to support and we do not want to impact them with collective offsets management
35
- def mark_as_consumed(message)
36
- return super if collapsed?
37
-
38
- manager = coordinator.virtual_offset_manager
39
-
40
- coordinator.synchronize do
41
- manager.mark(message)
42
- # If this is last marking on a finished flow, we can use the original
43
- # last message and in order to do so, we need to mark all previous messages as
44
- # consumed as otherwise the computed offset could be different
45
- # We mark until our offset just in case of a DLQ flow or similar, where we do not
46
- # want to mark all but until the expected location
47
- manager.mark_until(message) if coordinator.finished?
48
-
49
- return revoked? unless manager.markable?
50
-
51
- manager.markable? ? super(manager.markable) : revoked?
36
+ def mark_as_consumed(message, offset_metadata = nil)
37
+ if @_in_transaction && !collapsed?
38
+ mark_in_transaction(message, offset_metadata, true)
39
+ elsif collapsed?
40
+ super
41
+ else
42
+ manager = coordinator.virtual_offset_manager
43
+
44
+ coordinator.synchronize do
45
+ manager.mark(message, offset_metadata)
46
+ # If this is last marking on a finished flow, we can use the original
47
+ # last message and in order to do so, we need to mark all previous messages as
48
+ # consumed as otherwise the computed offset could be different
49
+ # We mark until our offset just in case of a DLQ flow or similar, where we do not
50
+ # want to mark all but until the expected location
51
+ manager.mark_until(message, offset_metadata) if coordinator.finished?
52
+
53
+ return revoked? unless manager.markable?
54
+
55
+ manager.markable? ? super(*manager.markable) : revoked?
56
+ end
52
57
  end
53
58
  end
54
59
 
55
60
  # @param message [Karafka::Messages::Message] blocking marks message as consumed
56
- def mark_as_consumed!(message)
57
- return super if collapsed?
61
+ # @param offset_metadata [String, nil]
62
+ def mark_as_consumed!(message, offset_metadata = nil)
63
+ if @_in_transaction && !collapsed?
64
+ mark_in_transaction(message, offset_metadata, false)
65
+ elsif collapsed?
66
+ super
67
+ else
68
+ manager = coordinator.virtual_offset_manager
69
+
70
+ coordinator.synchronize do
71
+ manager.mark(message, offset_metadata)
72
+ manager.mark_until(message, offset_metadata) if coordinator.finished?
73
+ manager.markable? ? super(*manager.markable) : revoked?
74
+ end
75
+ end
76
+ end
58
77
 
59
- manager = coordinator.virtual_offset_manager
78
+ # Stores the next offset for processing inside of the transaction when collapsed and
79
+ # accumulates marking as consumed in the local buffer.
80
+ #
81
+ # Due to nature of VPs we cannot provide full EOS support but we can simulate it,
82
+ # making sure that no offset are stored unless transaction is finished. We do it by
83
+ # accumulating the post-transaction marking requests and after it is successfully done
84
+ # we mark each as consumed. This effectively on errors "rollbacks" the state and
85
+ # prevents offset storage.
86
+ #
87
+ # Since the EOS here is "weak", we do not have to worry about the race-conditions and
88
+ # we do not have to have any mutexes.
89
+ #
90
+ # @param message [Messages::Message] message we want to commit inside of a transaction
91
+ # @param offset_metadata [String, nil] offset metadata or nil if none
92
+ # @param async [Boolean] should we mark in async or sync way (applicable only to post
93
+ # transaction state synchronization usage as within transaction it is always sync)
94
+ def mark_in_transaction(message, offset_metadata, async)
95
+ raise Errors::TransactionRequiredError unless @_in_transaction
60
96
 
61
- coordinator.synchronize do
62
- manager.mark(message)
63
- manager.mark_until(message) if coordinator.finished?
64
- manager.markable? ? super(manager.markable) : revoked?
65
- end
97
+ return super if collapsed?
98
+
99
+ @_transaction_marked << [message, offset_metadata, async]
66
100
  end
67
101
 
68
102
  # @return [Boolean] is the virtual processing collapsed in the context of given
@@ -112,7 +146,7 @@ module Karafka
112
146
  #
113
147
  # @note This can be done without the mutex, because it happens from the same thread
114
148
  # for all the work (listener thread)
115
- def handle_before_enqueue
149
+ def handle_before_schedule_consume
116
150
  super
117
151
 
118
152
  coordinator.virtual_offset_manager.register(
@@ -30,22 +30,29 @@ module Karafka
30
30
 
31
31
  # @param topic [String]
32
32
  # @param partition [Integer]
33
+ # @param offset_metadata_strategy [Symbol] what metadata should we select. That is, should
34
+ # we use the most recent or one picked from the offset that is going to be committed
33
35
  #
34
36
  # @note We need topic and partition because we use a seek message (virtual) for real offset
35
37
  # management. We could keep real message reference but this can be memory consuming
36
38
  # and not worth it.
37
- def initialize(topic, partition)
39
+ def initialize(topic, partition, offset_metadata_strategy)
38
40
  @topic = topic
39
41
  @partition = partition
40
42
  @groups = []
41
43
  @marked = {}
44
+ @offsets_metadata = {}
42
45
  @real_offset = -1
46
+ @offset_metadata_strategy = offset_metadata_strategy
47
+ @current_offset_metadata = nil
43
48
  end
44
49
 
45
50
  # Clears the manager for a next collective operation
46
51
  def clear
47
52
  @groups.clear
48
- @marked = {}
53
+ @offsets_metadata.clear
54
+ @current_offset_metadata = nil
55
+ @marked.clear
49
56
  @real_offset = -1
50
57
  end
51
58
 
@@ -65,9 +72,14 @@ module Karafka
65
72
  # and we can refresh our real offset representation based on that as it might have changed
66
73
  # to a newer real offset.
67
74
  # @param message [Karafka::Messages::Message] message coming from VP we want to mark
68
- def mark(message)
75
+ # @param offset_metadata [String, nil] offset metadata. `nil` if none
76
+ def mark(message, offset_metadata)
69
77
  offset = message.offset
70
78
 
79
+ # Store metadata when we materialize the most stable offset
80
+ @offsets_metadata[offset] = offset_metadata
81
+ @current_offset_metadata = offset_metadata
82
+
71
83
  group = @groups.find { |reg_group| reg_group.include?(offset) }
72
84
 
73
85
  # This case can happen when someone uses MoM and wants to mark message from a previous
@@ -81,6 +93,9 @@ module Karafka
81
93
 
82
94
  # Mark all previous messages from the same group also as virtually consumed
83
95
  group[0..position].each do |markable_offset|
96
+ # Set previous messages metadata offset as the offset of higher one for overwrites
97
+ # unless a different metadata were set explicitely
98
+ @offsets_metadata[markable_offset] ||= offset_metadata
84
99
  @marked[markable_offset] = true
85
100
  end
86
101
 
@@ -91,13 +106,15 @@ module Karafka
91
106
  # Mark all from all groups including the `message`.
92
107
  # Useful when operating in a collapsed state for marking
93
108
  # @param message [Karafka::Messages::Message]
94
- def mark_until(message)
95
- mark(message)
109
+ # @param offset_metadata [String, nil]
110
+ def mark_until(message, offset_metadata)
111
+ mark(message, offset_metadata)
96
112
 
97
113
  @groups.each do |group|
98
114
  group.each do |offset|
99
115
  next if offset > message.offset
100
116
 
117
+ @offsets_metadata[offset] = offset_metadata
101
118
  @marked[offset] = true
102
119
  end
103
120
  end
@@ -116,15 +133,28 @@ module Karafka
116
133
  !@real_offset.negative?
117
134
  end
118
135
 
119
- # @return [Messages::Seek] markable message for real offset marking
136
+ # @return [Array<Messages::Seek, String>] markable message for real offset marking and
137
+ # its associated metadata
120
138
  def markable
121
139
  raise Errors::InvalidRealOffsetUsageError unless markable?
122
140
 
123
- Messages::Seek.new(
124
- @topic,
125
- @partition,
126
- @real_offset
127
- )
141
+ offset_metadata = case @offset_metadata_strategy
142
+ when :exact
143
+ @offsets_metadata.fetch(@real_offset)
144
+ when :current
145
+ @current_offset_metadata
146
+ else
147
+ raise Errors::UnsupportedCaseError, @offset_metadata_strategy
148
+ end
149
+
150
+ [
151
+ Messages::Seek.new(
152
+ @topic,
153
+ @partition,
154
+ @real_offset
155
+ ),
156
+ offset_metadata
157
+ ]
128
158
  end
129
159
 
130
160
  private
@@ -23,6 +23,8 @@ module Karafka
23
23
  @long_running_job ||= Config.new(active: active)
24
24
  end
25
25
 
26
+ alias long_running long_running_job
27
+
26
28
  # @return [Boolean] is a given job on a topic a long-running one
27
29
  def long_running_job?
28
30
  long_running_job.active?
@@ -0,0 +1,38 @@
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 Multiplexing < Base
19
+ # Multiplexing configuration
20
+ Config = Struct.new(
21
+ :active,
22
+ :min,
23
+ :max,
24
+ :boot,
25
+ keyword_init: true
26
+ ) do
27
+ alias_method :active?, :active
28
+
29
+ # @return [Boolean] true if we are allowed to upscale and downscale
30
+ def dynamic?
31
+ min < max
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,114 @@
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 Multiplexing < Base
19
+ # Namespace for multiplexing feature contracts
20
+ module Contracts
21
+ # Validates the subscription group multiplexing setup
22
+ # We validate it on the topic level as subscription groups are not built during the
23
+ # routing as they are pre-run dynamically built.
24
+ #
25
+ # multiplexing attributes are optional since multiplexing may not be enabled
26
+ class Topic < Karafka::Contracts::Base
27
+ configure do |config|
28
+ config.error_messages = YAML.safe_load(
29
+ File.read(
30
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
31
+ )
32
+ ).fetch('en').fetch('validations').fetch('topic')
33
+ end
34
+
35
+ nested(:subscription_group_details) do
36
+ optional(:multiplexing_min) { |val| val.is_a?(Integer) && val >= 1 }
37
+ optional(:multiplexing_max) { |val| val.is_a?(Integer) && val >= 1 }
38
+ optional(:multiplexing_boot) { |val| val.is_a?(Integer) && val >= 1 }
39
+ end
40
+
41
+ # Makes sure min is not more than max
42
+ virtual do |data, errors|
43
+ next unless errors.empty?
44
+ next unless min(data)
45
+ next unless max(data)
46
+
47
+ min = min(data)
48
+ max = max(data)
49
+
50
+ next if min <= max
51
+
52
+ [[%w[subscription_group_details], :multiplexing_min_max_mismatch]]
53
+ end
54
+
55
+ # Makes sure, that boot is between min and max
56
+ virtual do |data, errors|
57
+ next unless errors.empty?
58
+ next unless min(data)
59
+ next unless max(data)
60
+ next unless boot(data)
61
+
62
+ min = min(data)
63
+ max = max(data)
64
+ boot = boot(data)
65
+
66
+ next if boot >= min && boot <= max
67
+
68
+ [[%w[subscription_group_details], :multiplexing_boot_mismatch]]
69
+ end
70
+
71
+ # Makes sure, that boot is equal to min and max when not in dynamic mode
72
+ virtual do |data, errors|
73
+ next unless errors.empty?
74
+ next unless min(data)
75
+ next unless max(data)
76
+ next unless boot(data)
77
+
78
+ min = min(data)
79
+ max = max(data)
80
+ boot = boot(data)
81
+
82
+ # In dynamic mode there are other rules to check boot
83
+ next if min != max
84
+ next if boot == min
85
+
86
+ [[%w[subscription_group_details], :multiplexing_boot_not_dynamic]]
87
+ end
88
+
89
+ class << self
90
+ # @param data [Hash] topic details
91
+ # @return [Integer, false] min or false if missing
92
+ def min(data)
93
+ data[:subscription_group_details].fetch(:multiplexing_min, false)
94
+ end
95
+
96
+ # @param data [Hash] topic details
97
+ # @return [Integer, false] max or false if missing
98
+ def max(data)
99
+ data[:subscription_group_details].fetch(:multiplexing_max, false)
100
+ end
101
+
102
+ # @param data [Hash] topic details
103
+ # @return [Integer, false] boot or false if missing
104
+ def boot(data)
105
+ data[:subscription_group_details].fetch(:multiplexing_boot, false)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,42 @@
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 Multiplexing < Base
19
+ # Patches to Karafka OSS
20
+ module Patches
21
+ # Contracts patches
22
+ module Contracts
23
+ # Consumer group contract patches
24
+ module ConsumerGroup
25
+ # Redefines the setup allowing for multiple sgs as long as with different names
26
+ #
27
+ # @param topic [Hash] topic config hash
28
+ # @return [Array] topic unique key for validators
29
+ def topic_unique_key(topic)
30
+ [
31
+ topic[:name],
32
+ topic[:subscription_group_details]
33
+ ]
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,38 @@
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 Multiplexing < Base
19
+ # Allows for multiplexing setup inside a consumer group definition
20
+ module Proxy
21
+ # @param min [Integer, nil] min multiplexing count or nil to set it to max, effectively
22
+ # disabling dynamic multiplexing
23
+ # @param max [Integer] max multiplexing count
24
+ # @param boot [Integer] how many listeners should we start during boot by default
25
+ def multiplexing(min: nil, max: 1, boot: nil)
26
+ @target.current_subscription_group_details.merge!(
27
+ multiplexing_min: min || max,
28
+ multiplexing_max: max,
29
+ # Picks half of max by default as long as possible. Otherwise goes with min
30
+ multiplexing_boot: boot || [min || max, (max / 2)].max
31
+ )
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
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 Multiplexing < Base
19
+ # Adds methods needed for the multiplexing to work
20
+ module SubscriptionGroup
21
+ # @return [Config] multiplexing config
22
+ def multiplexing
23
+ @multiplexing ||= begin
24
+ max = @details.fetch(:multiplexing_max, 1)
25
+ min = @details.fetch(:multiplexing_min, max)
26
+ boot = @details.fetch(:multiplexing_boot, max / 2)
27
+ active = max > 1
28
+
29
+ Config.new(active: active, min: min, max: max, boot: boot)
30
+ end
31
+ end
32
+
33
+ # @return [Boolean] is multiplexing active
34
+ def multiplexing?
35
+ multiplexing.active?
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,40 @@
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 Multiplexing < Base
19
+ # Expands the builder to multiply multiplexed groups
20
+ module SubscriptionGroupsBuilder
21
+ # Takes into consideration multiplexing and builds the more groups
22
+ #
23
+ # @param topics_array [Array<Routing::Topic>] group of topics that have the same
24
+ # settings and can use the same connection
25
+ # @return [Array<Array<Routing::Topics>>] expanded groups
26
+ def expand(topics_array)
27
+ factor = topics_array.first.subscription_group_details.fetch(:multiplexing_max, 1)
28
+
29
+ Array.new(factor) do |i|
30
+ ::Karafka::Routing::Topics.new(
31
+ i.zero? ? topics_array : topics_array.map(&:dup)
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,59 @@
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
+ # Namespace for Pro routing enhancements
17
+ module Routing
18
+ # Namespace for additional Pro features
19
+ module Features
20
+ # Multiplexing allows for creating multiple subscription groups for the same topic inside
21
+ # of the same subscription group allowing for better parallelism with limited number
22
+ # of processes
23
+ class Multiplexing < Base
24
+ class << self
25
+ # @param _config [Karafka::Core::Configurable::Node] app config node
26
+ def pre_setup(_config)
27
+ # Make sure we use proper unique validator for topics definitions
28
+ ::Karafka::Contracts::ConsumerGroup.singleton_class.prepend(
29
+ Patches::Contracts::ConsumerGroup
30
+ )
31
+ end
32
+
33
+ # If needed installs the needed listener and initializes tracker
34
+ #
35
+ # @param _config [Karafka::Core::Configurable::Node] app config
36
+ def post_setup(_config)
37
+ ::Karafka::App.monitor.subscribe('app.running') do
38
+ # Do not install the manager and listener to control multiplexing unless there is
39
+ # multiplexing enabled and it is dynamic.
40
+ # We only need to control multiplexing when it is in a dynamic state
41
+ next unless ::Karafka::App
42
+ .subscription_groups
43
+ .values
44
+ .flat_map(&:itself)
45
+ .any? { |sg| sg.multiplexing? && sg.multiplexing.dynamic? }
46
+
47
+ # Subscribe for events and possibility to manage via the Pro connection manager
48
+ # that supports multiplexing
49
+ ::Karafka.monitor.subscribe(
50
+ ::Karafka::Pro::Connection::Multiplexing::Listener.new
51
+ )
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end