karafka 2.5.2 → 2.5.4.rc1
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
- data/CHANGELOG.md +16 -0
- data/config/locales/errors.yml +14 -0
- data/karafka.gemspec +15 -4
- data/lib/active_job/queue_adapters/karafka_adapter.rb +2 -2
- data/lib/karafka/active_job/consumer.rb +2 -2
- data/lib/karafka/active_job/current_attributes.rb +2 -2
- data/lib/karafka/active_job/deserializer.rb +1 -1
- data/lib/karafka/active_job/dispatcher.rb +2 -2
- data/lib/karafka/admin/configs/resource.rb +7 -1
- data/lib/karafka/admin/consumer_groups.rb +6 -8
- data/lib/karafka/admin/contracts/replication.rb +149 -0
- data/lib/karafka/admin/replication.rb +462 -0
- data/lib/karafka/admin/topics.rb +5 -4
- data/lib/karafka/admin.rb +57 -12
- data/lib/karafka/app.rb +3 -3
- data/lib/karafka/base_consumer.rb +1 -1
- data/lib/karafka/cli/base.rb +1 -1
- data/lib/karafka/cli/console.rb +1 -1
- data/lib/karafka/cli/contracts/server.rb +1 -1
- data/lib/karafka/cli/help.rb +1 -1
- data/lib/karafka/cli/install.rb +2 -1
- data/lib/karafka/cli/server.rb +1 -1
- data/lib/karafka/cli/swarm.rb +1 -1
- data/lib/karafka/connection/client.rb +19 -18
- data/lib/karafka/connection/manager.rb +1 -0
- data/lib/karafka/connection/proxy.rb +1 -1
- data/lib/karafka/connection/rebalance_manager.rb +1 -1
- data/lib/karafka/connection/status.rb +1 -0
- data/lib/karafka/constraints.rb +1 -1
- data/lib/karafka/contracts/base.rb +1 -1
- data/lib/karafka/deserializers/payload.rb +1 -1
- data/lib/karafka/helpers/async.rb +1 -1
- data/lib/karafka/helpers/config_importer.rb +3 -3
- data/lib/karafka/helpers/multi_delegator.rb +3 -0
- data/lib/karafka/instrumentation/assignments_tracker.rb +2 -1
- data/lib/karafka/instrumentation/callbacks/error.rb +2 -2
- data/lib/karafka/instrumentation/callbacks/statistics.rb +3 -3
- data/lib/karafka/instrumentation/logger.rb +6 -6
- data/lib/karafka/instrumentation/logger_listener.rb +0 -2
- data/lib/karafka/instrumentation/monitor.rb +2 -2
- data/lib/karafka/instrumentation/vendors/appsignal/base.rb +1 -1
- data/lib/karafka/instrumentation/vendors/appsignal/metrics_listener.rb +4 -0
- data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +32 -16
- data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +2 -2
- data/lib/karafka/instrumentation/vendors/kubernetes/base_listener.rb +1 -1
- data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +3 -15
- data/lib/karafka/licenser.rb +1 -1
- data/lib/karafka/messages/builders/batch_metadata.rb +1 -1
- data/lib/karafka/messages/messages.rb +32 -0
- data/lib/karafka/pro/active_job/consumer.rb +2 -2
- data/lib/karafka/pro/active_job/dispatcher.rb +3 -3
- data/lib/karafka/pro/cleaner/messages/messages.rb +1 -1
- data/lib/karafka/pro/cleaner.rb +3 -3
- data/lib/karafka/pro/cli/contracts/server.rb +1 -1
- data/lib/karafka/pro/cli/parallel_segments/base.rb +4 -3
- data/lib/karafka/pro/cli/parallel_segments/collapse.rb +1 -1
- data/lib/karafka/pro/cli/parallel_segments/distribute.rb +1 -1
- data/lib/karafka/pro/cli/parallel_segments.rb +1 -1
- data/lib/karafka/pro/connection/manager.rb +1 -2
- data/lib/karafka/pro/connection/multiplexing/listener.rb +1 -0
- data/lib/karafka/pro/contracts/base.rb +1 -1
- data/lib/karafka/pro/encryption/cipher.rb +3 -2
- data/lib/karafka/pro/encryption/contracts/config.rb +1 -1
- data/lib/karafka/pro/encryption/messages/parser.rb +1 -1
- data/lib/karafka/pro/encryption/setup/config.rb +1 -1
- data/lib/karafka/pro/iterator/tpl_builder.rb +1 -1
- data/lib/karafka/pro/iterator.rb +1 -1
- data/lib/karafka/pro/loader.rb +1 -1
- data/lib/karafka/pro/processing/coordinator.rb +1 -1
- data/lib/karafka/pro/processing/filters/base.rb +1 -0
- data/lib/karafka/pro/processing/filters/delayer.rb +1 -1
- data/lib/karafka/pro/processing/filters/expirer.rb +1 -1
- data/lib/karafka/pro/processing/filters/inline_insights_delayer.rb +1 -1
- data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +1 -1
- data/lib/karafka/pro/processing/jobs/eofed_non_blocking.rb +1 -1
- data/lib/karafka/pro/processing/jobs/periodic.rb +1 -1
- data/lib/karafka/pro/processing/jobs/revoked_non_blocking.rb +1 -1
- data/lib/karafka/pro/processing/jobs_builder.rb +1 -1
- data/lib/karafka/pro/processing/jobs_queue.rb +0 -2
- data/lib/karafka/pro/processing/offset_metadata/fetcher.rb +1 -0
- data/lib/karafka/pro/processing/partitioner.rb +1 -1
- data/lib/karafka/pro/processing/strategies/base.rb +1 -1
- data/lib/karafka/pro/processing/strategies/default.rb +2 -2
- data/lib/karafka/pro/processing/strategies/dlq/default.rb +1 -1
- data/lib/karafka/pro/processing/strategies/vp/default.rb +1 -1
- data/lib/karafka/pro/processing/strategy_selector.rb +1 -0
- data/lib/karafka/pro/processing/virtual_partitions/distributors/balanced.rb +4 -2
- data/lib/karafka/pro/processing/virtual_partitions/distributors/consistent.rb +4 -2
- data/lib/karafka/pro/recurring_tasks/consumer.rb +3 -2
- data/lib/karafka/pro/recurring_tasks/contracts/config.rb +2 -2
- data/lib/karafka/pro/recurring_tasks/contracts/task.rb +1 -1
- data/lib/karafka/pro/recurring_tasks/deserializer.rb +1 -1
- data/lib/karafka/pro/recurring_tasks/dispatcher.rb +1 -1
- data/lib/karafka/pro/recurring_tasks/executor.rb +2 -1
- data/lib/karafka/pro/recurring_tasks/schedule.rb +5 -2
- data/lib/karafka/pro/recurring_tasks/serializer.rb +6 -5
- data/lib/karafka/pro/recurring_tasks/setup/config.rb +2 -2
- data/lib/karafka/pro/recurring_tasks/task.rb +1 -1
- data/lib/karafka/pro/routing/features/dead_letter_queue/topic.rb +3 -0
- data/lib/karafka/pro/routing/features/multiplexing/subscription_groups_builder.rb +1 -1
- data/lib/karafka/pro/routing/features/multiplexing.rb +5 -5
- data/lib/karafka/pro/routing/features/offset_metadata.rb +4 -4
- data/lib/karafka/pro/routing/features/parallel_segments/builder.rb +1 -1
- data/lib/karafka/pro/routing/features/patterns/patterns.rb +1 -1
- data/lib/karafka/pro/routing/features/periodic_job/topic.rb +1 -1
- data/lib/karafka/pro/routing/features/recurring_tasks/builder.rb +1 -1
- data/lib/karafka/pro/routing/features/swarm.rb +1 -1
- data/lib/karafka/pro/routing/features/throttling/topic.rb +3 -1
- data/lib/karafka/pro/scheduled_messages/consumer.rb +1 -1
- data/lib/karafka/pro/scheduled_messages/contracts/config.rb +2 -2
- data/lib/karafka/pro/scheduled_messages/contracts/message.rb +1 -1
- data/lib/karafka/pro/scheduled_messages/daily_buffer.rb +3 -2
- data/lib/karafka/pro/scheduled_messages/day.rb +1 -0
- data/lib/karafka/pro/scheduled_messages/deserializers/headers.rb +1 -1
- data/lib/karafka/pro/scheduled_messages/deserializers/payload.rb +1 -1
- data/lib/karafka/pro/scheduled_messages/max_epoch.rb +1 -0
- data/lib/karafka/pro/scheduled_messages/proxy.rb +1 -1
- data/lib/karafka/pro/scheduled_messages/serializer.rb +3 -3
- data/lib/karafka/pro/scheduled_messages/setup/config.rb +2 -2
- data/lib/karafka/pro/scheduled_messages/state.rb +1 -0
- data/lib/karafka/pro/scheduled_messages/tracker.rb +1 -0
- data/lib/karafka/process.rb +4 -4
- data/lib/karafka/processing/executor.rb +1 -1
- data/lib/karafka/processing/inline_insights/tracker.rb +1 -0
- data/lib/karafka/processing/jobs_queue.rb +1 -1
- data/lib/karafka/processing/result.rb +1 -0
- data/lib/karafka/processing/strategies/dlq.rb +1 -1
- data/lib/karafka/processing/strategy_selector.rb +1 -0
- data/lib/karafka/routing/activity_manager.rb +1 -0
- data/lib/karafka/routing/builder.rb +3 -1
- data/lib/karafka/routing/consumer_group.rb +19 -1
- data/lib/karafka/routing/contracts/consumer_group.rb +3 -2
- data/lib/karafka/routing/contracts/topic.rb +5 -2
- data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +1 -1
- data/lib/karafka/routing/features/declaratives/topic.rb +5 -2
- data/lib/karafka/routing/features/deserializers/topic.rb +3 -3
- data/lib/karafka/routing/features/inline_insights.rb +5 -5
- data/lib/karafka/routing/router.rb +1 -1
- data/lib/karafka/routing/subscription_group.rb +2 -2
- data/lib/karafka/routing/subscription_groups_builder.rb +18 -2
- data/lib/karafka/routing/topic.rb +3 -3
- data/lib/karafka/server.rb +1 -1
- data/lib/karafka/setup/attributes_map.rb +4 -2
- data/lib/karafka/setup/config.rb +21 -10
- data/lib/karafka/setup/config_proxy.rb +209 -0
- data/lib/karafka/setup/contracts/config.rb +1 -1
- data/lib/karafka/swarm/liveness_listener.rb +1 -0
- data/lib/karafka/swarm/manager.rb +7 -6
- data/lib/karafka/swarm/node.rb +1 -1
- data/lib/karafka/swarm/supervisor.rb +1 -0
- data/lib/karafka/time_trackers/base.rb +1 -1
- data/lib/karafka/version.rb +1 -1
- data/lib/karafka.rb +2 -3
- metadata +8 -65
- data/.coditsu/ci.yml +0 -3
- data/.console_irbrc +0 -11
- data/.github/CODEOWNERS +0 -3
- data/.github/FUNDING.yml +0 -1
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -43
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- data/.github/workflows/ci_linux_ubuntu_x86_64_gnu.yml +0 -278
- data/.github/workflows/ci_macos_arm64.yml +0 -151
- data/.github/workflows/push.yml +0 -35
- data/.github/workflows/trigger-wiki-refresh.yml +0 -30
- data/.github/workflows/verify-action-pins.yml +0 -16
- data/.gitignore +0 -69
- data/.rspec +0 -7
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/CODE_OF_CONDUCT.md +0 -46
- data/CONTRIBUTING.md +0 -32
- data/Gemfile +0 -28
- data/Gemfile.lock +0 -173
- data/Rakefile +0 -4
- data/SECURITY.md +0 -23
- data/bin/benchmarks +0 -99
- data/bin/clean_kafka +0 -43
- data/bin/create_token +0 -22
- data/bin/integrations +0 -341
- data/bin/record_rss +0 -50
- data/bin/rspecs +0 -26
- data/bin/scenario +0 -29
- data/bin/stress_many +0 -13
- data/bin/stress_one +0 -13
- data/bin/verify_kafka_warnings +0 -36
- data/bin/verify_license_integrity +0 -37
- data/bin/verify_topics_naming +0 -27
- data/bin/wait_for_kafka +0 -24
- data/docker-compose.yml +0 -25
- data/examples/payloads/avro/.gitkeep +0 -0
- data/examples/payloads/json/sample_set_01/enrollment_event.json +0 -579
- data/examples/payloads/json/sample_set_01/ingestion_event.json +0 -30
- data/examples/payloads/json/sample_set_01/transaction_event.json +0 -17
- data/examples/payloads/json/sample_set_01/user_event.json +0 -11
- data/examples/payloads/json/sample_set_02/download.json +0 -191
- data/examples/payloads/json/sample_set_03/event_type_1.json +0 -18
- data/examples/payloads/json/sample_set_03/event_type_2.json +0 -263
- data/examples/payloads/json/sample_set_03/event_type_3.json +0 -41
- data/log/.gitkeep +0 -0
- data/renovate.json +0 -21
|
@@ -24,22 +24,38 @@ module Karafka
|
|
|
24
24
|
|
|
25
25
|
private_constant :DISTRIBUTION_KEYS
|
|
26
26
|
|
|
27
|
+
# Initializes the subscription groups builder
|
|
27
28
|
def initialize
|
|
28
29
|
@position = -1
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
# @param topics [Karafka::Routing::Topics] all the topics based on which we want to build
|
|
32
33
|
# subscription groups
|
|
34
|
+
# @param base_position [Integer, nil] optional starting position for subscription groups.
|
|
35
|
+
# When provided, positions will start from this value instead of continuing from the
|
|
36
|
+
# global counter. This is used when rebuilding subscription groups to maintain stable
|
|
37
|
+
# positions for static group membership.
|
|
33
38
|
# @return [Array<SubscriptionGroup>] all subscription groups we need in separate threads
|
|
34
|
-
def call(topics)
|
|
39
|
+
def call(topics, base_position: nil)
|
|
40
|
+
# If base_position is provided, use it for stable rebuilding (consumer group reopening).
|
|
41
|
+
# Otherwise continue from global counter for new subscription groups.
|
|
42
|
+
# We subtract 1 from base_position because position is incremented in the map block below.
|
|
43
|
+
# This ensures the first subscription group gets the correct base_position value.
|
|
44
|
+
use_base = !base_position.nil?
|
|
45
|
+
position = use_base ? base_position - 1 : @position
|
|
46
|
+
|
|
35
47
|
topics
|
|
36
48
|
.map { |topic| [checksum(topic), topic] }
|
|
37
49
|
.group_by(&:first)
|
|
38
50
|
.values
|
|
39
51
|
.map { |value| value.map(&:last) }
|
|
40
52
|
.flat_map { |value| expand(value) }
|
|
41
|
-
.map { |grouped_topics| SubscriptionGroup.new(
|
|
53
|
+
.map { |grouped_topics| SubscriptionGroup.new(position += 1, grouped_topics) }
|
|
42
54
|
.tap do |subscription_groups|
|
|
55
|
+
# Always ensure global counter is at least as high as the highest position used.
|
|
56
|
+
# This prevents position collisions when new consumer groups are created after
|
|
57
|
+
# existing ones are rebuilt with base_position.
|
|
58
|
+
@position = position if position > @position
|
|
43
59
|
subscription_groups.each do |subscription_group|
|
|
44
60
|
subscription_group.topics.each do |topic|
|
|
45
61
|
topic.subscription_group = subscription_group
|
|
@@ -29,7 +29,7 @@ module Karafka
|
|
|
29
29
|
|
|
30
30
|
private_constant :INHERITABLE_ATTRIBUTES
|
|
31
31
|
|
|
32
|
-
# @param [String, Symbol] name of a topic on which we want to listen
|
|
32
|
+
# @param name [String, Symbol] name of a topic on which we want to listen
|
|
33
33
|
# @param consumer_group [Karafka::Routing::ConsumerGroup] owning consumer group of this topic
|
|
34
34
|
def initialize(name, consumer_group)
|
|
35
35
|
@name = name.to_s
|
|
@@ -92,7 +92,7 @@ module Karafka
|
|
|
92
92
|
# consumer class is defined with a name. It won't support code reload for anonymous
|
|
93
93
|
# consumer classes, but this is an edge case
|
|
94
94
|
begin
|
|
95
|
-
|
|
95
|
+
Object.const_get(@consumer.to_s)
|
|
96
96
|
rescue NameError
|
|
97
97
|
# It will only fail if the in case of anonymous classes
|
|
98
98
|
@consumer
|
|
@@ -139,7 +139,7 @@ module Karafka
|
|
|
139
139
|
[attribute, public_send(attribute)]
|
|
140
140
|
end
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
map.to_h.merge!(
|
|
143
143
|
id: id,
|
|
144
144
|
name: name,
|
|
145
145
|
active: active?,
|
data/lib/karafka/server.rb
CHANGED
|
@@ -174,7 +174,7 @@ module Karafka
|
|
|
174
174
|
# This ensures that if users have configured the default pool, it is closed correctly
|
|
175
175
|
#
|
|
176
176
|
# Custom pools need to be closed by users themselves
|
|
177
|
-
|
|
177
|
+
WaterDrop::ConnectionPool.close
|
|
178
178
|
|
|
179
179
|
Karafka::App.terminate!
|
|
180
180
|
end
|
|
@@ -122,6 +122,7 @@ module Karafka
|
|
|
122
122
|
sasl.oauthbearer.config
|
|
123
123
|
sasl.oauthbearer.extensions
|
|
124
124
|
sasl.oauthbearer.grant.type
|
|
125
|
+
sasl.oauthbearer.metadata.authentication.type
|
|
125
126
|
sasl.oauthbearer.method
|
|
126
127
|
sasl.oauthbearer.scope
|
|
127
128
|
sasl.oauthbearer.token.endpoint.url
|
|
@@ -279,6 +280,7 @@ module Karafka
|
|
|
279
280
|
sasl.oauthbearer.config
|
|
280
281
|
sasl.oauthbearer.extensions
|
|
281
282
|
sasl.oauthbearer.grant.type
|
|
283
|
+
sasl.oauthbearer.metadata.authentication.type
|
|
282
284
|
sasl.oauthbearer.method
|
|
283
285
|
sasl.oauthbearer.scope
|
|
284
286
|
sasl.oauthbearer.token.endpoint.url
|
|
@@ -357,7 +359,7 @@ module Karafka
|
|
|
357
359
|
end
|
|
358
360
|
|
|
359
361
|
# @private
|
|
360
|
-
# @return [Hash
|
|
362
|
+
# @return [Hash{Symbol => Array<Symbol>}] hash with consumer and producer attributes list
|
|
361
363
|
# that is sorted.
|
|
362
364
|
# @note This method should not be used directly. It is only used to generate appropriate
|
|
363
365
|
# options list in case it would change
|
|
@@ -367,7 +369,7 @@ module Karafka
|
|
|
367
369
|
|
|
368
370
|
attributes = { consumer: Set.new, producer: Set.new }
|
|
369
371
|
|
|
370
|
-
|
|
372
|
+
URI.parse(SOURCE).open.readlines.each do |line|
|
|
371
373
|
next unless line.include?('|')
|
|
372
374
|
|
|
373
375
|
attribute, attribute_type = line.split('|').map(&:strip)
|
data/lib/karafka/setup/config.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Karafka
|
|
|
12
12
|
# enough and will still keep the code simple
|
|
13
13
|
# @see Karafka::Setup::Configurators::Base for more details about configurators api
|
|
14
14
|
class Config
|
|
15
|
-
extend
|
|
15
|
+
extend Karafka::Core::Configurable
|
|
16
16
|
|
|
17
17
|
# Available settings
|
|
18
18
|
|
|
@@ -33,9 +33,9 @@ module Karafka
|
|
|
33
33
|
# Used only for logging.
|
|
34
34
|
setting :client_id, default: 'karafka'
|
|
35
35
|
# option logger [Instance] logger that we want to use
|
|
36
|
-
setting :logger, default:
|
|
36
|
+
setting :logger, default: Karafka::Instrumentation::Logger.new
|
|
37
37
|
# option monitor [Instance] monitor that we will to use (defaults to Karafka::Monitor)
|
|
38
|
-
setting :monitor, default:
|
|
38
|
+
setting :monitor, default: Karafka::Instrumentation::Monitor.new
|
|
39
39
|
# option [Boolean] should we reload consumers with each incoming batch thus effectively
|
|
40
40
|
# supporting code reload (if someone reloads code) or should we keep the persistence
|
|
41
41
|
setting :consumer_persistence, default: true
|
|
@@ -211,7 +211,7 @@ module Karafka
|
|
|
211
211
|
setting :cli do
|
|
212
212
|
# option contract [Object] cli setup validation contract (in the context of options and
|
|
213
213
|
# topics)
|
|
214
|
-
setting :contract, default:
|
|
214
|
+
setting :contract, default: Karafka::Cli::Contracts::Server.new
|
|
215
215
|
end
|
|
216
216
|
|
|
217
217
|
setting :routing do
|
|
@@ -345,7 +345,7 @@ module Karafka
|
|
|
345
345
|
# (incoming). Can be replaced with a custom implementation for formats like Avro,
|
|
346
346
|
# Protobuf, etc. This is a global setting because Rails serializes jobs before
|
|
347
347
|
# Karafka receives them, so we need a consistent approach across all ActiveJob topics.
|
|
348
|
-
setting :deserializer, default:
|
|
348
|
+
setting :deserializer, default: Karafka::ActiveJob::Deserializer.new
|
|
349
349
|
end
|
|
350
350
|
end
|
|
351
351
|
|
|
@@ -415,17 +415,21 @@ module Karafka
|
|
|
415
415
|
# of the pro defaults with custom components
|
|
416
416
|
Pro::Loader.pre_setup_all(config) if Karafka.pro?
|
|
417
417
|
|
|
418
|
-
|
|
418
|
+
# Wrap config in a proxy that intercepts producer block configuration
|
|
419
|
+
proxy = ConfigProxy.new(config)
|
|
420
|
+
# We need to check for the block presence here because user can just run setup without
|
|
421
|
+
# any block given
|
|
422
|
+
configure { yield(proxy) if block_given? }
|
|
419
423
|
|
|
420
424
|
Contracts::Config.new.validate!(
|
|
421
425
|
config.to_h,
|
|
422
426
|
scope: %w[config]
|
|
423
427
|
)
|
|
424
428
|
|
|
425
|
-
configure_components
|
|
429
|
+
configure_components(proxy)
|
|
426
430
|
|
|
427
431
|
# Refreshes the references that are cached that might have been changed by the config
|
|
428
|
-
|
|
432
|
+
Karafka.refresh!
|
|
429
433
|
|
|
430
434
|
# Post-setup configure all routing features that would need this
|
|
431
435
|
Routing::Features::Base.post_setup_all(config)
|
|
@@ -443,14 +447,16 @@ module Karafka
|
|
|
443
447
|
private
|
|
444
448
|
|
|
445
449
|
# Sets up all the components that are based on the user configuration
|
|
450
|
+
# @param config_proxy [ConfigProxy] the configuration proxy containing deferred setup
|
|
451
|
+
# blocks
|
|
446
452
|
# @note At the moment it is only WaterDrop
|
|
447
|
-
def configure_components
|
|
453
|
+
def configure_components(config_proxy)
|
|
448
454
|
oauth_listener = config.oauth.token_provider_listener
|
|
449
455
|
# We need to subscribe the oauth listener here because we want it to be ready before
|
|
450
456
|
# any consumer/admin runs
|
|
451
457
|
Karafka::App.monitor.subscribe(oauth_listener) if oauth_listener
|
|
452
458
|
|
|
453
|
-
config.producer ||=
|
|
459
|
+
config.producer ||= WaterDrop::Producer.new do |producer_config|
|
|
454
460
|
# In some cases WaterDrop updates the config and we don't want our consumer config to
|
|
455
461
|
# be polluted by those updates, that's why we copy
|
|
456
462
|
producer_kafka = AttributesMap.producer(config.kafka.dup)
|
|
@@ -463,6 +469,11 @@ module Karafka
|
|
|
463
469
|
producer_config.oauth.token_provider_listener = oauth_listener
|
|
464
470
|
producer_config.logger = config.logger
|
|
465
471
|
end
|
|
472
|
+
|
|
473
|
+
# Execute user's producer configuration block
|
|
474
|
+
# This happens after the default producer setup, allowing users to customize settings
|
|
475
|
+
# If no block was provided during setup, this will be an empty lambda that does nothing
|
|
476
|
+
config_proxy.producer_initialization_block.call(config.producer.config)
|
|
466
477
|
end
|
|
467
478
|
end
|
|
468
479
|
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Setup
|
|
5
|
+
# Configuration proxy that wraps the actual config object during the setup phase.
|
|
6
|
+
#
|
|
7
|
+
# ## Purpose
|
|
8
|
+
#
|
|
9
|
+
# This proxy exists to intercept specific configuration methods during the setup block
|
|
10
|
+
# execution, allowing for deferred initialization and special handling of certain
|
|
11
|
+
# configuration aspects without permanently modifying the config object's API.
|
|
12
|
+
#
|
|
13
|
+
# The key design principle is: **the proxy only exists during setup and doesn't pollute
|
|
14
|
+
# the permanent config API**. Once setup is complete, all access goes directly to the
|
|
15
|
+
# real config object.
|
|
16
|
+
#
|
|
17
|
+
# ## Why Use a Proxy?
|
|
18
|
+
#
|
|
19
|
+
# During Karafka setup, there's a specific order of operations:
|
|
20
|
+
#
|
|
21
|
+
# 1. User configures basic settings (kafka, client_id, etc.)
|
|
22
|
+
# 2. Config validation runs
|
|
23
|
+
# 3. Components are initialized based on finalized config
|
|
24
|
+
# 4. Post-setup hooks execute
|
|
25
|
+
#
|
|
26
|
+
# Some configuration needs to happen **after** user settings but **during** component
|
|
27
|
+
# initialization. The proxy intercepts these special cases during step 1, stores the
|
|
28
|
+
# instructions, and applies them during step 3.
|
|
29
|
+
#
|
|
30
|
+
# ## Current Use Case: Producer Configuration
|
|
31
|
+
#
|
|
32
|
+
# The proxy currently intercepts `producer` calls with blocks:
|
|
33
|
+
#
|
|
34
|
+
# ```ruby
|
|
35
|
+
# Karafka::App.setup do |config|
|
|
36
|
+
# config.kafka = { 'bootstrap.servers': 'localhost:9092' }
|
|
37
|
+
#
|
|
38
|
+
# # This is intercepted by the proxy
|
|
39
|
+
# config.producer do |producer_config|
|
|
40
|
+
# producer_config.kafka['compression.type'] = 'snappy'
|
|
41
|
+
# end
|
|
42
|
+
# end
|
|
43
|
+
# ```
|
|
44
|
+
#
|
|
45
|
+
# Without the proxy, we'd have two problems:
|
|
46
|
+
#
|
|
47
|
+
# 1. **Permanent API pollution**: Adding a `producer` method to config that accepts blocks
|
|
48
|
+
# would change its permanent API, even though this functionality is only needed during
|
|
49
|
+
# setup.
|
|
50
|
+
#
|
|
51
|
+
# 2. **Timing issues**: The producer doesn't exist yet when the user's setup block runs.
|
|
52
|
+
# The producer is created in `configure_components` after all user configuration is
|
|
53
|
+
# complete. We need to store the user's producer configuration block and apply it
|
|
54
|
+
# later at the right time.
|
|
55
|
+
#
|
|
56
|
+
# The proxy solves both:
|
|
57
|
+
# - It only exists during the setup call
|
|
58
|
+
# - It stores the producer configuration block in an instance variable
|
|
59
|
+
# - After setup, the block is passed to `configure_components` for execution
|
|
60
|
+
# - The proxy is then discarded
|
|
61
|
+
#
|
|
62
|
+
# ## Future Use Cases
|
|
63
|
+
#
|
|
64
|
+
# This pattern can be extended for other deferred configuration needs:
|
|
65
|
+
#
|
|
66
|
+
# - **Monitor configuration**: Intercept monitor setup to configure it after all components
|
|
67
|
+
# are initialized
|
|
68
|
+
# - **Custom component initialization**: Allow users to configure internal components that
|
|
69
|
+
# need access to the fully-configured environment
|
|
70
|
+
# - **Feature toggles**: Enable/disable features based on the complete configuration state
|
|
71
|
+
#
|
|
72
|
+
# ## Implementation Details
|
|
73
|
+
#
|
|
74
|
+
# The proxy uses Ruby's SimpleDelegator pattern:
|
|
75
|
+
# 1. Inherits from SimpleDelegator for automatic delegation
|
|
76
|
+
# 2. Implements specific interceptor methods (currently just `producer`)
|
|
77
|
+
# 3. Delegates everything else to the wrapped config automatically
|
|
78
|
+
# 4. Stores deferred configuration in instance variables
|
|
79
|
+
#
|
|
80
|
+
# ## Lifecycle
|
|
81
|
+
#
|
|
82
|
+
# ```
|
|
83
|
+
# Setup.setup(&block)
|
|
84
|
+
# ↓
|
|
85
|
+
# proxy = ConfigProxy.new(config) # Create proxy
|
|
86
|
+
# ↓
|
|
87
|
+
# configure { yield(proxy) } # User block receives proxy
|
|
88
|
+
# ↓
|
|
89
|
+
# [User calls config methods] # Most delegate to real config
|
|
90
|
+
# ↓
|
|
91
|
+
# [User calls config.producer {}] # Intercepted by proxy
|
|
92
|
+
# ↓
|
|
93
|
+
# configure_components(proxy.producer_initialization_block) # Block retrieved
|
|
94
|
+
# ↓
|
|
95
|
+
# [Block executed with real producer config]
|
|
96
|
+
# ↓
|
|
97
|
+
# proxy = nil # Proxy discarded
|
|
98
|
+
# ```
|
|
99
|
+
#
|
|
100
|
+
# ## Example Usage
|
|
101
|
+
#
|
|
102
|
+
# ```ruby
|
|
103
|
+
# class KarafkaApp < Karafka::App
|
|
104
|
+
# setup do |config|
|
|
105
|
+
# # Standard config access - delegated to real config
|
|
106
|
+
# config.kafka = { 'bootstrap.servers': 'localhost:9092' }
|
|
107
|
+
# config.client_id = 'my_app'
|
|
108
|
+
#
|
|
109
|
+
# # Special intercepted method - handled by proxy
|
|
110
|
+
# config.producer do |producer_config|
|
|
111
|
+
# producer_config.kafka['compression.type'] = 'snappy'
|
|
112
|
+
# producer_config.kafka['linger.ms'] = 10
|
|
113
|
+
# producer_config.max_wait_timeout = 60_000
|
|
114
|
+
# end
|
|
115
|
+
# end
|
|
116
|
+
# end
|
|
117
|
+
# ```
|
|
118
|
+
#
|
|
119
|
+
# @see Karafka::Setup::Config.setup
|
|
120
|
+
# @see Karafka::Setup::Config.configure_components
|
|
121
|
+
class ConfigProxy < SimpleDelegator
|
|
122
|
+
# @return [Proc] the stored producer initialization block (defaults to empty lambda)
|
|
123
|
+
attr_reader :producer_initialization_block
|
|
124
|
+
|
|
125
|
+
# Creates a new configuration proxy wrapping the actual config object.
|
|
126
|
+
#
|
|
127
|
+
# Uses SimpleDelegator to automatically delegate all method calls to the wrapped config
|
|
128
|
+
# except for specifically intercepted methods like {#producer}.
|
|
129
|
+
#
|
|
130
|
+
# The producer initialization block defaults to an empty lambda, eliminating the need for
|
|
131
|
+
# nil checks when executing the block in {#configure_components}.
|
|
132
|
+
#
|
|
133
|
+
# @param config [Karafka::Setup::Config::Node] the actual config object to wrap
|
|
134
|
+
#
|
|
135
|
+
# @example
|
|
136
|
+
# proxy = ConfigProxy.new(Karafka::App.config)
|
|
137
|
+
def initialize(config)
|
|
138
|
+
super
|
|
139
|
+
@producer_initialization_block = ->(_) {}
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Captures a block for producer configuration or delegates producer assignment to config.
|
|
143
|
+
#
|
|
144
|
+
# This method has dual behavior:
|
|
145
|
+
#
|
|
146
|
+
# 1. **With a block**: Stores the block for later execution after the producer is created.
|
|
147
|
+
# This allows users to customize producer settings without manually creating a producer
|
|
148
|
+
# instance.
|
|
149
|
+
#
|
|
150
|
+
# 2. **With an instance**: Delegates to `config.producer=` for direct producer assignment.
|
|
151
|
+
# This preserves the existing API for users who want to provide their own producer.
|
|
152
|
+
#
|
|
153
|
+
# The block is stored in `@producer_initialization_block` and later passed to
|
|
154
|
+
# `configure_components` where it's executed with the producer's config object.
|
|
155
|
+
#
|
|
156
|
+
# ## Why This Exists
|
|
157
|
+
#
|
|
158
|
+
# The producer is created in `configure_components` after all user configuration is
|
|
159
|
+
# complete. This ensures the producer inherits the correct kafka settings. However,
|
|
160
|
+
# users may want to customize the producer further (add middleware, change timeouts,
|
|
161
|
+
# etc.) without creating their own producer instance.
|
|
162
|
+
#
|
|
163
|
+
# This method bridges the gap: it lets users configure the producer **as if it exists**
|
|
164
|
+
# during setup, but actually defers the configuration until after it's created.
|
|
165
|
+
#
|
|
166
|
+
# ## Block Execution Timing
|
|
167
|
+
#
|
|
168
|
+
# ```
|
|
169
|
+
# setup do |config|
|
|
170
|
+
# config.kafka = { ... } # Runs immediately
|
|
171
|
+
#
|
|
172
|
+
# config.producer do |pc| # Block STORED (not executed yet)
|
|
173
|
+
# pc.kafka['...'] = '...'
|
|
174
|
+
# end
|
|
175
|
+
# end
|
|
176
|
+
# # User block complete
|
|
177
|
+
# # Config validation runs
|
|
178
|
+
# # configure_components creates producer
|
|
179
|
+
# # NOW the stored block executes: block.call(producer.config)
|
|
180
|
+
# ```
|
|
181
|
+
#
|
|
182
|
+
# @param instance [WaterDrop::Producer, nil] optional producer instance for direct
|
|
183
|
+
# assignment
|
|
184
|
+
# @param block [Proc] optional block for producer configuration. Will be called with
|
|
185
|
+
# the producer's config object after the producer is created.
|
|
186
|
+
# @return [void]
|
|
187
|
+
#
|
|
188
|
+
# @example Configuring producer with a block
|
|
189
|
+
# config.producer do |producer_config|
|
|
190
|
+
# producer_config.kafka['compression.type'] = 'snappy'
|
|
191
|
+
# producer_config.max_wait_timeout = 30_000
|
|
192
|
+
# producer_config.middleware.append(MyMiddleware.new)
|
|
193
|
+
# end
|
|
194
|
+
#
|
|
195
|
+
# @example Direct producer assignment
|
|
196
|
+
# custom_producer = WaterDrop::Producer.new { |c| c.kafka = { 'bootstrap.servers' => 'localhost:9092' } }
|
|
197
|
+
# config.producer = custom_producer
|
|
198
|
+
def producer(instance = nil, &block)
|
|
199
|
+
if block
|
|
200
|
+
# Store the configuration block for later execution
|
|
201
|
+
@producer_initialization_block = block
|
|
202
|
+
else
|
|
203
|
+
# Direct assignment - delegate to real config via __getobj__
|
|
204
|
+
__getobj__.producer = instance
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -29,6 +29,7 @@ module Karafka
|
|
|
29
29
|
# @return [Array<Node>] All nodes that manager manages
|
|
30
30
|
attr_reader :nodes
|
|
31
31
|
|
|
32
|
+
# Initializes the swarm manager with empty nodes
|
|
32
33
|
def initialize
|
|
33
34
|
@nodes = []
|
|
34
35
|
@statuses = Hash.new { |h, k| h[k] = {} }
|
|
@@ -97,7 +98,7 @@ module Karafka
|
|
|
97
98
|
# If we've issued a stop to this process and it does not want to stop in the period, kills it
|
|
98
99
|
#
|
|
99
100
|
# @param statuses [Hash] hash with statuses transitions with times
|
|
100
|
-
# @param [Swarm::Node] node we're checking
|
|
101
|
+
# @param node [Swarm::Node] node we're checking
|
|
101
102
|
# @return [Boolean] should it be the last action taken on this node in this run
|
|
102
103
|
def terminate_if_hanging(statuses, node)
|
|
103
104
|
return false unless statuses.key?(:stop)
|
|
@@ -118,7 +119,7 @@ module Karafka
|
|
|
118
119
|
# reported it is not healthy.
|
|
119
120
|
#
|
|
120
121
|
# @param statuses [Hash] hash with statuses transitions with times
|
|
121
|
-
# @param [Swarm::Node] node we're checking
|
|
122
|
+
# @param node [Swarm::Node] node we're checking
|
|
122
123
|
# @return [Boolean] should it be the last action taken on this node in this run
|
|
123
124
|
def stop_if_not_healthy(statuses, node)
|
|
124
125
|
status = node.status
|
|
@@ -146,7 +147,7 @@ module Karafka
|
|
|
146
147
|
# If node stopped responding, starts the stopping procedure.
|
|
147
148
|
#
|
|
148
149
|
# @param statuses [Hash] hash with statuses transitions with times
|
|
149
|
-
# @param [Swarm::Node] node we're checking
|
|
150
|
+
# @param node [Swarm::Node] node we're checking
|
|
150
151
|
# @return [Boolean] should it be the last action taken on this node in this run
|
|
151
152
|
def stop_if_not_responding(statuses, node)
|
|
152
153
|
# Do nothing if already stopping
|
|
@@ -171,7 +172,7 @@ module Karafka
|
|
|
171
172
|
# Cleans up a dead process and remembers time of death for restart after a period.
|
|
172
173
|
#
|
|
173
174
|
# @param statuses [Hash] hash with statuses transitions with times
|
|
174
|
-
# @param [Swarm::Node] node we're checking
|
|
175
|
+
# @param node [Swarm::Node] node we're checking
|
|
175
176
|
# @return [Boolean] should it be the last action taken on this node in this run
|
|
176
177
|
def cleanup_one(statuses, node)
|
|
177
178
|
return false if statuses.key?(:dead_since)
|
|
@@ -188,7 +189,7 @@ module Karafka
|
|
|
188
189
|
# killed for some external reason.
|
|
189
190
|
#
|
|
190
191
|
# @param statuses [Hash] hash with statuses transitions with times
|
|
191
|
-
# @param [Swarm::Node] node we're checking
|
|
192
|
+
# @param node [Swarm::Node] node we're checking
|
|
192
193
|
# @return [Boolean] should it be the last action taken on this node in this run
|
|
193
194
|
def restart_after_timeout(statuses, node)
|
|
194
195
|
return false unless over?(statuses[:dead_since], node_restart_timeout)
|
|
@@ -200,7 +201,7 @@ module Karafka
|
|
|
200
201
|
|
|
201
202
|
# Starts a new node (or restarts dead)
|
|
202
203
|
#
|
|
203
|
-
# @param [Swarm::Node] node we're starting
|
|
204
|
+
# @param node [Swarm::Node] node we're starting
|
|
204
205
|
def start_one(node)
|
|
205
206
|
instr_args = { caller: self, node: node }
|
|
206
207
|
|
data/lib/karafka/swarm/node.rb
CHANGED
|
@@ -73,7 +73,7 @@ module Karafka
|
|
|
73
73
|
old_producer_config = old_producer.config
|
|
74
74
|
|
|
75
75
|
# Supervisor producer is closed, hence we need a new one here
|
|
76
|
-
config.producer =
|
|
76
|
+
config.producer = WaterDrop::Producer.new do |p_config|
|
|
77
77
|
p_config.kafka = Setup::AttributesMap.producer(kafka.dup)
|
|
78
78
|
p_config.logger = config.logger
|
|
79
79
|
|
data/lib/karafka/version.rb
CHANGED
data/lib/karafka.rb
CHANGED
|
@@ -11,7 +11,6 @@ require 'fileutils'
|
|
|
11
11
|
require 'openssl'
|
|
12
12
|
require 'optparse'
|
|
13
13
|
require 'socket'
|
|
14
|
-
require 'base64'
|
|
15
14
|
require 'date'
|
|
16
15
|
require 'singleton'
|
|
17
16
|
require 'digest'
|
|
@@ -34,7 +33,7 @@ module Karafka
|
|
|
34
33
|
env.replace(environment.to_s)
|
|
35
34
|
end
|
|
36
35
|
|
|
37
|
-
# @return [Logger] logger that we want to use. Will use
|
|
36
|
+
# @return [Logger] logger that we want to use. Will use Karafka::Logger by default
|
|
38
37
|
def logger
|
|
39
38
|
@logger ||= App.config.logger
|
|
40
39
|
end
|
|
@@ -148,7 +147,7 @@ module Karafka
|
|
|
148
147
|
#
|
|
149
148
|
# This method refreshes the things that might have been altered by the configuration
|
|
150
149
|
def refresh!
|
|
151
|
-
config =
|
|
150
|
+
config = Karafka::App.config
|
|
152
151
|
|
|
153
152
|
@logger = config.logger
|
|
154
153
|
@producer = config.producer
|