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
@@ -5,6 +5,10 @@ module Karafka
5
5
  # Buffer for executors of a given subscription group. It wraps around the concept of building
6
6
  # and caching them, so we can re-use them instead of creating new each time.
7
7
  class ExecutorsBuffer
8
+ include Helpers::ConfigImporter.new(
9
+ executor_class: %i[internal processing executor_class]
10
+ )
11
+
8
12
  # @param client [Connection::Client]
9
13
  # @param subscription_group [Routing::SubscriptionGroup]
10
14
  # @return [ExecutorsBuffer]
@@ -13,7 +17,6 @@ module Karafka
13
17
  @client = client
14
18
  # We need two layers here to keep track of topics, partitions and processing groups
15
19
  @buffer = Hash.new { |h, k| h[k] = Hash.new { |h2, k2| h2[k2] = {} } }
16
- @executor_class = Karafka::App.config.internal.processing.executor_class
17
20
  end
18
21
 
19
22
  # Finds or creates an executor based on the provided details
@@ -24,7 +27,7 @@ module Karafka
24
27
  # @param coordinator [Karafka::Processing::Coordinator]
25
28
  # @return [Executor, Pro::Processing::Executor] consumer executor
26
29
  def find_or_create(topic, partition, parallel_key, coordinator)
27
- @buffer[topic][partition][parallel_key] ||= @executor_class.new(
30
+ @buffer[topic][partition][parallel_key] ||= executor_class.new(
28
31
  @subscription_group.id,
29
32
  @client,
30
33
  coordinator
@@ -13,6 +13,11 @@ module Karafka
13
13
  # @note This job queue also keeps track / understands number of busy workers. This is because
14
14
  # we use a single workers poll that can have granular scheduling.
15
15
  class JobsQueue
16
+ include Helpers::ConfigImporter.new(
17
+ concurrency: %i[concurrency],
18
+ tick_interval: %i[internal tick_interval]
19
+ )
20
+
16
21
  # @return [Karafka::Processing::JobsQueue]
17
22
  def initialize
18
23
  @queue = Queue.new
@@ -24,8 +29,6 @@ module Karafka
24
29
  # We cannot use a single semaphore as it could potentially block in listeners that should
25
30
  # process with their data and also could unlock when a given group needs to remain locked
26
31
  @semaphores = {}
27
- @concurrency = Karafka::App.config.concurrency
28
- @tick_interval = ::Karafka::App.config.internal.tick_interval
29
32
  @in_processing = Hash.new { |h, k| h[k] = [] }
30
33
  @statistics = { busy: 0, enqueued: 0 }
31
34
 
@@ -67,7 +70,7 @@ module Karafka
67
70
  # Assume that moving to queue means being picked up immediately not to create stats
68
71
  # race conditions because of pop overhead. If there are workers available, we assume
69
72
  # work is going to be handled as we never reject enqueued jobs
70
- if @statistics[:busy] < @concurrency
73
+ if @statistics[:busy] < concurrency
71
74
  @statistics[:busy] += 1
72
75
  else
73
76
  # If system is fully loaded, it means this job is indeed enqueued
@@ -155,12 +158,14 @@ module Karafka
155
158
  # the work to be finished.
156
159
  # @note This method is blocking.
157
160
  def wait(group_id)
161
+ interval_in_seconds = tick_interval / 1_000.0
162
+
158
163
  # Go doing other things while we cannot process and wait for anyone to finish their work
159
164
  # and re-check the wait status
160
165
  while wait?(group_id)
161
166
  yield if block_given?
162
167
 
163
- @semaphores.fetch(group_id).pop(timeout: @tick_interval / 1_000.0)
168
+ @semaphores.fetch(group_id).pop(timeout: interval_in_seconds)
164
169
  end
165
170
  end
166
171
 
@@ -34,7 +34,7 @@ module Karafka
34
34
  # We can commit the offset here because we know that we skip it "forever" and
35
35
  # since AJ consumer commits the offset after each job, we also know that the
36
36
  # previous job was successful
37
- mark_as_consumed(skippable_message)
37
+ mark_dispatched_to_dlq(skippable_message)
38
38
  pause(coordinator.seek_offset, nil, false)
39
39
  end
40
40
  end
@@ -120,7 +120,7 @@ module Karafka
120
120
  raise e
121
121
  ensure
122
122
  # We need to decrease number of jobs that this coordinator coordinates as it has finished
123
- coordinator.decrement
123
+ coordinator.decrement(:consume)
124
124
  end
125
125
 
126
126
  # Standard flow marks work as consumed and moves on if everything went ok.
@@ -147,6 +147,8 @@ module Karafka
147
147
  # Code that should run on idle runs without messages available
148
148
  def handle_idle
149
149
  nil
150
+ ensure
151
+ coordinator.decrement(:idle)
150
152
  end
151
153
 
152
154
  # We need to always un-pause the processing in case we have lost a given partition.
@@ -161,6 +163,8 @@ module Karafka
161
163
  Karafka.monitor.instrument('consumer.revoked', caller: self) do
162
164
  revoked
163
165
  end
166
+ ensure
167
+ coordinator.decrement(:revoked)
164
168
  end
165
169
 
166
170
  # Runs the shutdown code
@@ -169,6 +173,8 @@ module Karafka
169
173
  Karafka.monitor.instrument('consumer.shutdown', caller: self) do
170
174
  shutdown
171
175
  end
176
+ ensure
177
+ coordinator.decrement(:shutdown)
172
178
  end
173
179
  end
174
180
  end
@@ -76,7 +76,7 @@ module Karafka
76
76
  dispatch_to_dlq(skippable_message)
77
77
 
78
78
  # We mark the broken message as consumed and move on
79
- mark_as_consumed(skippable_message)
79
+ mark_dispatched_to_dlq(skippable_message)
80
80
 
81
81
  return if revoked?
82
82
 
@@ -106,7 +106,8 @@ module Karafka
106
106
  # @param skippable_message [Karafka::Messages::Message] message we are skipping that also
107
107
  # should go to the dlq topic
108
108
  def dispatch_to_dlq(skippable_message)
109
- producer.produce_async(
109
+ producer.public_send(
110
+ topic.dead_letter_queue.dispatch_method,
110
111
  topic: topic.dead_letter_queue.topic,
111
112
  payload: skippable_message.raw_payload
112
113
  )
@@ -118,6 +119,20 @@ module Karafka
118
119
  message: skippable_message
119
120
  )
120
121
  end
122
+
123
+ # Marks message that went to DLQ (if applicable) based on the requested method
124
+ # @param skippable_message [Karafka::Messages::Message]
125
+ def mark_dispatched_to_dlq(skippable_message)
126
+ case topic.dead_letter_queue.marking_method
127
+ when :mark_as_consumed
128
+ mark_as_consumed(skippable_message)
129
+ when :mark_as_consumed!
130
+ mark_as_consumed!(skippable_message)
131
+ else
132
+ # This should never happen. Bug if encountered. Please report
133
+ raise Karafka::Errors::UnsupportedCaseError
134
+ end
135
+ end
121
136
  end
122
137
  end
123
138
  end
@@ -5,11 +5,14 @@ module Karafka
5
5
  # Abstraction layer around workers batch.
6
6
  class WorkersBatch
7
7
  include Enumerable
8
+ include Helpers::ConfigImporter.new(
9
+ concurrency: %i[concurrency]
10
+ )
8
11
 
9
12
  # @param jobs_queue [JobsQueue]
10
13
  # @return [WorkersBatch]
11
14
  def initialize(jobs_queue)
12
- @batch = Array.new(App.config.concurrency) { Processing::Worker.new(jobs_queue) }
15
+ @batch = Array.new(concurrency) { Processing::Worker.new(jobs_queue) }
13
16
  end
14
17
 
15
18
  # Iterates over available workers and yields each worker
@@ -14,6 +14,10 @@ module Karafka
14
14
  # end
15
15
  # end
16
16
  class Builder < Array
17
+ include Helpers::ConfigImporter.new(
18
+ default_group_id: %i[group_id]
19
+ )
20
+
17
21
  # Empty default per-topic config
18
22
  EMPTY_DEFAULTS = ->(_) {}.freeze
19
23
 
@@ -116,7 +120,7 @@ module Karafka
116
120
  **args,
117
121
  &block
118
122
  )
119
- consumer_group('app') do
123
+ consumer_group(default_group_id) do
120
124
  target.public_send(
121
125
  :subscription_group=,
122
126
  subscription_group_name.to_s,
@@ -132,7 +136,7 @@ module Karafka
132
136
  # @param topic_name [String, Symbol] name of a topic from which we want to consumer
133
137
  # @param block [Proc] proc we want to evaluate in the topic context
134
138
  def topic(topic_name, &block)
135
- consumer_group('app') do
139
+ consumer_group(default_group_id) do
136
140
  topic(topic_name, &block)
137
141
  end
138
142
  end
@@ -22,7 +22,8 @@ module Karafka
22
22
  # kafka and don't understand the concept of consumer groups.
23
23
  def initialize(name)
24
24
  @name = name.to_s
25
- @id = config.consumer_mapper.call(name)
25
+ # This used to be different when consumer mappers existed but now it is the same
26
+ @id = @name
26
27
  @topics = Topics.new([])
27
28
  # Initialize the subscription group so there's always a value for it, since even if not
28
29
  # defined directly, a subscription group will be created
@@ -17,6 +17,11 @@ module Karafka
17
17
  :transactional,
18
18
  # Strategy to apply (if strategies supported)
19
19
  :strategy,
20
+ # Should we use `#produce_sync` or `#produce_async`
21
+ :dispatch_method,
22
+ # Should we use `#mark_as_consumed` or `#mark_as_consumed!` (in flows that mark)
23
+ :marking_method,
24
+ # Initialize with kwargs
20
25
  keyword_init: true
21
26
  ) do
22
27
  alias_method :active?, :active
@@ -21,6 +21,14 @@ module Karafka
21
21
  required(:independent) { |val| [true, false].include?(val) }
22
22
  required(:max_retries) { |val| val.is_a?(Integer) && val >= 0 }
23
23
  required(:transactional) { |val| [true, false].include?(val) }
24
+
25
+ required(:dispatch_method) do |val|
26
+ %i[produce_async produce_sync].include?(val)
27
+ end
28
+
29
+ required(:marking_method) do |val|
30
+ %i[mark_as_consumed mark_as_consumed!].include?(val)
31
+ end
24
32
  end
25
33
 
26
34
  # Validate topic name only if dlq is active
@@ -18,19 +18,27 @@ module Karafka
18
18
  # in a retry flow to reset the errors counter
19
19
  # @param transactional [Boolean] if applicable, should transaction be used to move
20
20
  # given message to the dead-letter topic and mark it as consumed.
21
+ # @param dispatch_method [Symbol] `:produce_async` or `:produce_sync`. Describes
22
+ # whether dispatch on dlq should be sync or async (async by default)
23
+ # @param marking_method [Symbol] `:mark_as_consumed` or `:mark_as_consumed!`. Describes
24
+ # whether marking on DLQ should be async or sync (async by default)
21
25
  # @return [Config] defined config
22
26
  def dead_letter_queue(
23
27
  max_retries: DEFAULT_MAX_RETRIES,
24
28
  topic: nil,
25
29
  independent: false,
26
- transactional: true
30
+ transactional: true,
31
+ dispatch_method: :produce_async,
32
+ marking_method: :mark_as_consumed
27
33
  )
28
34
  @dead_letter_queue ||= Config.new(
29
35
  active: !topic.nil?,
30
36
  max_retries: max_retries,
31
37
  topic: topic,
32
38
  independent: independent,
33
- transactional: transactional
39
+ transactional: transactional,
40
+ dispatch_method: dispatch_method,
41
+ marking_method: marking_method
34
42
  )
35
43
  end
36
44
 
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Routing
5
+ module Features
6
+ class Deserializers < Base
7
+ # Config of this feature
8
+ Config = Struct.new(
9
+ :active,
10
+ :payload,
11
+ :key,
12
+ :headers,
13
+ keyword_init: true
14
+ ) { alias_method :active?, :active }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Routing
5
+ module Features
6
+ class Deserializers < Base
7
+ # This feature validation contracts
8
+ module Contracts
9
+ # Basic validation of the Kafka expected config details
10
+ class Topic < Karafka::Contracts::Base
11
+ configure do |config|
12
+ config.error_messages = YAML.safe_load(
13
+ File.read(
14
+ File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
15
+ )
16
+ ).fetch('en').fetch('validations').fetch('topic')
17
+ end
18
+
19
+ nested :deserializers do
20
+ # Always enabled
21
+ required(:active) { |val| val == true }
22
+ required(:payload) { |val| val.respond_to?(:call) }
23
+ required(:headers) { |val| val.respond_to?(:call) }
24
+ required(:key) { |val| val.respond_to?(:call) }
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Routing
5
+ module Features
6
+ # Deserializers for all the message details (payload, headers, key)
7
+ class Deserializers < Base
8
+ # Routing topic deserializers API. It allows to configure deserializers for various
9
+ # components of each message.
10
+ module Topic
11
+ # Allows for setting all the deserializers with standard defaults
12
+ # @param payload [Object] Deserializer for the message payload
13
+ # @param key [Object] deserializer for the message key
14
+ # @param headers [Object] deserializer for the message headers
15
+ def deserializers(
16
+ payload: ::Karafka::Deserializers::Payload.new,
17
+ key: ::Karafka::Deserializers::Key.new,
18
+ headers: ::Karafka::Deserializers::Headers.new
19
+ )
20
+ @deserializers ||= Config.new(
21
+ active: true,
22
+ payload: payload,
23
+ key: key,
24
+ headers: headers
25
+ )
26
+ end
27
+
28
+ # Supports pre 2.4 format where only payload deserializer could be defined. We do not
29
+ # retire this format because it is not bad when users do not do anything advanced with
30
+ # key or headers
31
+ # @param payload [Object] payload deserializer
32
+ def deserializer(payload)
33
+ deserializers(payload: payload)
34
+ end
35
+
36
+ # @return [Boolean] Deserializers are always active
37
+ def deserializers?
38
+ deserializers.active?
39
+ end
40
+
41
+ # @return [Hash] topic setup hash
42
+ def to_h
43
+ super.merge(
44
+ deserializers: deserializers.to_h
45
+ ).freeze
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Routing
5
+ module Features
6
+ # Namespace for feature allowing to configure deserializers for payload, key and headers
7
+ class Deserializers < Base
8
+ end
9
+ end
10
+ end
11
+ end
@@ -18,22 +18,17 @@ module Karafka
18
18
  instance_eval(&defaults) if defaults
19
19
  end
20
20
 
21
- # Ruby 2.7.0 to 2.7.2 do not have arg forwarding, so we fallback to the old way
22
- arg_forwarding = RUBY_VERSION < '3.0' ? '*args, &block' : '...'
23
-
24
- class_eval <<~RUBY, __FILE__, __LINE__ + 1
25
- # Translates the no "=" DSL of routing into elements assignments on target
26
- # @param method_name [Symbol] name of the missing method
27
- def method_missing(method_name, #{arg_forwarding})
28
- return super unless respond_to_missing?(method_name)
21
+ # Translates the no "=" DSL of routing into elements assignments on target
22
+ # @param method_name [Symbol] name of the missing method
23
+ def method_missing(method_name, ...)
24
+ return super unless respond_to_missing?(method_name)
29
25
 
30
- if @target.respond_to?(:"\#{method_name}=")
31
- @target.public_send(:"\#{method_name}=", #{arg_forwarding})
32
- else
33
- @target.public_send(method_name, #{arg_forwarding})
34
- end
26
+ if @target.respond_to?(:"#{method_name}=")
27
+ @target.public_send(:"#{method_name}=", ...)
28
+ else
29
+ @target.public_send(method_name, ...)
35
30
  end
36
- RUBY
31
+ end
37
32
 
38
33
  # Tells whether or not a given element exists on the target
39
34
  # @param method_name [Symbol] name of the missing method
@@ -23,7 +23,7 @@ module Karafka
23
23
  end
24
24
 
25
25
  # Finds the topic by name (in any consumer group) and if not present, will built a new
26
- # representation of the topic with the defaults and default deserializer.
26
+ # representation of the topic with the defaults and default deserializers.
27
27
  #
28
28
  # This is used in places where we may operate on topics that are not part of the routing
29
29
  # but we want to do something on them (display data, iterate over, etc)
@@ -33,7 +33,16 @@ module Karafka
33
33
  # @note Please note, that in case of a new topic, it will have a newly built consumer group
34
34
  # as well, that is not part of the routing.
35
35
  def find_or_initialize_by_name(name)
36
- find_by(name: name) || Topic.new(name, ConsumerGroup.new(name))
36
+ existing_topic = find_by(name: name)
37
+
38
+ return existing_topic if existing_topic
39
+
40
+ virtual_topic = Topic.new(name, ConsumerGroup.new(name))
41
+
42
+ Karafka::Routing::Proxy.new(
43
+ virtual_topic,
44
+ Karafka::App.config.internal.routing.builder.defaults
45
+ ).target
37
46
  end
38
47
 
39
48
  module_function :find_by
@@ -76,7 +76,8 @@ module Karafka
76
76
  activity_manager.active?(:subscription_groups, name)
77
77
  end
78
78
 
79
- # @return [Array<String>] names of topics to which we should subscribe.
79
+ # @return [false, Array<String>] names of topics to which we should subscribe or false when
80
+ # operating only on direct assignments
80
81
  #
81
82
  # @note Most of the time it should not include inactive topics but in case of pattern
82
83
  # matching the matcher topics become inactive down the road, hence we filter out so
@@ -85,6 +86,13 @@ module Karafka
85
86
  topics.select(&:active?).map(&:subscription_name)
86
87
  end
87
88
 
89
+ # @param _consumer [Karafka::Connection::Proxy]
90
+ # @return [false, Rdkafka::Consumer::TopicPartitionList] List of tpls for direct assignments
91
+ # or false for the normal mode
92
+ def assignments(_consumer)
93
+ false
94
+ end
95
+
88
96
  # @return [String] id of the subscription group
89
97
  # @note This is an alias for displaying in places where we print the stringified version.
90
98
  def to_s
@@ -18,7 +18,6 @@ module Karafka
18
18
  # Attributes we can inherit from the root unless they were defined on this level
19
19
  INHERITABLE_ATTRIBUTES = %i[
20
20
  kafka
21
- deserializer
22
21
  max_messages
23
22
  max_wait_time
24
23
  initial_offset
@@ -25,7 +25,7 @@ module Karafka
25
25
  # Register all the listeners so they can be started and managed
26
26
  @manager.register(listeners)
27
27
 
28
- workers.each(&:async_call)
28
+ workers.each_with_index { |worker, i| worker.async_call("karafka.worker##{i}") }
29
29
 
30
30
  # We aggregate threads here for a supervised shutdown process
31
31
  Karafka::Server.workers = workers
@@ -64,15 +64,9 @@ module Karafka
64
64
  setting :logger, default: ::Karafka::Instrumentation::Logger.new
65
65
  # option monitor [Instance] monitor that we will to use (defaults to Karafka::Monitor)
66
66
  setting :monitor, default: ::Karafka::Instrumentation::Monitor.new
67
- # Mapper used to remap consumer groups ids, so in case users migrate from other tools
68
- # or they need to maintain their own internal consumer group naming conventions, they
69
- # can easily do it, replacing the default client_id + consumer name pattern concept
70
- setting :consumer_mapper, default: Routing::ConsumerMapper.new
71
67
  # option [Boolean] should we reload consumers with each incoming batch thus effectively
72
68
  # supporting code reload (if someone reloads code) or should we keep the persistence
73
69
  setting :consumer_persistence, default: true
74
- # Default deserializer for converting incoming data into ruby objects
75
- setting :deserializer, default: Karafka::Serialization::Json::Deserializer.new
76
70
  # option [String] should we start with the earliest possible offset or latest
77
71
  # This will set the `auto.offset.reset` value unless present in the kafka scope
78
72
  setting :initial_offset, default: 'earliest'
@@ -100,6 +94,15 @@ module Karafka
100
94
  # Disabling this may be needed in scenarios where we do not have control over topics names
101
95
  # and/or we work with existing systems where we cannot change topics names.
102
96
  setting :strict_topics_namespacing, default: true
97
+ # option [String] default consumer group name for implicit routing
98
+ setting :group_id, default: 'app'
99
+
100
+ setting :oauth do
101
+ # option [false, #call] Listener for using oauth bearer. This listener will be able to
102
+ # get the client name to decide whether to use a single multi-client token refreshing
103
+ # or have separate tokens per instance.
104
+ setting :token_provider_listener, default: false
105
+ end
103
106
 
104
107
  # rdkafka default options
105
108
  # @see https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
@@ -137,11 +140,12 @@ module Karafka
137
140
  # involving a consumer instance
138
141
  'enable.auto.commit': false,
139
142
  # Make sure that topic metadata lookups do not create topics accidentally
140
- 'allow.auto.create.topics': false
143
+ 'allow.auto.create.topics': false,
144
+ # Do not store offsets automatically in admin in any way
145
+ 'enable.auto.offset.store': false
141
146
  }
142
147
 
143
- # option [String] default name for the admin consumer group. Please note, that this is a
144
- # subject to be remapped by the consumer mapper as any other consumer group in the routes
148
+ # option [String] default name for the admin consumer group.
145
149
  setting :group_id, default: 'karafka_admin'
146
150
 
147
151
  # option max_wait_time [Integer] We wait only for this amount of time before raising error
@@ -230,6 +234,14 @@ module Karafka
230
234
 
231
235
  # Settings that are altered by our client proxy layer
232
236
  setting :proxy do
237
+ # commit offsets request
238
+ setting :commit do
239
+ # How many times should we try to run this call before raising an error
240
+ setting :max_attempts, default: 3
241
+ # How long should we wait before next attempt in case of a failure
242
+ setting :wait_time, default: 1_000
243
+ end
244
+
233
245
  # Committed offsets for given CG query
234
246
  setting :committed do
235
247
  # timeout for this request. For busy or remote clusters, this should be high enough
@@ -259,6 +271,26 @@ module Karafka
259
271
  # How long should we wait before next attempt in case of a failure
260
272
  setting :wait_time, default: 1_000
261
273
  end
274
+
275
+ # Settings for lag request
276
+ setting :lag do
277
+ # timeout for this request. For busy or remote clusters, this should be high enough
278
+ setting :timeout, default: 10_000
279
+ # How many times should we try to run this call before raising an error
280
+ setting :max_attempts, default: 3
281
+ # How long should we wait before next attempt in case of a failure
282
+ setting :wait_time, default: 1_000
283
+ end
284
+
285
+ # Settings for metadata request
286
+ setting :metadata do
287
+ # timeout for this request. For busy or remote clusters, this should be high enough
288
+ setting :timeout, default: 10_000
289
+ # How many times should we try to run this call before raising an error
290
+ setting :max_attempts, default: 3
291
+ # How long should we wait before next attempt in case of a failure
292
+ setting :wait_time, default: 1_000
293
+ end
262
294
  end
263
295
  end
264
296
 
@@ -368,10 +400,19 @@ module Karafka
368
400
  # Sets up all the components that are based on the user configuration
369
401
  # @note At the moment it is only WaterDrop
370
402
  def configure_components
403
+ oauth_listener = config.oauth.token_provider_listener
404
+ # We need to subscribe the oauth listener here because we want it to be ready before
405
+ # any consumer/admin runs
406
+ Karafka::App.monitor.subscribe(oauth_listener) if oauth_listener
407
+
371
408
  config.producer ||= ::WaterDrop::Producer.new do |producer_config|
372
409
  # In some cases WaterDrop updates the config and we don't want our consumer config to
373
410
  # be polluted by those updates, that's why we copy
374
411
  producer_config.kafka = AttributesMap.producer(config.kafka.dup)
412
+ # We also propagate same listener to the default producer to make sure, that the
413
+ # listener for oauth is also automatically used by the producer. That way we don't
414
+ # have to configure it manually for the default producer
415
+ producer_config.oauth.token_provider_listener = oauth_listener
375
416
  producer_config.logger = config.logger
376
417
  end
377
418
  end
@@ -3,6 +3,11 @@
3
3
  module Karafka
4
4
  # App status monitor
5
5
  class Status
6
+ include Helpers::ConfigImporter.new(
7
+ monitor: %i[monitor],
8
+ conductor: %i[internal connection conductor]
9
+ )
10
+
6
11
  # Available states and their transitions.
7
12
  STATES = {
8
13
  initializing: :initialize!,
@@ -60,14 +65,8 @@ module Karafka
60
65
  # We skip as during this state we do not have yet a monitor
61
66
  return if initializing?
62
67
 
63
- # We do not set conductor in the initializer because this status object is created
64
- # before the configuration kicks in
65
- # We need to signal conductor on each state change as those may be relevant to
66
- # listeners operations
67
- @conductor ||= Karafka::App.config.internal.connection.conductor
68
- @conductor.signal
69
-
70
- Karafka.monitor.instrument("app.#{state}")
68
+ conductor.signal
69
+ monitor.instrument("app.#{state}", caller: self)
71
70
  end
72
71
  end
73
72
  RUBY