karafka 2.6.0.beta1 → 2.6.0.rc2
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 +66 -27
- data/certs/expired.txt +2 -0
- data/config/locales/errors.yml +5 -0
- data/config/locales/pro_errors.yml +5 -5
- 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 +20 -3
- 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 +34 -15
- 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/routing/features/pausing/config.rb +7 -11
- data/lib/karafka/pro/routing/features/pausing/contracts/topic.rb +6 -8
- data/lib/karafka/pro/routing/features/pausing/topic.rb +18 -63
- 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/features/pausing/config.rb +27 -0
- data/lib/karafka/routing/features/pausing.rb +13 -0
- data/lib/karafka/routing/subscription_group.rb +1 -1
- data/lib/karafka/routing/topic.rb +20 -7
- data/lib/karafka/routing/topics.rb +21 -1
- data/lib/karafka/server.rb +9 -0
- data/lib/karafka/setup/config.rb +52 -55
- 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 +14 -4
|
@@ -159,14 +159,16 @@ module Karafka
|
|
|
159
159
|
# )
|
|
160
160
|
#
|
|
161
161
|
# @example Read offsets from a specific point in time
|
|
162
|
-
# Karafka::Admin::Recovery.read_committed_offsets(
|
|
162
|
+
# Karafka::Admin::Recovery.read_committed_offsets(
|
|
163
|
+
# 'sync', last_committed_at: Time.new(2025, 3, 1)
|
|
164
|
+
# )
|
|
163
165
|
#
|
|
164
166
|
# @example Migrate a stuck consumer group to a new name (two-step workflow)
|
|
165
167
|
# # Step 1: Read committed offsets from the broken group (bypasses coordinator)
|
|
166
168
|
# offsets = Karafka::Admin::Recovery.read_committed_offsets('sync')
|
|
167
169
|
# #=> { 'events' => { 0 => 1400, 1 => 1402 }, 'orders' => { 0 => 890 } }
|
|
168
170
|
#
|
|
169
|
-
# # Step 2: Inspect the recovered offsets
|
|
171
|
+
# # Step 2: Inspect the recovered offsets - verify all expected topics and partitions
|
|
170
172
|
# # are present and the offset values look reasonable before committing them
|
|
171
173
|
#
|
|
172
174
|
# # Step 3: Write the offsets to the target group using standard Admin APIs
|
|
@@ -193,11 +195,11 @@ module Karafka
|
|
|
193
195
|
next unless parsed[:group] == group_id
|
|
194
196
|
|
|
195
197
|
if parsed[:offset].nil?
|
|
196
|
-
# Tombstone
|
|
198
|
+
# Tombstone - offset was deleted, remove from results
|
|
197
199
|
committed[parsed[:topic]].delete(parsed[:partition])
|
|
198
200
|
committed.delete(parsed[:topic]) if committed[parsed[:topic]].empty?
|
|
199
201
|
else
|
|
200
|
-
# Last write wins
|
|
202
|
+
# Last write wins - scanning forward means we naturally end up with the most
|
|
201
203
|
# recent commit per partition
|
|
202
204
|
committed[parsed[:topic]][parsed[:partition]] = parsed[:offset]
|
|
203
205
|
end
|
|
@@ -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
|