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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci.yml +12 -38
- data/CHANGELOG.md +59 -0
- data/Gemfile +6 -3
- data/Gemfile.lock +29 -27
- data/bin/integrations +1 -1
- data/config/locales/errors.yml +21 -2
- data/config/locales/pro_errors.yml +16 -1
- data/karafka.gemspec +4 -2
- data/lib/active_job/queue_adapters/karafka_adapter.rb +2 -0
- data/lib/karafka/admin/configs/config.rb +81 -0
- data/lib/karafka/admin/configs/resource.rb +88 -0
- data/lib/karafka/admin/configs.rb +103 -0
- data/lib/karafka/admin.rb +211 -90
- data/lib/karafka/base_consumer.rb +2 -2
- data/lib/karafka/cli/info.rb +9 -7
- data/lib/karafka/cli/server.rb +7 -7
- data/lib/karafka/cli/topics/align.rb +109 -0
- data/lib/karafka/cli/topics/base.rb +66 -0
- data/lib/karafka/cli/topics/create.rb +35 -0
- data/lib/karafka/cli/topics/delete.rb +30 -0
- data/lib/karafka/cli/topics/migrate.rb +31 -0
- data/lib/karafka/cli/topics/plan.rb +169 -0
- data/lib/karafka/cli/topics/repartition.rb +41 -0
- data/lib/karafka/cli/topics/reset.rb +18 -0
- data/lib/karafka/cli/topics.rb +13 -123
- data/lib/karafka/connection/client.rb +55 -37
- data/lib/karafka/connection/listener.rb +22 -17
- data/lib/karafka/connection/proxy.rb +93 -4
- data/lib/karafka/connection/status.rb +14 -2
- data/lib/karafka/constraints.rb +3 -3
- data/lib/karafka/contracts/config.rb +14 -1
- data/lib/karafka/contracts/topic.rb +1 -1
- data/lib/karafka/deserializers/headers.rb +15 -0
- data/lib/karafka/deserializers/key.rb +15 -0
- data/lib/karafka/deserializers/payload.rb +16 -0
- data/lib/karafka/embedded.rb +2 -0
- data/lib/karafka/helpers/async.rb +5 -2
- data/lib/karafka/helpers/colorize.rb +6 -0
- data/lib/karafka/instrumentation/callbacks/oauthbearer_token_refresh.rb +29 -0
- data/lib/karafka/instrumentation/logger_listener.rb +23 -3
- data/lib/karafka/instrumentation/notifications.rb +10 -0
- data/lib/karafka/instrumentation/vendors/appsignal/client.rb +16 -2
- data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +20 -0
- data/lib/karafka/messages/batch_metadata.rb +1 -1
- data/lib/karafka/messages/builders/batch_metadata.rb +1 -1
- data/lib/karafka/messages/builders/message.rb +10 -6
- data/lib/karafka/messages/message.rb +2 -1
- data/lib/karafka/messages/metadata.rb +20 -4
- data/lib/karafka/messages/parser.rb +1 -1
- data/lib/karafka/pro/base_consumer.rb +12 -23
- data/lib/karafka/pro/encryption/cipher.rb +7 -3
- data/lib/karafka/pro/encryption/contracts/config.rb +1 -0
- data/lib/karafka/pro/encryption/errors.rb +4 -1
- data/lib/karafka/pro/encryption/messages/middleware.rb +13 -11
- data/lib/karafka/pro/encryption/messages/parser.rb +22 -20
- data/lib/karafka/pro/encryption/setup/config.rb +5 -0
- data/lib/karafka/pro/iterator/expander.rb +2 -1
- data/lib/karafka/pro/iterator/tpl_builder.rb +38 -0
- data/lib/karafka/pro/iterator.rb +28 -2
- data/lib/karafka/pro/loader.rb +3 -0
- data/lib/karafka/pro/processing/coordinator.rb +15 -2
- data/lib/karafka/pro/processing/expansions_selector.rb +2 -0
- data/lib/karafka/pro/processing/jobs_queue.rb +122 -5
- data/lib/karafka/pro/processing/periodic_job/consumer.rb +67 -0
- data/lib/karafka/pro/processing/piping/consumer.rb +126 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +2 -0
- data/lib/karafka/pro/processing/strategies/default.rb +5 -1
- data/lib/karafka/pro/processing/strategies/dlq/default.rb +21 -5
- data/lib/karafka/pro/processing/strategies/lrj/default.rb +2 -0
- data/lib/karafka/pro/processing/strategies/lrj/mom.rb +2 -0
- data/lib/karafka/pro/processing/subscription_groups_coordinator.rb +52 -0
- data/lib/karafka/pro/routing/features/direct_assignments/config.rb +27 -0
- data/lib/karafka/pro/routing/features/direct_assignments/contracts/consumer_group.rb +53 -0
- data/lib/karafka/pro/routing/features/direct_assignments/contracts/topic.rb +108 -0
- data/lib/karafka/pro/routing/features/direct_assignments/subscription_group.rb +77 -0
- data/lib/karafka/pro/routing/features/direct_assignments/topic.rb +69 -0
- data/lib/karafka/pro/routing/features/direct_assignments.rb +25 -0
- data/lib/karafka/pro/routing/features/patterns/builder.rb +1 -1
- data/lib/karafka/pro/routing/features/swarm/contracts/routing.rb +76 -0
- data/lib/karafka/pro/routing/features/swarm/contracts/topic.rb +16 -5
- data/lib/karafka/pro/routing/features/swarm/topic.rb +25 -2
- data/lib/karafka/pro/routing/features/swarm.rb +11 -0
- data/lib/karafka/pro/swarm/liveness_listener.rb +20 -0
- data/lib/karafka/processing/coordinator.rb +17 -8
- data/lib/karafka/processing/coordinators_buffer.rb +5 -2
- data/lib/karafka/processing/executor.rb +6 -2
- data/lib/karafka/processing/executors_buffer.rb +5 -2
- data/lib/karafka/processing/jobs_queue.rb +9 -4
- data/lib/karafka/processing/strategies/aj_dlq_mom.rb +1 -1
- data/lib/karafka/processing/strategies/default.rb +7 -1
- data/lib/karafka/processing/strategies/dlq.rb +17 -2
- data/lib/karafka/processing/workers_batch.rb +4 -1
- data/lib/karafka/routing/builder.rb +6 -2
- data/lib/karafka/routing/consumer_group.rb +2 -1
- data/lib/karafka/routing/features/dead_letter_queue/config.rb +5 -0
- data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +8 -0
- data/lib/karafka/routing/features/dead_letter_queue/topic.rb +10 -2
- data/lib/karafka/routing/features/deserializers/config.rb +18 -0
- data/lib/karafka/routing/features/deserializers/contracts/topic.rb +31 -0
- data/lib/karafka/routing/features/deserializers/topic.rb +51 -0
- data/lib/karafka/routing/features/deserializers.rb +11 -0
- data/lib/karafka/routing/proxy.rb +9 -14
- data/lib/karafka/routing/router.rb +11 -2
- data/lib/karafka/routing/subscription_group.rb +9 -1
- data/lib/karafka/routing/topic.rb +0 -1
- data/lib/karafka/runner.rb +1 -1
- data/lib/karafka/setup/config.rb +50 -9
- data/lib/karafka/status.rb +7 -8
- data/lib/karafka/swarm/supervisor.rb +16 -2
- data/lib/karafka/templates/karafka.rb.erb +28 -1
- data/lib/karafka/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +38 -12
- metadata.gz.sig +0 -0
- data/lib/karafka/routing/consumer_mapper.rb +0 -23
- data/lib/karafka/serialization/json/deserializer.rb +0 -19
- 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 =
|
|
148
|
-
expansions =
|
|
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] ||=
|
|
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] <
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
@@ -18,22 +18,17 @@ module Karafka
|
|
|
18
18
|
instance_eval(&defaults) if defaults
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
|
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)
|
|
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
|
data/lib/karafka/runner.rb
CHANGED
|
@@ -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.
|
|
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
|
data/lib/karafka/setup/config.rb
CHANGED
|
@@ -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.
|
|
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
|
data/lib/karafka/status.rb
CHANGED
|
@@ -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
|
-
|
|
64
|
-
|
|
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
|