karafka 2.2.13 → 2.3.0.alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci.yml +38 -12
- data/.ruby-version +1 -1
- data/CHANGELOG.md +161 -125
- data/Gemfile.lock +12 -12
- data/README.md +0 -2
- data/SECURITY.md +23 -0
- data/config/locales/errors.yml +7 -1
- data/config/locales/pro_errors.yml +22 -0
- data/docker-compose.yml +3 -1
- data/karafka.gemspec +2 -2
- data/lib/karafka/admin/acl.rb +287 -0
- data/lib/karafka/admin.rb +118 -16
- data/lib/karafka/app.rb +12 -3
- data/lib/karafka/base_consumer.rb +32 -31
- data/lib/karafka/cli/base.rb +1 -1
- data/lib/karafka/connection/client.rb +94 -84
- data/lib/karafka/connection/conductor.rb +28 -0
- data/lib/karafka/connection/listener.rb +165 -46
- data/lib/karafka/connection/listeners_batch.rb +5 -11
- data/lib/karafka/connection/manager.rb +72 -0
- data/lib/karafka/connection/messages_buffer.rb +12 -0
- data/lib/karafka/connection/proxy.rb +17 -0
- data/lib/karafka/connection/status.rb +75 -0
- data/lib/karafka/contracts/config.rb +14 -10
- data/lib/karafka/contracts/consumer_group.rb +9 -1
- data/lib/karafka/contracts/topic.rb +3 -1
- data/lib/karafka/errors.rb +13 -0
- data/lib/karafka/instrumentation/assignments_tracker.rb +96 -0
- data/lib/karafka/instrumentation/callbacks/rebalance.rb +10 -7
- data/lib/karafka/instrumentation/logger_listener.rb +3 -9
- data/lib/karafka/instrumentation/notifications.rb +19 -9
- data/lib/karafka/instrumentation/vendors/appsignal/metrics_listener.rb +31 -28
- data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +22 -3
- data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +15 -12
- data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +39 -36
- data/lib/karafka/pro/base_consumer.rb +47 -0
- data/lib/karafka/pro/connection/manager.rb +300 -0
- data/lib/karafka/pro/connection/multiplexing/listener.rb +40 -0
- data/lib/karafka/pro/instrumentation/performance_tracker.rb +85 -0
- data/lib/karafka/pro/iterator/tpl_builder.rb +1 -1
- data/lib/karafka/pro/iterator.rb +1 -6
- data/lib/karafka/pro/loader.rb +16 -2
- data/lib/karafka/pro/processing/coordinator.rb +2 -1
- data/lib/karafka/pro/processing/executor.rb +37 -0
- data/lib/karafka/pro/processing/expansions_selector.rb +32 -0
- data/lib/karafka/pro/processing/jobs/periodic.rb +41 -0
- data/lib/karafka/pro/processing/jobs/periodic_non_blocking.rb +32 -0
- data/lib/karafka/pro/processing/jobs_builder.rb +14 -3
- data/lib/karafka/pro/processing/offset_metadata/consumer.rb +44 -0
- data/lib/karafka/pro/processing/offset_metadata/fetcher.rb +131 -0
- data/lib/karafka/pro/processing/offset_metadata/listener.rb +46 -0
- data/lib/karafka/pro/processing/schedulers/base.rb +143 -0
- data/lib/karafka/pro/processing/schedulers/default.rb +107 -0
- data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +1 -1
- data/lib/karafka/pro/processing/strategies/default.rb +136 -3
- data/lib/karafka/pro/processing/strategies/dlq/default.rb +35 -0
- data/lib/karafka/pro/processing/strategies/lrj/default.rb +1 -1
- data/lib/karafka/pro/processing/strategies/lrj/mom.rb +1 -1
- data/lib/karafka/pro/processing/strategies/vp/default.rb +60 -26
- data/lib/karafka/pro/processing/virtual_offset_manager.rb +41 -11
- data/lib/karafka/pro/routing/features/long_running_job/topic.rb +2 -0
- data/lib/karafka/pro/routing/features/multiplexing/config.rb +38 -0
- data/lib/karafka/pro/routing/features/multiplexing/contracts/topic.rb +114 -0
- data/lib/karafka/pro/routing/features/multiplexing/patches/contracts/consumer_group.rb +42 -0
- data/lib/karafka/pro/routing/features/multiplexing/proxy.rb +38 -0
- data/lib/karafka/pro/routing/features/multiplexing/subscription_group.rb +42 -0
- data/lib/karafka/pro/routing/features/multiplexing/subscription_groups_builder.rb +40 -0
- data/lib/karafka/pro/routing/features/multiplexing.rb +59 -0
- data/lib/karafka/pro/routing/features/non_blocking_job/topic.rb +32 -0
- data/lib/karafka/pro/routing/features/non_blocking_job.rb +37 -0
- data/lib/karafka/pro/routing/features/offset_metadata/config.rb +33 -0
- data/lib/karafka/pro/routing/features/offset_metadata/contracts/topic.rb +42 -0
- data/lib/karafka/pro/routing/features/offset_metadata/topic.rb +65 -0
- data/lib/karafka/pro/routing/features/offset_metadata.rb +40 -0
- data/lib/karafka/pro/routing/features/patterns/contracts/consumer_group.rb +4 -0
- data/lib/karafka/pro/routing/features/patterns/detector.rb +18 -10
- data/lib/karafka/pro/routing/features/periodic_job/config.rb +37 -0
- data/lib/karafka/pro/routing/features/periodic_job/contracts/topic.rb +44 -0
- data/lib/karafka/pro/routing/features/periodic_job/topic.rb +94 -0
- data/lib/karafka/pro/routing/features/periodic_job.rb +27 -0
- data/lib/karafka/pro/routing/features/virtual_partitions/config.rb +1 -0
- data/lib/karafka/pro/routing/features/virtual_partitions/contracts/topic.rb +1 -0
- data/lib/karafka/pro/routing/features/virtual_partitions/topic.rb +7 -2
- data/lib/karafka/process.rb +5 -3
- data/lib/karafka/processing/coordinator.rb +5 -1
- data/lib/karafka/processing/executor.rb +43 -13
- data/lib/karafka/processing/executors_buffer.rb +22 -7
- data/lib/karafka/processing/jobs/base.rb +19 -2
- data/lib/karafka/processing/jobs/consume.rb +3 -3
- data/lib/karafka/processing/jobs/idle.rb +5 -0
- data/lib/karafka/processing/jobs/revoked.rb +5 -0
- data/lib/karafka/processing/jobs/shutdown.rb +5 -0
- data/lib/karafka/processing/jobs_queue.rb +19 -8
- data/lib/karafka/processing/schedulers/default.rb +42 -0
- data/lib/karafka/processing/strategies/base.rb +13 -4
- data/lib/karafka/processing/strategies/default.rb +23 -7
- data/lib/karafka/processing/strategies/dlq.rb +36 -0
- data/lib/karafka/processing/worker.rb +4 -1
- data/lib/karafka/routing/builder.rb +12 -2
- data/lib/karafka/routing/consumer_group.rb +5 -5
- data/lib/karafka/routing/features/base.rb +44 -8
- data/lib/karafka/routing/features/dead_letter_queue/config.rb +6 -1
- data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +1 -0
- data/lib/karafka/routing/features/dead_letter_queue/topic.rb +9 -2
- data/lib/karafka/routing/proxy.rb +4 -3
- data/lib/karafka/routing/subscription_group.rb +2 -2
- data/lib/karafka/routing/subscription_groups_builder.rb +11 -2
- data/lib/karafka/routing/topic.rb +8 -10
- data/lib/karafka/routing/topics.rb +1 -1
- data/lib/karafka/runner.rb +13 -3
- data/lib/karafka/server.rb +5 -9
- data/lib/karafka/setup/config.rb +21 -1
- data/lib/karafka/status.rb +23 -14
- data/lib/karafka/templates/karafka.rb.erb +7 -0
- data/lib/karafka/time_trackers/partition_usage.rb +56 -0
- data/lib/karafka/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +47 -13
- metadata.gz.sig +0 -0
- data/lib/karafka/connection/consumer_group_coordinator.rb +0 -48
- data/lib/karafka/pro/performance_tracker.rb +0 -84
- data/lib/karafka/pro/processing/scheduler.rb +0 -74
- data/lib/karafka/processing/scheduler.rb +0 -38
data/lib/karafka/app.rb
CHANGED
|
@@ -36,6 +36,13 @@ module Karafka
|
|
|
36
36
|
# Just a nicer name for the consumer groups
|
|
37
37
|
alias routes consumer_groups
|
|
38
38
|
|
|
39
|
+
# Returns current assignments of this process. Both topics and partitions
|
|
40
|
+
#
|
|
41
|
+
# @return [Hash<Karafka::Routing::Topic, Array<Integer>>]
|
|
42
|
+
def assignments
|
|
43
|
+
Instrumentation::AssignmentsTracker.instance.current
|
|
44
|
+
end
|
|
45
|
+
|
|
39
46
|
# Allow for easier status management via `Karafka::App` by aliasing status methods here
|
|
40
47
|
Status::STATES.each do |state, transition|
|
|
41
48
|
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
@@ -68,9 +75,11 @@ module Karafka
|
|
|
68
75
|
monitor
|
|
69
76
|
pro?
|
|
70
77
|
].each do |delegated|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
79
|
+
def #{delegated}
|
|
80
|
+
Karafka.#{delegated}
|
|
81
|
+
end
|
|
82
|
+
RUBY
|
|
74
83
|
end
|
|
75
84
|
end
|
|
76
85
|
end
|
|
@@ -11,6 +11,9 @@ module Karafka
|
|
|
11
11
|
|
|
12
12
|
def_delegators :@coordinator, :topic, :partition
|
|
13
13
|
|
|
14
|
+
def_delegators :producer, :produce_async, :produce_sync, :produce_many_async,
|
|
15
|
+
:produce_many_sync
|
|
16
|
+
|
|
14
17
|
# @return [String] id of the current consumer
|
|
15
18
|
attr_reader :id
|
|
16
19
|
# @return [Karafka::Routing::Topic] topic to which a given consumer is subscribed
|
|
@@ -34,16 +37,9 @@ module Karafka
|
|
|
34
37
|
# @note This should not be used by the end users as it is part of the lifecycle of things and
|
|
35
38
|
# not as a part of the public api. This should not perform any extensive operations as it is
|
|
36
39
|
# blocking and running in the listener thread.
|
|
37
|
-
def
|
|
40
|
+
def on_before_schedule_consume
|
|
38
41
|
@used = true
|
|
39
|
-
|
|
40
|
-
rescue StandardError => e
|
|
41
|
-
Karafka.monitor.instrument(
|
|
42
|
-
'error.occurred',
|
|
43
|
-
error: e,
|
|
44
|
-
caller: self,
|
|
45
|
-
type: 'consumer.before_enqueue.error'
|
|
46
|
-
)
|
|
42
|
+
handle_before_schedule_consume
|
|
47
43
|
end
|
|
48
44
|
|
|
49
45
|
# Can be used to run preparation code in the worker
|
|
@@ -59,13 +55,6 @@ module Karafka
|
|
|
59
55
|
# We run this after the full metadata setup, so we can use all the messages information
|
|
60
56
|
# if needed
|
|
61
57
|
handle_before_consume
|
|
62
|
-
rescue StandardError => e
|
|
63
|
-
Karafka.monitor.instrument(
|
|
64
|
-
'error.occurred',
|
|
65
|
-
error: e,
|
|
66
|
-
caller: self,
|
|
67
|
-
type: 'consumer.before_consume.error'
|
|
68
|
-
)
|
|
69
58
|
end
|
|
70
59
|
|
|
71
60
|
# Executes the default consumer flow.
|
|
@@ -94,13 +83,13 @@ module Karafka
|
|
|
94
83
|
# not as part of the public api.
|
|
95
84
|
def on_after_consume
|
|
96
85
|
handle_after_consume
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Can be used to run code prior to scheduling of idle execution
|
|
89
|
+
#
|
|
90
|
+
# @private
|
|
91
|
+
def on_before_schedule_idle
|
|
92
|
+
handle_before_schedule_idle
|
|
104
93
|
end
|
|
105
94
|
|
|
106
95
|
# Trigger method for running on idle runs without messages
|
|
@@ -108,13 +97,13 @@ module Karafka
|
|
|
108
97
|
# @private
|
|
109
98
|
def on_idle
|
|
110
99
|
handle_idle
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Can be used to run code prior to scheduling of revoked execution
|
|
103
|
+
#
|
|
104
|
+
# @private
|
|
105
|
+
def on_before_schedule_revoked
|
|
106
|
+
handle_before_schedule_revoked
|
|
118
107
|
end
|
|
119
108
|
|
|
120
109
|
# Trigger method for running on partition revocation.
|
|
@@ -131,6 +120,13 @@ module Karafka
|
|
|
131
120
|
)
|
|
132
121
|
end
|
|
133
122
|
|
|
123
|
+
# Can be used to run code prior to scheduling of revoked execution
|
|
124
|
+
#
|
|
125
|
+
# @private
|
|
126
|
+
def on_before_schedule_shutdown
|
|
127
|
+
handle_before_schedule_shutdown
|
|
128
|
+
end
|
|
129
|
+
|
|
134
130
|
# Trigger method for running on shutdown.
|
|
135
131
|
#
|
|
136
132
|
# @private
|
|
@@ -226,9 +222,14 @@ module Karafka
|
|
|
226
222
|
# @param manual_seek [Boolean] Flag to differentiate between user seek and system/strategy
|
|
227
223
|
# based seek. User seek operations should take precedence over system actions, hence we need
|
|
228
224
|
# to know who invoked it.
|
|
225
|
+
# @param reset_offset [Boolean] should we reset offset when seeking backwards. It is false by
|
|
226
|
+
# default to prevent marking in the offset that was earlier than the highest marked offset
|
|
227
|
+
# for given consumer group. It can be set to true if we want to reprocess data once again and
|
|
228
|
+
# want to make sure that the marking starts from where we moved to.
|
|
229
229
|
# @note Please note, that if you are seeking to a time offset, getting the offset is blocking
|
|
230
|
-
def seek(offset, manual_seek = true)
|
|
230
|
+
def seek(offset, manual_seek = true, reset_offset: false)
|
|
231
231
|
coordinator.manual_seek if manual_seek
|
|
232
|
+
coordinator.seek_offset = nil if reset_offset
|
|
232
233
|
|
|
233
234
|
client.seek(
|
|
234
235
|
Karafka::Messages::Seek.new(
|
data/lib/karafka/cli/base.rb
CHANGED
|
@@ -10,6 +10,10 @@ module Karafka
|
|
|
10
10
|
class Client
|
|
11
11
|
attr_reader :rebalance_manager
|
|
12
12
|
|
|
13
|
+
# @return [Karafka::Routing::SubscriptionGroup] subscription group to which this client
|
|
14
|
+
# belongs to
|
|
15
|
+
attr_reader :subscription_group
|
|
16
|
+
|
|
13
17
|
# @return [String] underlying consumer name
|
|
14
18
|
# @note Consumer name may change in case we regenerate it
|
|
15
19
|
attr_reader :name
|
|
@@ -20,16 +24,7 @@ module Karafka
|
|
|
20
24
|
# How many times should we retry polling in case of a failure
|
|
21
25
|
MAX_POLL_RETRIES = 20
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
# This applies only to a case when a short-lived Karafka instance with a client would be
|
|
25
|
-
# closed before first rebalance. Mitigates a librdkafka bug.
|
|
26
|
-
COOPERATIVE_STICKY_MAX_WAIT = 60_000
|
|
27
|
-
|
|
28
|
-
# We want to make sure we never close several clients in the same moment to prevent
|
|
29
|
-
# potential race conditions and other issues
|
|
30
|
-
SHUTDOWN_MUTEX = Mutex.new
|
|
31
|
-
|
|
32
|
-
private_constant :MAX_POLL_RETRIES, :SHUTDOWN_MUTEX, :COOPERATIVE_STICKY_MAX_WAIT
|
|
27
|
+
private_constant :MAX_POLL_RETRIES
|
|
33
28
|
|
|
34
29
|
# Creates a new consumer instance.
|
|
35
30
|
#
|
|
@@ -45,12 +40,8 @@ module Karafka
|
|
|
45
40
|
@buffer = RawMessagesBuffer.new
|
|
46
41
|
@tick_interval = ::Karafka::App.config.internal.tick_interval
|
|
47
42
|
@rebalance_manager = RebalanceManager.new(@subscription_group.id)
|
|
48
|
-
@rebalance_callback = Instrumentation::Callbacks::Rebalance.new(
|
|
49
|
-
@subscription_group.id,
|
|
50
|
-
@subscription_group.consumer_group.id
|
|
51
|
-
)
|
|
43
|
+
@rebalance_callback = Instrumentation::Callbacks::Rebalance.new(@subscription_group)
|
|
52
44
|
@events_poller = Helpers::IntervalRunner.new { events_poll }
|
|
53
|
-
@kafka = build_consumer
|
|
54
45
|
# There are few operations that can happen in parallel from the listener threads as well
|
|
55
46
|
# as from the workers. They are not fully thread-safe because they may be composed out of
|
|
56
47
|
# few calls to Kafka or out of few internal state changes. That is why we mutex them.
|
|
@@ -125,13 +116,19 @@ module Karafka
|
|
|
125
116
|
# Stores offset for a given partition of a given topic based on the provided message.
|
|
126
117
|
#
|
|
127
118
|
# @param message [Karafka::Messages::Message]
|
|
128
|
-
|
|
129
|
-
|
|
119
|
+
# @param offset_metadata [String, nil] offset storage metadata or nil if none
|
|
120
|
+
def store_offset(message, offset_metadata = nil)
|
|
121
|
+
internal_store_offset(message, offset_metadata)
|
|
130
122
|
end
|
|
131
123
|
|
|
132
124
|
# @return [Boolean] true if our current assignment has been lost involuntarily.
|
|
133
125
|
def assignment_lost?
|
|
134
|
-
|
|
126
|
+
kafka.assignment_lost?
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @return [Rdkafka::Consumer::TopicPartitionList] current active assignment
|
|
130
|
+
def assignment
|
|
131
|
+
kafka.assignment
|
|
135
132
|
end
|
|
136
133
|
|
|
137
134
|
# Commits the offset on a current consumer in a non-blocking or blocking way.
|
|
@@ -202,7 +199,7 @@ module Karafka
|
|
|
202
199
|
|
|
203
200
|
@paused_tpls[topic][partition] = tpl
|
|
204
201
|
|
|
205
|
-
|
|
202
|
+
kafka.pause(tpl)
|
|
206
203
|
|
|
207
204
|
# If offset is not provided, will pause where it finished.
|
|
208
205
|
# This makes librdkafka not purge buffers and can provide significant network savings
|
|
@@ -243,43 +240,23 @@ module Karafka
|
|
|
243
240
|
partition: partition
|
|
244
241
|
)
|
|
245
242
|
|
|
246
|
-
|
|
243
|
+
kafka.resume(tpl)
|
|
247
244
|
end
|
|
248
245
|
end
|
|
249
246
|
|
|
250
247
|
# Gracefully stops topic consumption.
|
|
251
|
-
#
|
|
252
|
-
# @note Stopping running consumers without a really important reason is not recommended
|
|
253
|
-
# as until all the consumers are stopped, the server will keep running serving only
|
|
254
|
-
# part of the messages
|
|
255
248
|
def stop
|
|
256
|
-
#
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
# extensive time period.
|
|
261
|
-
#
|
|
262
|
-
# @see https://github.com/confluentinc/librdkafka/issues/4312
|
|
249
|
+
# In case of cooperative-sticky, there is a bug in librdkafka that may hang it.
|
|
250
|
+
# To mitigate it we first need to unsubscribe so we will not receive any assignments and
|
|
251
|
+
# only then we should be good to go.
|
|
252
|
+
# @see https://github.com/confluentinc/librdkafka/issues/4527
|
|
263
253
|
if @subscription_group.kafka[:'partition.assignment.strategy'] == 'cooperative-sticky'
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
(COOPERATIVE_STICKY_MAX_WAIT / 100).times do
|
|
267
|
-
# If we're past the first rebalance, no need to wait
|
|
268
|
-
if @rebalance_manager.active?
|
|
269
|
-
# We give it a a bit of time because librdkafka has a tendency to do some-post
|
|
270
|
-
# callback work that from its perspective is still under rebalance
|
|
271
|
-
sleep(5) if active_wait
|
|
272
|
-
|
|
273
|
-
break
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
active_wait = true
|
|
277
|
-
|
|
278
|
-
# poll to trigger potential rebalances that could occur during stopping and to trigger
|
|
279
|
-
# potential callbacks
|
|
280
|
-
poll(100)
|
|
254
|
+
unsubscribe
|
|
281
255
|
|
|
256
|
+
until assignment.empty?
|
|
282
257
|
sleep(0.1)
|
|
258
|
+
|
|
259
|
+
ping
|
|
283
260
|
end
|
|
284
261
|
end
|
|
285
262
|
|
|
@@ -288,33 +265,40 @@ module Karafka
|
|
|
288
265
|
|
|
289
266
|
# Marks given message as consumed.
|
|
290
267
|
#
|
|
291
|
-
# @param [Karafka::Messages::Message] message that we want to mark as processed
|
|
268
|
+
# @param message [Karafka::Messages::Message] message that we want to mark as processed
|
|
269
|
+
# @param metadata [String, nil] offset storage metadata or nil if none
|
|
292
270
|
# @return [Boolean] true if successful. False if we no longer own given partition
|
|
293
271
|
# @note This method won't trigger automatic offsets commits, rather relying on the offset
|
|
294
272
|
# check-pointing trigger that happens with each batch processed. It will however check the
|
|
295
273
|
# `librdkafka` assignment ownership to increase accuracy for involuntary revocations.
|
|
296
|
-
def mark_as_consumed(message)
|
|
297
|
-
store_offset(message) && !assignment_lost?
|
|
274
|
+
def mark_as_consumed(message, metadata = nil)
|
|
275
|
+
store_offset(message, metadata) && !assignment_lost?
|
|
298
276
|
end
|
|
299
277
|
|
|
300
278
|
# Marks a given message as consumed and commits the offsets in a blocking way.
|
|
301
279
|
#
|
|
302
|
-
# @param [Karafka::Messages::Message] message that we want to mark as processed
|
|
280
|
+
# @param message [Karafka::Messages::Message] message that we want to mark as processed
|
|
281
|
+
# @param metadata [String, nil] offset storage metadata or nil if none
|
|
303
282
|
# @return [Boolean] true if successful. False if we no longer own given partition
|
|
304
|
-
def mark_as_consumed!(message)
|
|
305
|
-
return false unless mark_as_consumed(message)
|
|
283
|
+
def mark_as_consumed!(message, metadata = nil)
|
|
284
|
+
return false unless mark_as_consumed(message, metadata)
|
|
306
285
|
|
|
307
286
|
commit_offsets!
|
|
308
287
|
end
|
|
309
288
|
|
|
310
289
|
# Closes and resets the client completely.
|
|
311
290
|
def reset
|
|
312
|
-
|
|
291
|
+
Karafka.monitor.instrument(
|
|
292
|
+
'client.reset',
|
|
293
|
+
caller: self,
|
|
294
|
+
subscription_group: @subscription_group
|
|
295
|
+
) do
|
|
296
|
+
close
|
|
313
297
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
298
|
+
@events_poller.reset
|
|
299
|
+
@closed = false
|
|
300
|
+
@paused_tpls.clear
|
|
301
|
+
end
|
|
318
302
|
end
|
|
319
303
|
|
|
320
304
|
# Runs a single poll on the main queue and consumer queue ignoring all the potential errors
|
|
@@ -340,7 +324,27 @@ module Karafka
|
|
|
340
324
|
# @note It is non-blocking when timeout 0 and will not wait if queue empty. It costs up to
|
|
341
325
|
# 2ms when no callbacks are triggered.
|
|
342
326
|
def events_poll(timeout = 0)
|
|
343
|
-
|
|
327
|
+
kafka.events_poll(timeout)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Returns pointer to the consumer group metadata. It is used only in the context of
|
|
331
|
+
# exactly-once-semantics in transactions, this is why it is never remapped to Ruby
|
|
332
|
+
# @return [FFI::Pointer]
|
|
333
|
+
def consumer_group_metadata_pointer
|
|
334
|
+
kafka.consumer_group_metadata_pointer
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Return the current committed offset per partition for this consumer group.
|
|
338
|
+
# The offset field of each requested partition will either be set to stored offset or to
|
|
339
|
+
# -1001 in case there was no stored offset for that partition.
|
|
340
|
+
#
|
|
341
|
+
# @param tpl [Rdkafka::Consumer::TopicPartitionList] for which we want to get committed
|
|
342
|
+
# @return [Rdkafka::Consumer::TopicPartitionList]
|
|
343
|
+
# @raise [Rdkafka::RdkafkaError] When getting the committed positions fails.
|
|
344
|
+
# @note It is recommended to use this only on rebalances to get positions with metadata
|
|
345
|
+
# when working with metadata as this is synchronous
|
|
346
|
+
def committed(tpl = nil)
|
|
347
|
+
Proxy.new(kafka).committed(tpl)
|
|
344
348
|
end
|
|
345
349
|
|
|
346
350
|
private
|
|
@@ -349,9 +353,10 @@ module Karafka
|
|
|
349
353
|
#
|
|
350
354
|
# Non thread-safe offset storing method
|
|
351
355
|
# @param message [Karafka::Messages::Message]
|
|
356
|
+
# @param metadata [String, nil] offset storage metadata or nil if none
|
|
352
357
|
# @return [Boolean] true if we could store the offset (if we still own the partition)
|
|
353
|
-
def internal_store_offset(message)
|
|
354
|
-
|
|
358
|
+
def internal_store_offset(message, metadata)
|
|
359
|
+
kafka.store_offset(message, metadata)
|
|
355
360
|
true
|
|
356
361
|
rescue Rdkafka::RdkafkaError => e
|
|
357
362
|
return false if e.code == :assignment_lost
|
|
@@ -367,7 +372,7 @@ module Karafka
|
|
|
367
372
|
# even when no stored, because with sync commit, it refreshes the ownership state of the
|
|
368
373
|
# consumer in a sync way.
|
|
369
374
|
def internal_commit_offsets(async: true)
|
|
370
|
-
|
|
375
|
+
kafka.commit(nil, async)
|
|
371
376
|
|
|
372
377
|
true
|
|
373
378
|
rescue Rdkafka::RdkafkaError => e
|
|
@@ -404,7 +409,7 @@ module Karafka
|
|
|
404
409
|
message.partition => message.offset
|
|
405
410
|
)
|
|
406
411
|
|
|
407
|
-
proxy = Proxy.new(
|
|
412
|
+
proxy = Proxy.new(kafka)
|
|
408
413
|
|
|
409
414
|
# Now we can overwrite the seek message offset with our resolved offset and we can
|
|
410
415
|
# then seek to the appropriate message
|
|
@@ -426,29 +431,29 @@ module Karafka
|
|
|
426
431
|
# seeking and pausing
|
|
427
432
|
return if message.offset == topic_partition_position(message.topic, message.partition)
|
|
428
433
|
|
|
429
|
-
|
|
434
|
+
kafka.seek(message)
|
|
430
435
|
end
|
|
431
436
|
|
|
432
437
|
# Commits the stored offsets in a sync way and closes the consumer.
|
|
433
438
|
def close
|
|
434
|
-
#
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
# and triggers this from a different thread
|
|
439
|
-
return if @closed
|
|
439
|
+
# Once client is closed, we should not close it again
|
|
440
|
+
# This could only happen in case of a race-condition when forceful shutdown happens
|
|
441
|
+
# and triggers this from a different thread
|
|
442
|
+
return if @closed
|
|
440
443
|
|
|
441
|
-
|
|
444
|
+
@closed = true
|
|
442
445
|
|
|
443
|
-
|
|
444
|
-
::Karafka::Core::Instrumentation.statistics_callbacks.delete(@subscription_group.id)
|
|
445
|
-
::Karafka::Core::Instrumentation.error_callbacks.delete(@subscription_group.id)
|
|
446
|
+
return unless @kafka
|
|
446
447
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
448
|
+
# Remove callbacks runners that were registered
|
|
449
|
+
::Karafka::Core::Instrumentation.statistics_callbacks.delete(@subscription_group.id)
|
|
450
|
+
::Karafka::Core::Instrumentation.error_callbacks.delete(@subscription_group.id)
|
|
451
|
+
|
|
452
|
+
kafka.close
|
|
453
|
+
@kafka = nil
|
|
454
|
+
@buffer.clear
|
|
455
|
+
# @note We do not clear rebalance manager here as we may still have revocation info
|
|
456
|
+
# here that we want to consider valid prior to running another reconnection
|
|
452
457
|
end
|
|
453
458
|
|
|
454
459
|
# Unsubscribes from all the subscriptions
|
|
@@ -456,7 +461,7 @@ module Karafka
|
|
|
456
461
|
# @note We do not re-raise since this is supposed to be only used on close and can be safely
|
|
457
462
|
# ignored. We do however want to instrument on it
|
|
458
463
|
def unsubscribe
|
|
459
|
-
|
|
464
|
+
kafka.unsubscribe
|
|
460
465
|
rescue ::Rdkafka::RdkafkaError => e
|
|
461
466
|
Karafka.monitor.instrument(
|
|
462
467
|
'error.occurred',
|
|
@@ -470,7 +475,7 @@ module Karafka
|
|
|
470
475
|
# @param partition [Integer]
|
|
471
476
|
# @return [Rdkafka::Consumer::TopicPartitionList]
|
|
472
477
|
def topic_partition_list(topic, partition)
|
|
473
|
-
rdkafka_partition =
|
|
478
|
+
rdkafka_partition = kafka
|
|
474
479
|
.assignment
|
|
475
480
|
.to_h[topic]
|
|
476
481
|
&.detect { |part| part.partition == partition }
|
|
@@ -489,7 +494,7 @@ module Karafka
|
|
|
489
494
|
rd_partition = ::Rdkafka::Consumer::Partition.new(partition, nil, 0)
|
|
490
495
|
tpl = ::Rdkafka::Consumer::TopicPartitionList.new(topic => [rd_partition])
|
|
491
496
|
|
|
492
|
-
|
|
497
|
+
kafka.position(tpl).to_h.fetch(topic).first.offset || -1
|
|
493
498
|
end
|
|
494
499
|
|
|
495
500
|
# Performs a single poll operation and handles retries and errors
|
|
@@ -517,7 +522,7 @@ module Karafka
|
|
|
517
522
|
# blocking events from being handled.
|
|
518
523
|
poll_tick = timeout > @tick_interval ? @tick_interval : timeout
|
|
519
524
|
|
|
520
|
-
result =
|
|
525
|
+
result = kafka.poll(poll_tick)
|
|
521
526
|
|
|
522
527
|
# If we've got a message, we can return it
|
|
523
528
|
return result if result
|
|
@@ -644,6 +649,11 @@ module Karafka
|
|
|
644
649
|
|
|
645
650
|
@buffer.uniq!
|
|
646
651
|
end
|
|
652
|
+
|
|
653
|
+
# @return [Rdkafka::Consumer] librdkafka consumer instance
|
|
654
|
+
def kafka
|
|
655
|
+
@kafka ||= build_consumer
|
|
656
|
+
end
|
|
647
657
|
end
|
|
648
658
|
end
|
|
649
659
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Connection
|
|
5
|
+
# Conductor is responsible for time orchestration of listeners manager.
|
|
6
|
+
# It blocks when manager is not needed as there were no state changes that could cause any
|
|
7
|
+
# listeners config changes and unblocks when things change or when certain time passed.
|
|
8
|
+
# The time based unblocking allows for building of complex managers that could be state aware
|
|
9
|
+
class Conductor
|
|
10
|
+
# @param max_interval [Integer] after how many milliseconds of doing nothing should we wake
|
|
11
|
+
# up the manager despite no state changes
|
|
12
|
+
def initialize(max_interval = 30_000)
|
|
13
|
+
@lock = RUBY_VERSION < '3.2' ? Processing::TimedQueue.new : Queue.new
|
|
14
|
+
@timeout = max_interval / 1_000.0
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Waits in a blocking way until it is time to manage listeners
|
|
18
|
+
def wait
|
|
19
|
+
@lock.pop(timeout: @timeout)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Releases wait lock on state change
|
|
23
|
+
def signal
|
|
24
|
+
@lock << true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|