karafka 2.2.13 → 2.3.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|