karafka 2.6.0.beta1 → 2.6.0.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 +64 -27
- data/certs/expired.txt +2 -0
- data/config/locales/errors.yml +5 -0
- data/karafka.gemspec +1 -1
- data/lib/active_job/queue_adapters/karafka_adapter.rb +1 -1
- data/lib/karafka/active_job/current_attributes/loading.rb +1 -1
- data/lib/karafka/active_job/current_attributes.rb +4 -2
- data/lib/karafka/active_job/dispatcher.rb +1 -2
- data/lib/karafka/admin/consumer_groups.rb +8 -2
- data/lib/karafka/admin/replication.rb +6 -3
- data/lib/karafka/admin/topics.rb +32 -12
- data/lib/karafka/admin.rb +155 -59
- data/lib/karafka/app.rb +1 -1
- data/lib/karafka/base_consumer.rb +33 -5
- data/lib/karafka/cli/console.rb +0 -1
- data/lib/karafka/cli/help.rb +0 -1
- data/lib/karafka/cli/info.rb +0 -1
- data/lib/karafka/cli/install.rb +0 -1
- data/lib/karafka/cli/server.rb +0 -1
- data/lib/karafka/cli/swarm.rb +0 -1
- data/lib/karafka/cli.rb +2 -2
- data/lib/karafka/connection/client.rb +44 -7
- data/lib/karafka/connection/listener.rb +3 -3
- data/lib/karafka/connection/manager.rb +0 -1
- data/lib/karafka/connection/pauses_manager.rb +17 -0
- data/lib/karafka/connection/proxy.rb +45 -0
- data/lib/karafka/connection/status.rb +0 -1
- data/lib/karafka/instrumentation/assignments_tracker.rb +34 -0
- data/lib/karafka/instrumentation/callbacks/consumer_groups/decorator.rb +19 -0
- data/lib/karafka/instrumentation/callbacks/consumer_groups/error.rb +0 -1
- data/lib/karafka/instrumentation/callbacks/consumer_groups/statistics.rb +3 -2
- data/lib/karafka/instrumentation/critical_errors_listener.rb +85 -0
- data/lib/karafka/instrumentation/logger_listener.rb +2 -0
- data/lib/karafka/instrumentation/monitor.rb +2 -3
- data/lib/karafka/instrumentation/vendors/appsignal/errors_listener.rb +0 -1
- data/lib/karafka/instrumentation/vendors/appsignal/metrics_listener.rb +0 -1
- data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +4 -3
- data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +0 -2
- data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +181 -18
- data/lib/karafka/pro/admin/recovery/errors.rb +0 -1
- data/lib/karafka/pro/admin/recovery.rb +6 -4
- data/lib/karafka/pro/base_consumer.rb +5 -5
- data/lib/karafka/pro/cleaner/messages/metadata.rb +0 -1
- data/lib/karafka/pro/cli/parallel_segments/base.rb +1 -2
- data/lib/karafka/pro/connection/manager.rb +7 -4
- data/lib/karafka/pro/encryption/messages/middleware.rb +5 -0
- data/lib/karafka/pro/encryption/messages/parser.rb +4 -0
- data/lib/karafka/pro/instrumentation/callbacks/consumer_groups/decorator.rb +63 -0
- data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/compensator.rb +120 -0
- data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/fetcher.rb +88 -0
- data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/refresher.rb +185 -0
- data/lib/karafka/pro/instrumentation/consumer_groups/lag_compensation/registry.rb +102 -0
- data/lib/karafka/pro/instrumentation/performance_tracker.rb +40 -6
- data/lib/karafka/pro/iterator/tpl_builder.rb +27 -7
- data/lib/karafka/pro/iterator.rb +22 -7
- data/lib/karafka/pro/loader.rb +12 -0
- data/lib/karafka/pro/processing/consumer_groups/adaptive_iterator/consumer.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/adaptive_iterator/tracker.rb +2 -4
- data/lib/karafka/pro/processing/consumer_groups/collapser.rb +6 -9
- data/lib/karafka/pro/processing/consumer_groups/coordinator.rb +1 -4
- data/lib/karafka/pro/processing/consumer_groups/coordinators/errors_tracker.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/coordinators/filters_applier.rb +21 -10
- data/lib/karafka/pro/processing/consumer_groups/coordinators/virtual_offset_manager.rb +4 -4
- data/lib/karafka/pro/processing/consumer_groups/expansions_selector.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/filters/actions.rb +84 -0
- data/lib/karafka/pro/processing/consumer_groups/filters/base.rb +5 -6
- data/lib/karafka/pro/processing/consumer_groups/filters/delayer.rb +2 -5
- data/lib/karafka/pro/processing/consumer_groups/filters/expirer.rb +1 -4
- data/lib/karafka/pro/processing/consumer_groups/filters/inline_insights_delayer.rb +1 -4
- data/lib/karafka/pro/processing/consumer_groups/filters/throttler.rb +2 -5
- data/lib/karafka/pro/processing/consumer_groups/filters/virtual_limiter.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/jobs/consume_non_blocking.rb +2 -3
- data/lib/karafka/pro/processing/consumer_groups/jobs/eofed_non_blocking.rb +0 -2
- data/lib/karafka/pro/processing/consumer_groups/jobs/periodic.rb +2 -3
- data/lib/karafka/pro/processing/consumer_groups/jobs/periodic_non_blocking.rb +0 -2
- data/lib/karafka/pro/processing/consumer_groups/jobs/revoked_non_blocking.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/jobs_builder.rb +5 -4
- data/lib/karafka/pro/processing/consumer_groups/offset_metadata/consumer.rb +3 -7
- data/lib/karafka/pro/processing/consumer_groups/offset_metadata/fetcher.rb +8 -10
- data/lib/karafka/pro/processing/consumer_groups/offset_metadata/listener.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/parallel_segments/filters/base.rb +3 -5
- data/lib/karafka/pro/processing/consumer_groups/parallel_segments/filters/default.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/parallel_segments/filters/mom.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/partitioner.rb +2 -5
- data/lib/karafka/pro/processing/consumer_groups/periodic_job/consumer.rb +2 -5
- data/lib/karafka/pro/processing/consumer_groups/piping/consumer.rb +2 -5
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_lrj_mom.rb +10 -9
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_lrj_mom_vp.rb +11 -10
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_mom.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_ftr_mom_vp.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_lrj_mom.rb +8 -7
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_lrj_mom_vp.rb +9 -8
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_mom.rb +5 -8
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/dlq_mom_vp.rb +3 -6
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_lrj_mom.rb +4 -7
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_lrj_mom_vp.rb +6 -9
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_mom.rb +3 -7
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/ftr_mom_vp.rb +4 -8
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/lrj_mom.rb +3 -6
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/lrj_mom_vp.rb +4 -7
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/mom.rb +2 -5
- data/lib/karafka/pro/processing/consumer_groups/strategies/aj/mom_vp.rb +3 -6
- data/lib/karafka/pro/processing/consumer_groups/strategies/base.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/strategies/default.rb +56 -33
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/default.rb +27 -14
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj.rb +4 -7
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj_mom.rb +5 -8
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj_mom_vp.rb +5 -8
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_lrj_vp.rb +3 -7
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_mom.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_mom_vp.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/ftr_vp.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj.rb +2 -5
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj_mom.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj_mom_vp.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/lrj_vp.rb +3 -6
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/mom.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/mom_vp.rb +3 -6
- data/lib/karafka/pro/processing/consumer_groups/strategies/dlq/vp.rb +65 -8
- data/lib/karafka/pro/processing/consumer_groups/strategies/ftr/default.rb +0 -4
- data/lib/karafka/pro/processing/consumer_groups/strategies/ftr/vp.rb +2 -5
- data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/default.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr.rb +3 -6
- data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr_mom.rb +4 -7
- data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr_mom_vp.rb +4 -8
- data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/ftr_vp.rb +3 -6
- data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/mom.rb +2 -5
- data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/mom_vp.rb +3 -7
- data/lib/karafka/pro/processing/consumer_groups/strategies/lrj/vp.rb +2 -5
- data/lib/karafka/pro/processing/consumer_groups/strategies/mom/default.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/strategies/mom/ftr.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/strategies/mom/ftr_vp.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/strategies/mom/vp.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/strategies/vp/default.rb +11 -6
- data/lib/karafka/pro/processing/consumer_groups/strategy_selector.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/subscription_groups_coordinator.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/virtual_partitions/distributors/balanced.rb +0 -3
- data/lib/karafka/pro/processing/consumer_groups/virtual_partitions/distributors/base.rb +2 -4
- data/lib/karafka/pro/processing/consumer_groups/virtual_partitions/distributors/consistent.rb +0 -3
- data/lib/karafka/pro/processing/jobs_queue.rb +4 -1
- data/lib/karafka/pro/processing/schedulers/base.rb +4 -2
- data/lib/karafka/pro/processing/schedulers/default.rb +2 -2
- data/lib/karafka/pro/recurring_tasks/consumer.rb +9 -2
- data/lib/karafka/pro/recurring_tasks/contracts/config.rb +0 -1
- data/lib/karafka/pro/recurring_tasks/executor.rb +25 -13
- data/lib/karafka/pro/routing/features/consumer_groups/direct_assignments/subscription_group.rb +0 -2
- data/lib/karafka/pro/routing/features/consumer_groups/long_running_job.rb +0 -2
- data/lib/karafka/pro/routing/features/consumer_groups/multiplexing/proxy.rb +2 -2
- data/lib/karafka/pro/routing/features/consumer_groups/non_blocking_job.rb +0 -2
- data/lib/karafka/pro/routing/features/consumer_groups/offset_metadata/topic.rb +0 -6
- data/lib/karafka/pro/routing/features/consumer_groups/parallel_segments/consumer_group.rb +5 -4
- data/lib/karafka/pro/routing/features/consumer_groups/patterns/config.rb +0 -6
- data/lib/karafka/pro/routing/features/consumer_groups/patterns/consumer_group.rb +2 -1
- data/lib/karafka/pro/routing/features/consumer_groups/patterns/contracts/pattern.rb +0 -1
- data/lib/karafka/pro/routing/features/consumer_groups/patterns/detector.rb +2 -1
- data/lib/karafka/pro/routing/features/consumer_groups/patterns/pattern.rb +2 -1
- data/lib/karafka/pro/routing/features/consumer_groups/patterns/topics.rb +8 -8
- data/lib/karafka/pro/routing/features/consumer_groups/periodic_job/topic.rb +5 -5
- data/lib/karafka/pro/routing/features/consumer_groups/recurring_tasks/builder.rb +33 -13
- data/lib/karafka/pro/routing/features/consumer_groups/scheduled_messages/builder.rb +52 -28
- data/lib/karafka/pro/routing/features/consumer_groups/scheduled_messages/topic.rb +2 -1
- data/lib/karafka/pro/routing/features/consumer_groups/virtual_partitions/topic.rb +6 -5
- data/lib/karafka/pro/scheduled_messages/consumer.rb +7 -1
- data/lib/karafka/pro/scheduled_messages/schema_validator.rb +6 -1
- data/lib/karafka/pro/swarm/liveness_listener.rb +118 -13
- data/lib/karafka/pro.rb +0 -6
- data/lib/karafka/processing/consumer_groups/coordinator.rb +4 -5
- data/lib/karafka/processing/consumer_groups/coordinators_buffer.rb +6 -2
- data/lib/karafka/processing/consumer_groups/executor.rb +4 -4
- data/lib/karafka/processing/consumer_groups/executors_buffer.rb +2 -4
- data/lib/karafka/processing/consumer_groups/expansions_selector.rb +0 -2
- data/lib/karafka/processing/consumer_groups/inline_insights/consumer.rb +0 -2
- data/lib/karafka/processing/consumer_groups/inline_insights/listener.rb +0 -2
- data/lib/karafka/processing/consumer_groups/inline_insights/tracker.rb +14 -16
- data/lib/karafka/processing/consumer_groups/jobs/consume.rb +2 -5
- data/lib/karafka/processing/consumer_groups/jobs/eofed.rb +2 -3
- data/lib/karafka/processing/consumer_groups/jobs/idle.rb +2 -2
- data/lib/karafka/processing/consumer_groups/jobs/revoked.rb +2 -4
- data/lib/karafka/processing/consumer_groups/jobs/shutdown.rb +2 -2
- data/lib/karafka/processing/consumer_groups/partitioner.rb +3 -6
- data/lib/karafka/processing/consumer_groups/strategies/aj_dlq_mom.rb +5 -2
- data/lib/karafka/processing/consumer_groups/strategies/aj_mom.rb +2 -4
- data/lib/karafka/processing/consumer_groups/strategies/base.rb +3 -5
- data/lib/karafka/processing/consumer_groups/strategies/default.rb +43 -13
- data/lib/karafka/processing/consumer_groups/strategies/dlq.rb +5 -2
- data/lib/karafka/processing/consumer_groups/strategies/dlq_mom.rb +5 -2
- data/lib/karafka/processing/consumer_groups/strategies/mom.rb +2 -3
- data/lib/karafka/processing/consumer_groups/strategy_selector.rb +0 -2
- data/lib/karafka/processing/coordinator.rb +0 -2
- data/lib/karafka/processing/coordinators_buffer.rb +0 -2
- data/lib/karafka/processing/partitioner.rb +0 -2
- data/lib/karafka/processing/strategy_selector.rb +0 -2
- data/lib/karafka/processing/worker.rb +14 -6
- data/lib/karafka/processing/workers_pool.rb +21 -4
- data/lib/karafka/railtie.rb +0 -1
- data/lib/karafka/routing/contracts/routing.rb +2 -2
- data/lib/karafka/routing/features/consumer_groups/active_job/topic.rb +6 -5
- data/lib/karafka/routing/features/consumer_groups/manual_offset_management/topic.rb +2 -2
- data/lib/karafka/routing/features/deserializers/topic.rb +0 -1
- data/lib/karafka/routing/subscription_group.rb +1 -1
- data/lib/karafka/routing/topic.rb +4 -4
- data/lib/karafka/routing/topics.rb +21 -1
- data/lib/karafka/server.rb +9 -0
- data/lib/karafka/setup/config.rb +54 -10
- data/lib/karafka/setup/config_proxy.rb +2 -1
- data/lib/karafka/setup/contracts/config.rb +14 -0
- data/lib/karafka/swarm/node.rb +2 -2
- data/lib/karafka/version.rb +1 -2
- data/lib/karafka.rb +2 -2
- metadata +12 -4
|
@@ -37,8 +37,8 @@ module Karafka
|
|
|
37
37
|
#
|
|
38
38
|
# Methods here are suppose to be always available or are expected to be redefined
|
|
39
39
|
module BaseConsumer
|
|
40
|
-
# @return [Karafka::Pro::Processing::ConsumerGroups::Coordinators::ErrorsTracker] tracker for
|
|
41
|
-
# occurred during processing until another successful processing
|
|
40
|
+
# @return [Karafka::Pro::Processing::ConsumerGroups::Coordinators::ErrorsTracker] tracker for
|
|
41
|
+
# errors that occurred during processing until another successful processing
|
|
42
42
|
#
|
|
43
43
|
# @note This will always contain **only** details of errors that occurred during `#consume`
|
|
44
44
|
# because only those are retryable.
|
|
@@ -53,9 +53,9 @@ module Karafka
|
|
|
53
53
|
coordinator.errors_tracker
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
# @return [Karafka::Pro::Processing::ConsumerGroups::SubscriptionGroupsCoordinator]
|
|
57
|
-
# pause and resume polling of the given subscription group jobs
|
|
58
|
-
# further work.
|
|
56
|
+
# @return [Karafka::Pro::Processing::ConsumerGroups::SubscriptionGroupsCoordinator]
|
|
57
|
+
# Coordinator allowing to pause and resume polling of the given subscription group jobs
|
|
58
|
+
# queue for postponing further work.
|
|
59
59
|
#
|
|
60
60
|
# @note Since this stops polling, it can cause reaching `max.poll.interval.ms` limitations.
|
|
61
61
|
#
|
|
@@ -52,7 +52,7 @@ module Karafka
|
|
|
52
52
|
# @return [Hash{String => Array<Karafka::Routing::ConsumerGroup>}] hash with all parallel
|
|
53
53
|
# consumer groups as values and names of segments origin consumer group as the key.
|
|
54
54
|
def applicable_groups
|
|
55
|
-
requested_groups = options[:groups]
|
|
55
|
+
requested_groups = options[:groups] || []
|
|
56
56
|
|
|
57
57
|
workable_groups = Karafka::App
|
|
58
58
|
.routes
|
|
@@ -68,7 +68,6 @@ module Karafka
|
|
|
68
68
|
workable_group = workable_groups[requested_group]
|
|
69
69
|
|
|
70
70
|
if workable_group
|
|
71
|
-
requested_groups.delete(requested_group)
|
|
72
71
|
applicable_groups[requested_group] = workable_group
|
|
73
72
|
else
|
|
74
73
|
raise(
|
|
@@ -47,7 +47,6 @@ module Karafka
|
|
|
47
47
|
# Creates new manager instance
|
|
48
48
|
def initialize
|
|
49
49
|
super
|
|
50
|
-
@mutex = Mutex.new
|
|
51
50
|
@changes = Hash.new do |h, k|
|
|
52
51
|
h[k] = {
|
|
53
52
|
state: "",
|
|
@@ -253,7 +252,11 @@ module Karafka
|
|
|
253
252
|
# are also stable. This is a strong indicator that no rebalances or other operations are
|
|
254
253
|
# happening at a given moment.
|
|
255
254
|
def stable?(sg_listeners)
|
|
256
|
-
|
|
255
|
+
# Must stay a local, not a memoized `@scale_delay`: this manager is a single process-wide
|
|
256
|
+
# instance shared by every subscription-group family, so memoizing here would freeze the
|
|
257
|
+
# first family's `scale_delay` and apply it to all the others, making them ignore their
|
|
258
|
+
# own configured value.
|
|
259
|
+
scale_delay = sg_listeners.first.subscription_group.multiplexing.scale_delay
|
|
257
260
|
|
|
258
261
|
sg_listeners.all? do |sg_listener|
|
|
259
262
|
# If a listener is not active, we do not take it into consideration when looking at
|
|
@@ -262,8 +265,8 @@ module Karafka
|
|
|
262
265
|
|
|
263
266
|
state = @changes[sg_listener.subscription_group.id]
|
|
264
267
|
|
|
265
|
-
state[:state_age] >=
|
|
266
|
-
(monotonic_now - state[:changed_at]) >=
|
|
268
|
+
state[:state_age] >= scale_delay &&
|
|
269
|
+
(monotonic_now - state[:changed_at]) >= scale_delay &&
|
|
267
270
|
state[:state] == "up" &&
|
|
268
271
|
state[:join_state] == "steady"
|
|
269
272
|
end
|
|
@@ -48,6 +48,11 @@ module Karafka
|
|
|
48
48
|
def call(message)
|
|
49
49
|
payload = message[:payload]
|
|
50
50
|
|
|
51
|
+
# Tombstones (records with a nil payload) carry no data to encrypt. We pass them
|
|
52
|
+
# through untouched so they remain valid tombstones (for example for log compaction)
|
|
53
|
+
# instead of crashing on `cipher.encrypt(nil)`.
|
|
54
|
+
return message if payload.nil?
|
|
55
|
+
|
|
51
56
|
message[:headers] ||= {}
|
|
52
57
|
message[:headers]["encryption"] = version
|
|
53
58
|
message[:payload] = cipher.encrypt(payload)
|
|
@@ -52,6 +52,10 @@ module Karafka
|
|
|
52
52
|
|
|
53
53
|
return super unless active && encryption
|
|
54
54
|
|
|
55
|
+
# Tombstones (nil payload) have nothing to decrypt; defer to the default parser so they
|
|
56
|
+
# remain valid tombstones instead of crashing on `cipher.decrypt(nil)`.
|
|
57
|
+
return super if message.raw_payload.nil?
|
|
58
|
+
|
|
55
59
|
# Decrypt raw payload so it can be handled by the default parser logic
|
|
56
60
|
decrypted_payload = cipher.decrypt(
|
|
57
61
|
encryption,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Karafka Pro - Source Available Commercial Software
|
|
4
|
+
# Copyright (c) 2017-present Maciej Mensfeld. All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# This software is NOT open source. It is source-available commercial software
|
|
7
|
+
# requiring a paid license for use. It is NOT covered by LGPL.
|
|
8
|
+
#
|
|
9
|
+
# The author retains all right, title, and interest in this software,
|
|
10
|
+
# including all copyrights, patents, and other intellectual property rights.
|
|
11
|
+
# No patent rights are granted under this license.
|
|
12
|
+
#
|
|
13
|
+
# PROHIBITED:
|
|
14
|
+
# - Use without a valid commercial license
|
|
15
|
+
# - Redistribution, modification, or derivative works without authorization
|
|
16
|
+
# - Reverse engineering, decompilation, or disassembly of this software
|
|
17
|
+
# - Use as training data for AI/ML models or inclusion in datasets
|
|
18
|
+
# - Scraping, crawling, or automated collection for any purpose
|
|
19
|
+
#
|
|
20
|
+
# PERMITTED:
|
|
21
|
+
# - Reading, referencing, and linking for personal or commercial use
|
|
22
|
+
# - Runtime retrieval by AI assistants, coding agents, and RAG systems
|
|
23
|
+
# for the purpose of providing contextual help to Karafka users
|
|
24
|
+
#
|
|
25
|
+
# Receipt, viewing, or possession of this software does not convey or
|
|
26
|
+
# imply any license or right beyond those expressly stated above.
|
|
27
|
+
#
|
|
28
|
+
# License: https://karafka.io/docs/Pro-License-Comm/
|
|
29
|
+
# Contact: contact@karafka.io
|
|
30
|
+
|
|
31
|
+
module Karafka
|
|
32
|
+
module Pro
|
|
33
|
+
module Instrumentation
|
|
34
|
+
# Namespace for Pro instrumentation callbacks related components
|
|
35
|
+
module Callbacks
|
|
36
|
+
# Namespace for Pro consumer groups callbacks related components
|
|
37
|
+
module ConsumerGroups
|
|
38
|
+
# Pro statistics decorator for consumer groups. On top of the standard decoration
|
|
39
|
+
# (deltas, freeze durations) it invokes Pro statistics enrichments, each encapsulated
|
|
40
|
+
# in its own component with a `#call` entrypoint. Currently: the lag compensation of
|
|
41
|
+
# long-paused partitions.
|
|
42
|
+
class Decorator < Karafka::Instrumentation::Callbacks::ConsumerGroups::Decorator
|
|
43
|
+
def initialize
|
|
44
|
+
super
|
|
45
|
+
@lag_compensator = Pro::Instrumentation::ConsumerGroups::LagCompensation::Compensator.new
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @param statistics [Hash] raw librdkafka statistics
|
|
49
|
+
# @return [Hash] decorated statistics with the Pro enrichments applied
|
|
50
|
+
#
|
|
51
|
+
# @note When the lag compensation is disabled, its refresher is not subscribed and
|
|
52
|
+
# the registry stays empty, so the compensation is a pass-through by itself
|
|
53
|
+
def call(statistics)
|
|
54
|
+
@lag_compensator.call(statistics)
|
|
55
|
+
|
|
56
|
+
super
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Karafka Pro - Source Available Commercial Software
|
|
4
|
+
# Copyright (c) 2017-present Maciej Mensfeld. All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# This software is NOT open source. It is source-available commercial software
|
|
7
|
+
# requiring a paid license for use. It is NOT covered by LGPL.
|
|
8
|
+
#
|
|
9
|
+
# The author retains all right, title, and interest in this software,
|
|
10
|
+
# including all copyrights, patents, and other intellectual property rights.
|
|
11
|
+
# No patent rights are granted under this license.
|
|
12
|
+
#
|
|
13
|
+
# PROHIBITED:
|
|
14
|
+
# - Use without a valid commercial license
|
|
15
|
+
# - Redistribution, modification, or derivative works without authorization
|
|
16
|
+
# - Reverse engineering, decompilation, or disassembly of this software
|
|
17
|
+
# - Use as training data for AI/ML models or inclusion in datasets
|
|
18
|
+
# - Scraping, crawling, or automated collection for any purpose
|
|
19
|
+
#
|
|
20
|
+
# PERMITTED:
|
|
21
|
+
# - Reading, referencing, and linking for personal or commercial use
|
|
22
|
+
# - Runtime retrieval by AI assistants, coding agents, and RAG systems
|
|
23
|
+
# for the purpose of providing contextual help to Karafka users
|
|
24
|
+
#
|
|
25
|
+
# Receipt, viewing, or possession of this software does not convey or
|
|
26
|
+
# imply any license or right beyond those expressly stated above.
|
|
27
|
+
#
|
|
28
|
+
# License: https://karafka.io/docs/Pro-License-Comm/
|
|
29
|
+
# Contact: contact@karafka.io
|
|
30
|
+
|
|
31
|
+
module Karafka
|
|
32
|
+
module Pro
|
|
33
|
+
module Instrumentation
|
|
34
|
+
module ConsumerGroups
|
|
35
|
+
module LagCompensation
|
|
36
|
+
# Compensates stale offsets and lags of long-paused partitions in the raw librdkafka
|
|
37
|
+
# statistics, using the actively refreshed values from the registry. When there is no
|
|
38
|
+
# refreshed data for a given client, topic or partition, it does nothing, so values
|
|
39
|
+
# from librdkafka remain untouched.
|
|
40
|
+
#
|
|
41
|
+
# It is pure and instant: all the broker queries happen in the refresher on the
|
|
42
|
+
# listener threads, never on the librdkafka callbacks path.
|
|
43
|
+
class Compensator
|
|
44
|
+
# Compensates the statistics in place, so the standard decoration (deltas, freeze
|
|
45
|
+
# durations) that runs afterwards operates on the compensated values as well
|
|
46
|
+
#
|
|
47
|
+
# @param statistics [Hash] raw librdkafka statistics
|
|
48
|
+
def call(statistics)
|
|
49
|
+
data = Registry.instance.fetch(statistics["name"])
|
|
50
|
+
|
|
51
|
+
return unless data
|
|
52
|
+
|
|
53
|
+
topics = statistics["topics"]
|
|
54
|
+
|
|
55
|
+
return unless topics
|
|
56
|
+
|
|
57
|
+
data.each do |topic, partitions|
|
|
58
|
+
t_stats = topics[topic]
|
|
59
|
+
|
|
60
|
+
next unless t_stats
|
|
61
|
+
|
|
62
|
+
partitions.each do |partition, end_offset|
|
|
63
|
+
p_stats = t_stats["partitions"][partition.to_s]
|
|
64
|
+
|
|
65
|
+
next unless p_stats
|
|
66
|
+
|
|
67
|
+
compensate(p_stats, end_offset)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# Compensates a single partition statistics with the refreshed end offset. The
|
|
75
|
+
# committed and stored offsets are taken from the statistics themselves: they are
|
|
76
|
+
# maintained by the client commits (not by fetches), so their values stay accurate
|
|
77
|
+
# while a partition is paused and lags can be derived from them directly
|
|
78
|
+
#
|
|
79
|
+
# @param p_stats [Hash] single partition raw statistics
|
|
80
|
+
# @param end_offset [Integer] refreshed partition end offset
|
|
81
|
+
def compensate(p_stats, end_offset)
|
|
82
|
+
# The end offset only grows, so a refreshed value older or equal to what
|
|
83
|
+
# librdkafka reports means there is nothing to compensate: equal means no new
|
|
84
|
+
# data in the topic since the last fetch and lower means the partition resumed
|
|
85
|
+
# and its fetches are fresher than our refreshed snapshot. We compare against the
|
|
86
|
+
# last stable offset since that is what the statistics report for a read_committed
|
|
87
|
+
# consumer. Note the refreshed end offset itself is a high watermark (see
|
|
88
|
+
# `Fetcher`): on a topic with an in-flight transaction it can exceed the fetch-based
|
|
89
|
+
# last stable offset and briefly overstate the lag until the transaction resolves.
|
|
90
|
+
return unless end_offset > (p_stats["ls_offset"] || p_stats["hi_offset"] || -1)
|
|
91
|
+
|
|
92
|
+
p_stats["ls_offset"] = end_offset
|
|
93
|
+
# The real high watermark is always at least the last stable offset, so we bump
|
|
94
|
+
# it when needed to keep the statistics internally consistent, never overstating
|
|
95
|
+
hi_offset = p_stats["hi_offset"]
|
|
96
|
+
p_stats["hi_offset"] = end_offset if hi_offset.nil? || end_offset > hi_offset
|
|
97
|
+
|
|
98
|
+
committed = p_stats["committed_offset"]
|
|
99
|
+
|
|
100
|
+
# Negative means nothing was committed yet for this partition, in which case lag
|
|
101
|
+
# cannot be derived from the committed offset and we leave it untouched
|
|
102
|
+
if committed && committed >= 0
|
|
103
|
+
# Lags derive from the same reference the native librdkafka consumer lag does
|
|
104
|
+
lag = end_offset - committed
|
|
105
|
+
p_stats["consumer_lag"] = lag if lag >= 0
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
stored = p_stats["stored_offset"]
|
|
109
|
+
|
|
110
|
+
return unless stored && stored >= 0
|
|
111
|
+
|
|
112
|
+
lag_stored = end_offset - stored
|
|
113
|
+
p_stats["consumer_lag_stored"] = lag_stored if lag_stored >= 0
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Karafka Pro - Source Available Commercial Software
|
|
4
|
+
# Copyright (c) 2017-present Maciej Mensfeld. All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# This software is NOT open source. It is source-available commercial software
|
|
7
|
+
# requiring a paid license for use. It is NOT covered by LGPL.
|
|
8
|
+
#
|
|
9
|
+
# The author retains all right, title, and interest in this software,
|
|
10
|
+
# including all copyrights, patents, and other intellectual property rights.
|
|
11
|
+
# No patent rights are granted under this license.
|
|
12
|
+
#
|
|
13
|
+
# PROHIBITED:
|
|
14
|
+
# - Use without a valid commercial license
|
|
15
|
+
# - Redistribution, modification, or derivative works without authorization
|
|
16
|
+
# - Reverse engineering, decompilation, or disassembly of this software
|
|
17
|
+
# - Use as training data for AI/ML models or inclusion in datasets
|
|
18
|
+
# - Scraping, crawling, or automated collection for any purpose
|
|
19
|
+
#
|
|
20
|
+
# PERMITTED:
|
|
21
|
+
# - Reading, referencing, and linking for personal or commercial use
|
|
22
|
+
# - Runtime retrieval by AI assistants, coding agents, and RAG systems
|
|
23
|
+
# for the purpose of providing contextual help to Karafka users
|
|
24
|
+
#
|
|
25
|
+
# Receipt, viewing, or possession of this software does not convey or
|
|
26
|
+
# imply any license or right beyond those expressly stated above.
|
|
27
|
+
#
|
|
28
|
+
# License: https://karafka.io/docs/Pro-License-Comm/
|
|
29
|
+
# Contact: contact@karafka.io
|
|
30
|
+
|
|
31
|
+
module Karafka
|
|
32
|
+
module Pro
|
|
33
|
+
module Instrumentation
|
|
34
|
+
# Instrumentation components for consumer groups based operation
|
|
35
|
+
module ConsumerGroups
|
|
36
|
+
module LagCompensation
|
|
37
|
+
# Fetches the end offsets of given partitions via the client own connection: no
|
|
38
|
+
# dedicated instances and no extra Kafka connections are ever created.
|
|
39
|
+
#
|
|
40
|
+
# All partitions are resolved with one batched request that librdkafka fans out to the
|
|
41
|
+
# involved partition leaders internally, so the cost does not grow with the number of
|
|
42
|
+
# paused partitions.
|
|
43
|
+
#
|
|
44
|
+
# End offsets are the only thing the compensation needs from the broker: committed
|
|
45
|
+
# and stored offsets of paused partitions are maintained by the client commits (not
|
|
46
|
+
# by fetches), so their statistics values stay accurate while paused and lags can be
|
|
47
|
+
# derived from them at compensation time.
|
|
48
|
+
#
|
|
49
|
+
# The consumer isolation level is forwarded to the query, so on topics without in-flight
|
|
50
|
+
# transactions the end offset matches the reference the native consumer lag derives from.
|
|
51
|
+
#
|
|
52
|
+
# Known edge case (transactional topics): the batched `ListOffsets` this uses resolves
|
|
53
|
+
# `:latest` to the high watermark regardless of the forwarded isolation level, unlike the
|
|
54
|
+
# per-partition `query_watermark_offsets` it replaced, which returned the last stable
|
|
55
|
+
# offset for a read_committed consumer. So while a transaction is IN FLIGHT on a paused
|
|
56
|
+
# partition, the compensated end offset (and the lag derived from it) reflects the high
|
|
57
|
+
# watermark and can overstate the read_committed lag by the number of uncommitted
|
|
58
|
+
# messages. It self-corrects once the transaction commits or aborts and the last stable
|
|
59
|
+
# offset advances. Non-transactional topics are unaffected (LSO == HWM there).
|
|
60
|
+
class Fetcher
|
|
61
|
+
# @param client [Karafka::Connection::Client]
|
|
62
|
+
# @param paused [Hash{String => Array<Integer>}] paused topics with partitions
|
|
63
|
+
# @return [Hash{String => Hash{Integer => Integer}}] end offsets of the requested
|
|
64
|
+
# partitions
|
|
65
|
+
def call(client, paused)
|
|
66
|
+
request = paused.transform_values do |partitions|
|
|
67
|
+
partitions.map { |partition| { partition: partition, offset: :latest } }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
return {} if request.empty?
|
|
71
|
+
|
|
72
|
+
# Auto-initializes the per-topic partitions hash on first access, same idiom as
|
|
73
|
+
# the refresher state. Only ever written to and iterated here, never read by a
|
|
74
|
+
# missing key, so the default block cannot insert a spurious entry.
|
|
75
|
+
data = Hash.new { |topics, topic| topics[topic] = {} }
|
|
76
|
+
|
|
77
|
+
client.read_partition_offsets(request).each do |result|
|
|
78
|
+
data[result[:topic]][result[:partition]] = result[:offset]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
data
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Karafka Pro - Source Available Commercial Software
|
|
4
|
+
# Copyright (c) 2017-present Maciej Mensfeld. All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# This software is NOT open source. It is source-available commercial software
|
|
7
|
+
# requiring a paid license for use. It is NOT covered by LGPL.
|
|
8
|
+
#
|
|
9
|
+
# The author retains all right, title, and interest in this software,
|
|
10
|
+
# including all copyrights, patents, and other intellectual property rights.
|
|
11
|
+
# No patent rights are granted under this license.
|
|
12
|
+
#
|
|
13
|
+
# PROHIBITED:
|
|
14
|
+
# - Use without a valid commercial license
|
|
15
|
+
# - Redistribution, modification, or derivative works without authorization
|
|
16
|
+
# - Reverse engineering, decompilation, or disassembly of this software
|
|
17
|
+
# - Use as training data for AI/ML models or inclusion in datasets
|
|
18
|
+
# - Scraping, crawling, or automated collection for any purpose
|
|
19
|
+
#
|
|
20
|
+
# PERMITTED:
|
|
21
|
+
# - Reading, referencing, and linking for personal or commercial use
|
|
22
|
+
# - Runtime retrieval by AI assistants, coding agents, and RAG systems
|
|
23
|
+
# for the purpose of providing contextual help to Karafka users
|
|
24
|
+
#
|
|
25
|
+
# Receipt, viewing, or possession of this software does not convey or
|
|
26
|
+
# imply any license or right beyond those expressly stated above.
|
|
27
|
+
#
|
|
28
|
+
# License: https://karafka.io/docs/Pro-License-Comm/
|
|
29
|
+
# Contact: contact@karafka.io
|
|
30
|
+
|
|
31
|
+
module Karafka
|
|
32
|
+
module Pro
|
|
33
|
+
module Instrumentation
|
|
34
|
+
module ConsumerGroups
|
|
35
|
+
module LagCompensation
|
|
36
|
+
# Refreshes offsets and lags data of partitions that stayed paused for at least the
|
|
37
|
+
# configured pause age, at most once per interval.
|
|
38
|
+
#
|
|
39
|
+
# Runs on the listener threads via the `client.events_poll` event, so the fetching
|
|
40
|
+
# happens on the thread that owns the client connection within its group identity.
|
|
41
|
+
# Pause state is tracked via the `client.pause` and `client.resume` instrumentation
|
|
42
|
+
# events, which fire on the same thread.
|
|
43
|
+
#
|
|
44
|
+
# On resume or revocation the partition data is dropped immediately, so stale values
|
|
45
|
+
# never overlay live statistics and revoked partitions are never queried again.
|
|
46
|
+
# Errors never propagate and are only instrumented: the next attempt happens on the
|
|
47
|
+
# next interval anyway.
|
|
48
|
+
class Refresher
|
|
49
|
+
include Karafka::Core::Helpers::Time
|
|
50
|
+
include Helpers::ConfigImporter.new(
|
|
51
|
+
monitor: %i[monitor],
|
|
52
|
+
interval: %i[internal statistics consumer_groups lag_compensation interval],
|
|
53
|
+
pause_age: %i[internal statistics consumer_groups lag_compensation pause_age]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def initialize
|
|
57
|
+
@fetcher = Fetcher.new
|
|
58
|
+
@mutex = Mutex.new
|
|
59
|
+
@states = Hash.new do |states, subscription_group_id|
|
|
60
|
+
states[subscription_group_id] = {
|
|
61
|
+
# Gates the refreshing to run at most once per interval
|
|
62
|
+
runner: Helpers::IntervalRunner.new(interval: interval) do |client, state|
|
|
63
|
+
refresh_tick(client, state)
|
|
64
|
+
end,
|
|
65
|
+
paused: Hash.new { |paused, topic| paused[topic] = {} },
|
|
66
|
+
client_name: nil
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Tracks when a given partition got paused
|
|
72
|
+
#
|
|
73
|
+
# @param event [Karafka::Core::Monitoring::Event] pause event
|
|
74
|
+
def on_client_pause(event)
|
|
75
|
+
state = state_for(event[:subscription_group].id)
|
|
76
|
+
|
|
77
|
+
state[:paused][event[:topic]][event[:partition]] = monotonic_now
|
|
78
|
+
state[:client_name] = event[:caller].name
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Stops refreshing a resumed partition but keeps its last refreshed values in the
|
|
82
|
+
# registry. The compensator keeps overlaying them (its guard applies them only while
|
|
83
|
+
# still fresher than the live statistics), so the emitted statistics do not snap back
|
|
84
|
+
# to the frozen pre-resume values during the gap before librdkafka post-resume
|
|
85
|
+
# fetches update the offsets. Once the live values catch up the compensator stops
|
|
86
|
+
# using them, and the kept entries are dropped in bulk on the next rebalance.
|
|
87
|
+
#
|
|
88
|
+
# @param event [Karafka::Core::Monitoring::Event] resume event
|
|
89
|
+
def on_client_resume(event)
|
|
90
|
+
state = state_for(event[:subscription_group].id)
|
|
91
|
+
# Fetch with a fallback not to auto-build tracking of an untracked topic
|
|
92
|
+
partitions = state[:paused].fetch(event[:topic], nil)
|
|
93
|
+
|
|
94
|
+
return unless partitions
|
|
95
|
+
return unless partitions.delete(event[:partition])
|
|
96
|
+
|
|
97
|
+
state[:paused].delete(event[:topic]) if partitions.empty?
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Once per interval refreshes the data of all the partitions that stayed paused
|
|
101
|
+
# for at least the configured pause age
|
|
102
|
+
#
|
|
103
|
+
# @param event [Karafka::Core::Monitoring::Event] event with the client
|
|
104
|
+
def on_client_events_poll(event)
|
|
105
|
+
# Never run during shutdown or quieting: blocking broker queries at that time
|
|
106
|
+
# would eat into the shutdown time budget for no benefit
|
|
107
|
+
return if Karafka::App.done?
|
|
108
|
+
|
|
109
|
+
state = state_for(event[:subscription_group].id)
|
|
110
|
+
|
|
111
|
+
state[:runner].call(event[:caller], state)
|
|
112
|
+
rescue => e
|
|
113
|
+
# Refreshing is a best-effort operation: values stay stale until the next
|
|
114
|
+
# successful run on one of the following intervals. Errors are still
|
|
115
|
+
# instrumented, so persistent failures (missing ACLs, removed topics, etc)
|
|
116
|
+
# remain observable
|
|
117
|
+
monitor.instrument(
|
|
118
|
+
"error.occurred",
|
|
119
|
+
caller: self,
|
|
120
|
+
error: e,
|
|
121
|
+
type: "lag_compensation.refresher.error"
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Expires all the refreshed data and pause tracking of a client on a rebalance. Its
|
|
126
|
+
# paused partitions may no longer belong to it and tracking will refill only via
|
|
127
|
+
# real pause events of partitions this client still owns.
|
|
128
|
+
#
|
|
129
|
+
# @param event [Karafka::Core::Monitoring::Event] revocation event
|
|
130
|
+
def on_rebalance_partitions_revoked(event)
|
|
131
|
+
state = state_for(event[:subscription_group].id)
|
|
132
|
+
# Clear (instead of a reassignment) preserves the auto-building behavior
|
|
133
|
+
state[:paused].clear
|
|
134
|
+
|
|
135
|
+
client_name = state[:client_name]
|
|
136
|
+
|
|
137
|
+
Registry.instance.evict(client_name) if client_name
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
# @param subscription_group_id [String]
|
|
143
|
+
# @return [Hash] mutable state of a given subscription group. The mutex guards the
|
|
144
|
+
# cross-thread auto-building; aside from creation, the state is accessed only
|
|
145
|
+
# from the subscription group own listener thread.
|
|
146
|
+
def state_for(subscription_group_id)
|
|
147
|
+
@mutex.synchronize do
|
|
148
|
+
@states[subscription_group_id]
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Refreshes and stores the data of all the long-paused partitions (if any)
|
|
153
|
+
#
|
|
154
|
+
# @param client [Karafka::Connection::Client]
|
|
155
|
+
# @param state [Hash] subscription group state
|
|
156
|
+
def refresh_tick(client, state)
|
|
157
|
+
state[:client_name] = client.name
|
|
158
|
+
|
|
159
|
+
long_paused = long_paused_for(state)
|
|
160
|
+
|
|
161
|
+
return if long_paused.empty?
|
|
162
|
+
|
|
163
|
+
Registry.instance.update(client.name, @fetcher.call(client, long_paused))
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# @param state [Hash] subscription group state with pause timestamps
|
|
167
|
+
# @return [Hash{String => Array<Integer>}] partitions that stayed paused for at
|
|
168
|
+
# least the configured pause age
|
|
169
|
+
def long_paused_for(state)
|
|
170
|
+
threshold = monotonic_now - pause_age
|
|
171
|
+
result = {}
|
|
172
|
+
|
|
173
|
+
state[:paused].each do |topic, partitions|
|
|
174
|
+
long_paused = partitions.select { |_, at| at <= threshold }.keys
|
|
175
|
+
result[topic] = long_paused unless long_paused.empty?
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
result
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Karafka Pro - Source Available Commercial Software
|
|
4
|
+
# Copyright (c) 2017-present Maciej Mensfeld. All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# This software is NOT open source. It is source-available commercial software
|
|
7
|
+
# requiring a paid license for use. It is NOT covered by LGPL.
|
|
8
|
+
#
|
|
9
|
+
# The author retains all right, title, and interest in this software,
|
|
10
|
+
# including all copyrights, patents, and other intellectual property rights.
|
|
11
|
+
# No patent rights are granted under this license.
|
|
12
|
+
#
|
|
13
|
+
# PROHIBITED:
|
|
14
|
+
# - Use without a valid commercial license
|
|
15
|
+
# - Redistribution, modification, or derivative works without authorization
|
|
16
|
+
# - Reverse engineering, decompilation, or disassembly of this software
|
|
17
|
+
# - Use as training data for AI/ML models or inclusion in datasets
|
|
18
|
+
# - Scraping, crawling, or automated collection for any purpose
|
|
19
|
+
#
|
|
20
|
+
# PERMITTED:
|
|
21
|
+
# - Reading, referencing, and linking for personal or commercial use
|
|
22
|
+
# - Runtime retrieval by AI assistants, coding agents, and RAG systems
|
|
23
|
+
# for the purpose of providing contextual help to Karafka users
|
|
24
|
+
#
|
|
25
|
+
# Receipt, viewing, or possession of this software does not convey or
|
|
26
|
+
# imply any license or right beyond those expressly stated above.
|
|
27
|
+
#
|
|
28
|
+
# License: https://karafka.io/docs/Pro-License-Comm/
|
|
29
|
+
# Contact: contact@karafka.io
|
|
30
|
+
|
|
31
|
+
module Karafka
|
|
32
|
+
module Pro
|
|
33
|
+
module Instrumentation
|
|
34
|
+
module ConsumerGroups
|
|
35
|
+
# Active refreshing of watermarks and lags for long-paused partitions. librdkafka only
|
|
36
|
+
# updates those values from fetch responses, so partitions paused for a long time
|
|
37
|
+
# report frozen statistics. The refresher periodically fetches fresh values via the
|
|
38
|
+
# running consumer connection and the decorator overlays them onto the emitted
|
|
39
|
+
# statistics.
|
|
40
|
+
module LagCompensation
|
|
41
|
+
# Thread-safe storage for actively refreshed watermarks and lags of long-paused
|
|
42
|
+
# partitions. Written by the refresher on listener threads, read by the statistics
|
|
43
|
+
# decorator on the librdkafka callbacks thread.
|
|
44
|
+
#
|
|
45
|
+
# No age based expiry is needed: entries are refreshed while a partition stays paused,
|
|
46
|
+
# kept (but no longer refreshed) once it resumes so the compensator can hand over to
|
|
47
|
+
# the live statistics, and dropped in bulk on rebalances. Within an assignment the
|
|
48
|
+
# stored set is therefore bounded by the partitions the client paused.
|
|
49
|
+
class Registry
|
|
50
|
+
include Singleton
|
|
51
|
+
|
|
52
|
+
def initialize
|
|
53
|
+
@mutex = Mutex.new
|
|
54
|
+
@clients = {}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Merges the freshly refreshed offsets into the stored data of a client, overwriting
|
|
58
|
+
# the values of the refreshed partitions and keeping the previously stored ones.
|
|
59
|
+
#
|
|
60
|
+
# A refresh covers only the currently long-paused partitions, so a partition that
|
|
61
|
+
# resumed is absent here and its last value is kept rather than dropped: the
|
|
62
|
+
# compensator keeps overlaying it (its guard applies it only while still fresher than
|
|
63
|
+
# the live statistics) until librdkafka post-resume fetches catch up, avoiding a
|
|
64
|
+
# snap back to the frozen pre-resume values in the meantime. Stale entries are
|
|
65
|
+
# dropped in bulk on the next rebalance.
|
|
66
|
+
#
|
|
67
|
+
# @param client_name [String] rdkafka client name (matches statistics `name` field)
|
|
68
|
+
# @param data [Hash{String => Hash{Integer => Integer}}] topics with partitions
|
|
69
|
+
# with their end offsets
|
|
70
|
+
def update(client_name, data)
|
|
71
|
+
@mutex.synchronize do
|
|
72
|
+
stored = (@clients[client_name] ||= {})
|
|
73
|
+
|
|
74
|
+
data.each do |topic, partitions|
|
|
75
|
+
(stored[topic] ||= {}).merge!(partitions)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @param client_name [String] rdkafka client name
|
|
81
|
+
# @return [Hash, nil] refreshed data of a given client or nil when none
|
|
82
|
+
def fetch(client_name)
|
|
83
|
+
@mutex.synchronize do
|
|
84
|
+
@clients[client_name]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Removes all the data of a given client. Used on rebalances as partitions may no
|
|
89
|
+
# longer belong to the client that refreshed them.
|
|
90
|
+
#
|
|
91
|
+
# @param client_name [String] rdkafka client name
|
|
92
|
+
def evict(client_name)
|
|
93
|
+
@mutex.synchronize do
|
|
94
|
+
@clients.delete(client_name)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|