karafka 2.3.3 → 2.4.0.beta2

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 (127) 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 +59 -0
  5. data/Gemfile +6 -3
  6. data/Gemfile.lock +29 -27
  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 +211 -90
  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/constraints.rb +3 -3
  33. data/lib/karafka/contracts/config.rb +14 -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/kubernetes/liveness_listener.rb +20 -0
  46. data/lib/karafka/messages/batch_metadata.rb +1 -1
  47. data/lib/karafka/messages/builders/batch_metadata.rb +1 -1
  48. data/lib/karafka/messages/builders/message.rb +10 -6
  49. data/lib/karafka/messages/message.rb +2 -1
  50. data/lib/karafka/messages/metadata.rb +20 -4
  51. data/lib/karafka/messages/parser.rb +1 -1
  52. data/lib/karafka/pro/base_consumer.rb +12 -23
  53. data/lib/karafka/pro/encryption/cipher.rb +7 -3
  54. data/lib/karafka/pro/encryption/contracts/config.rb +1 -0
  55. data/lib/karafka/pro/encryption/errors.rb +4 -1
  56. data/lib/karafka/pro/encryption/messages/middleware.rb +13 -11
  57. data/lib/karafka/pro/encryption/messages/parser.rb +22 -20
  58. data/lib/karafka/pro/encryption/setup/config.rb +5 -0
  59. data/lib/karafka/pro/iterator/expander.rb +2 -1
  60. data/lib/karafka/pro/iterator/tpl_builder.rb +38 -0
  61. data/lib/karafka/pro/iterator.rb +28 -2
  62. data/lib/karafka/pro/loader.rb +3 -0
  63. data/lib/karafka/pro/processing/coordinator.rb +15 -2
  64. data/lib/karafka/pro/processing/expansions_selector.rb +2 -0
  65. data/lib/karafka/pro/processing/jobs_queue.rb +122 -5
  66. data/lib/karafka/pro/processing/periodic_job/consumer.rb +67 -0
  67. data/lib/karafka/pro/processing/piping/consumer.rb +126 -0
  68. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +1 -1
  69. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +1 -1
  70. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom.rb +1 -1
  71. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +1 -1
  72. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +1 -1
  73. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +1 -1
  74. data/lib/karafka/pro/processing/strategies/aj/dlq_mom.rb +1 -1
  75. data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +1 -1
  76. data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +2 -0
  77. data/lib/karafka/pro/processing/strategies/default.rb +5 -1
  78. data/lib/karafka/pro/processing/strategies/dlq/default.rb +21 -5
  79. data/lib/karafka/pro/processing/strategies/lrj/default.rb +2 -0
  80. data/lib/karafka/pro/processing/strategies/lrj/mom.rb +2 -0
  81. data/lib/karafka/pro/processing/subscription_groups_coordinator.rb +52 -0
  82. data/lib/karafka/pro/routing/features/direct_assignments/config.rb +27 -0
  83. data/lib/karafka/pro/routing/features/direct_assignments/contracts/consumer_group.rb +53 -0
  84. data/lib/karafka/pro/routing/features/direct_assignments/contracts/topic.rb +108 -0
  85. data/lib/karafka/pro/routing/features/direct_assignments/subscription_group.rb +77 -0
  86. data/lib/karafka/pro/routing/features/direct_assignments/topic.rb +69 -0
  87. data/lib/karafka/pro/routing/features/direct_assignments.rb +25 -0
  88. data/lib/karafka/pro/routing/features/patterns/builder.rb +1 -1
  89. data/lib/karafka/pro/routing/features/swarm/contracts/routing.rb +76 -0
  90. data/lib/karafka/pro/routing/features/swarm/contracts/topic.rb +16 -5
  91. data/lib/karafka/pro/routing/features/swarm/topic.rb +25 -2
  92. data/lib/karafka/pro/routing/features/swarm.rb +11 -0
  93. data/lib/karafka/pro/swarm/liveness_listener.rb +20 -0
  94. data/lib/karafka/processing/coordinator.rb +17 -8
  95. data/lib/karafka/processing/coordinators_buffer.rb +5 -2
  96. data/lib/karafka/processing/executor.rb +6 -2
  97. data/lib/karafka/processing/executors_buffer.rb +5 -2
  98. data/lib/karafka/processing/jobs_queue.rb +9 -4
  99. data/lib/karafka/processing/strategies/aj_dlq_mom.rb +1 -1
  100. data/lib/karafka/processing/strategies/default.rb +7 -1
  101. data/lib/karafka/processing/strategies/dlq.rb +17 -2
  102. data/lib/karafka/processing/workers_batch.rb +4 -1
  103. data/lib/karafka/routing/builder.rb +6 -2
  104. data/lib/karafka/routing/consumer_group.rb +2 -1
  105. data/lib/karafka/routing/features/dead_letter_queue/config.rb +5 -0
  106. data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +8 -0
  107. data/lib/karafka/routing/features/dead_letter_queue/topic.rb +10 -2
  108. data/lib/karafka/routing/features/deserializers/config.rb +18 -0
  109. data/lib/karafka/routing/features/deserializers/contracts/topic.rb +31 -0
  110. data/lib/karafka/routing/features/deserializers/topic.rb +51 -0
  111. data/lib/karafka/routing/features/deserializers.rb +11 -0
  112. data/lib/karafka/routing/proxy.rb +9 -14
  113. data/lib/karafka/routing/router.rb +11 -2
  114. data/lib/karafka/routing/subscription_group.rb +9 -1
  115. data/lib/karafka/routing/topic.rb +0 -1
  116. data/lib/karafka/runner.rb +1 -1
  117. data/lib/karafka/setup/config.rb +50 -9
  118. data/lib/karafka/status.rb +7 -8
  119. data/lib/karafka/swarm/supervisor.rb +16 -2
  120. data/lib/karafka/templates/karafka.rb.erb +28 -1
  121. data/lib/karafka/version.rb +1 -1
  122. data.tar.gz.sig +0 -0
  123. metadata +38 -12
  124. metadata.gz.sig +0 -0
  125. data/lib/karafka/routing/consumer_mapper.rb +0 -23
  126. data/lib/karafka/serialization/json/deserializer.rb +0 -19
  127. data/lib/karafka/time_trackers/partition_usage.rb +0 -56
@@ -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
@@ -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