karafka 2.5.0.beta2 → 2.5.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/.github/workflows/ci.yml +5 -5
- data/.github/workflows/push.yml +2 -3
- data/CHANGELOG.md +16 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +20 -20
- data/bin/integrations +1 -0
- data/config/locales/errors.yml +2 -1
- data/docker-compose.yml +1 -1
- data/examples/payloads/avro/.gitkeep +0 -0
- data/karafka.gemspec +2 -2
- data/lib/karafka/active_job/job_extensions.rb +4 -1
- data/lib/karafka/admin.rb +27 -15
- data/lib/karafka/connection/client.rb +9 -0
- data/lib/karafka/connection/proxy.rb +1 -1
- data/lib/karafka/contracts/base.rb +3 -2
- data/lib/karafka/contracts/config.rb +2 -1
- data/lib/karafka/helpers/interval_runner.rb +8 -0
- data/lib/karafka/instrumentation/logger_listener.rb +11 -11
- data/lib/karafka/instrumentation/vendors/kubernetes/base_listener.rb +17 -2
- data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +29 -6
- data/lib/karafka/instrumentation/vendors/kubernetes/swarm_liveness_listener.rb +9 -0
- data/lib/karafka/pro/encryption.rb +4 -1
- data/lib/karafka/pro/processing/coordinators/errors_tracker.rb +5 -0
- data/lib/karafka/pro/processing/strategies/dlq/default.rb +4 -3
- data/lib/karafka/pro/recurring_tasks.rb +8 -2
- data/lib/karafka/pro/routing/features/swarm/contracts/routing.rb +3 -2
- data/lib/karafka/pro/routing/features/swarm.rb +4 -1
- data/lib/karafka/pro/scheduled_messages/consumer.rb +45 -8
- data/lib/karafka/pro/scheduled_messages/dispatcher.rb +2 -1
- data/lib/karafka/pro/scheduled_messages/proxy.rb +15 -3
- data/lib/karafka/pro/scheduled_messages/serializer.rb +2 -4
- data/lib/karafka/pro/scheduled_messages/state.rb +20 -23
- data/lib/karafka/pro/scheduled_messages/tracker.rb +34 -8
- data/lib/karafka/pro/scheduled_messages.rb +4 -1
- data/lib/karafka/routing/builder.rb +12 -3
- data/lib/karafka/routing/features/base/expander.rb +8 -2
- data/lib/karafka/server.rb +4 -1
- data/lib/karafka/setup/config.rb +17 -5
- data/lib/karafka/swarm/supervisor.rb +5 -2
- data/lib/karafka/version.rb +1 -1
- metadata +10 -9
- /data/examples/payloads/json/{enrollment_event.json → sample_set_01/enrollment_event.json} +0 -0
- /data/examples/payloads/json/{ingestion_event.json → sample_set_01/ingestion_event.json} +0 -0
- /data/examples/payloads/json/{transaction_event.json → sample_set_01/transaction_event.json} +0 -0
- /data/examples/payloads/json/{user_event.json → sample_set_01/user_event.json} +0 -0
@@ -53,7 +53,7 @@ module Karafka
|
|
53
53
|
consuming_ttl: 5 * 60 * 1_000,
|
54
54
|
polling_ttl: 5 * 60 * 1_000
|
55
55
|
)
|
56
|
-
# If this is set to
|
56
|
+
# If this is set to a symbol, it indicates unrecoverable error like fencing
|
57
57
|
# While fencing can be partial (for one of the SGs), we still should consider this
|
58
58
|
# as an undesired state for the whole process because it halts processing in a
|
59
59
|
# non-recoverable manner forever
|
@@ -116,7 +116,7 @@ module Karafka
|
|
116
116
|
# We mark as unrecoverable only on certain errors that will not be fixed by retrying
|
117
117
|
return unless UNRECOVERABLE_RDKAFKA_ERRORS.include?(error.code)
|
118
118
|
|
119
|
-
@unrecoverable =
|
119
|
+
@unrecoverable = error.code
|
120
120
|
end
|
121
121
|
|
122
122
|
# Deregister the polling tracker for given listener
|
@@ -142,17 +142,29 @@ module Karafka
|
|
142
142
|
# Did we exceed any of the ttls
|
143
143
|
# @return [String] 204 string if ok, 500 otherwise
|
144
144
|
def healthy?
|
145
|
-
time = monotonic_now
|
146
|
-
|
147
145
|
return false if @unrecoverable
|
148
|
-
return false if
|
149
|
-
return false if
|
146
|
+
return false if polling_ttl_exceeded?
|
147
|
+
return false if consuming_ttl_exceeded?
|
150
148
|
|
151
149
|
true
|
152
150
|
end
|
153
151
|
|
154
152
|
private
|
155
153
|
|
154
|
+
# @return [Boolean] true if the consumer exceeded the polling ttl
|
155
|
+
def polling_ttl_exceeded?
|
156
|
+
time = monotonic_now
|
157
|
+
|
158
|
+
@pollings.values.any? { |tick| (time - tick) > @polling_ttl }
|
159
|
+
end
|
160
|
+
|
161
|
+
# @return [Boolean] true if the consumer exceeded the consuming ttl
|
162
|
+
def consuming_ttl_exceeded?
|
163
|
+
time = monotonic_now
|
164
|
+
|
165
|
+
@consumptions.values.any? { |tick| (time - tick) > @consuming_ttl }
|
166
|
+
end
|
167
|
+
|
156
168
|
# Wraps the logic with a mutex
|
157
169
|
# @param block [Proc] code we want to run in mutex
|
158
170
|
def synchronize(&block)
|
@@ -191,6 +203,17 @@ module Karafka
|
|
191
203
|
@consumptions.delete(thread_id)
|
192
204
|
end
|
193
205
|
end
|
206
|
+
|
207
|
+
# @return [Hash] response body status
|
208
|
+
def status_body
|
209
|
+
super.merge!(
|
210
|
+
errors: {
|
211
|
+
polling_ttl_exceeded: polling_ttl_exceeded?,
|
212
|
+
consumption_ttl_exceeded: consuming_ttl_exceeded?,
|
213
|
+
unrecoverable: @unrecoverable
|
214
|
+
}
|
215
|
+
)
|
216
|
+
end
|
194
217
|
end
|
195
218
|
end
|
196
219
|
end
|
@@ -47,6 +47,15 @@ module Karafka
|
|
47
47
|
def healthy?
|
48
48
|
(monotonic_now - @controlling) < @controlling_ttl
|
49
49
|
end
|
50
|
+
|
51
|
+
# @return [Hash] response body status
|
52
|
+
def status_body
|
53
|
+
super.merge!(
|
54
|
+
errors: {
|
55
|
+
controlling_ttl_exceeded: !healthy?
|
56
|
+
}
|
57
|
+
)
|
58
|
+
end
|
50
59
|
end
|
51
60
|
end
|
52
61
|
end
|
@@ -22,7 +22,10 @@ module Karafka
|
|
22
22
|
|
23
23
|
# @param config [Karafka::Core::Configurable::Node] root node config
|
24
24
|
def post_setup(config)
|
25
|
-
Encryption::Contracts::Config.new.validate!(
|
25
|
+
Encryption::Contracts::Config.new.validate!(
|
26
|
+
config.to_h,
|
27
|
+
scope: %w[config]
|
28
|
+
)
|
26
29
|
|
27
30
|
# Don't inject extra components if encryption is not active
|
28
31
|
return unless config.encryption.active
|
@@ -22,6 +22,9 @@ module Karafka
|
|
22
22
|
# @return [Hash]
|
23
23
|
attr_reader :counts
|
24
24
|
|
25
|
+
# @return [String]
|
26
|
+
attr_reader :trace_id
|
27
|
+
|
25
28
|
# Max errors we keep in memory.
|
26
29
|
# We do not want to keep more because for DLQ-less this would cause memory-leaks.
|
27
30
|
# We do however count per class for granular error counting
|
@@ -41,6 +44,7 @@ module Karafka
|
|
41
44
|
@topic = topic
|
42
45
|
@partition = partition
|
43
46
|
@limit = limit
|
47
|
+
@trace_id = SecureRandom.uuid
|
44
48
|
end
|
45
49
|
|
46
50
|
# Clears all the errors
|
@@ -54,6 +58,7 @@ module Karafka
|
|
54
58
|
@errors.shift if @errors.size >= @limit
|
55
59
|
@errors << error
|
56
60
|
@counts[error.class] += 1
|
61
|
+
@trace_id = SecureRandom.uuid
|
57
62
|
end
|
58
63
|
|
59
64
|
# @return [Boolean] is the error tracker empty
|
@@ -149,15 +149,16 @@ module Karafka
|
|
149
149
|
|
150
150
|
dlq_message = {
|
151
151
|
topic: @_dispatch_to_dlq_topic || topic.dead_letter_queue.topic,
|
152
|
-
key:
|
152
|
+
key: skippable_message.raw_key,
|
153
|
+
partition_key: source_partition,
|
153
154
|
payload: skippable_message.raw_payload,
|
154
155
|
headers: skippable_message.raw_headers.merge(
|
155
156
|
'source_topic' => topic.name,
|
156
157
|
'source_partition' => source_partition,
|
157
158
|
'source_offset' => skippable_message.offset.to_s,
|
158
159
|
'source_consumer_group' => topic.consumer_group.id,
|
159
|
-
'
|
160
|
-
'
|
160
|
+
'source_attempts' => attempt.to_s,
|
161
|
+
'source_trace_id' => errors_tracker.trace_id
|
161
162
|
)
|
162
163
|
}
|
163
164
|
|
@@ -29,7 +29,10 @@ module Karafka
|
|
29
29
|
@schedule.instance_exec(&block)
|
30
30
|
|
31
31
|
@schedule.each do |task|
|
32
|
-
Contracts::Task.new.validate!(
|
32
|
+
Contracts::Task.new.validate!(
|
33
|
+
task.to_h,
|
34
|
+
scope: ['recurring_tasks', task.id]
|
35
|
+
)
|
33
36
|
end
|
34
37
|
|
35
38
|
@schedule
|
@@ -59,7 +62,10 @@ module Karafka
|
|
59
62
|
|
60
63
|
# @param config [Karafka::Core::Configurable::Node] root node config
|
61
64
|
def post_setup(config)
|
62
|
-
RecurringTasks::Contracts::Config.new.validate!(
|
65
|
+
RecurringTasks::Contracts::Config.new.validate!(
|
66
|
+
config.to_h,
|
67
|
+
scope: %w[config]
|
68
|
+
)
|
63
69
|
|
64
70
|
# Published after task is successfully executed
|
65
71
|
Karafka.monitor.notifications_bus.register_event('recurring_tasks.task.executed')
|
@@ -28,7 +28,8 @@ module Karafka
|
|
28
28
|
# Validates that each node has at least one assignment.
|
29
29
|
#
|
30
30
|
# @param builder [Karafka::Routing::Builder]
|
31
|
-
|
31
|
+
# @param scope [Array<String>]
|
32
|
+
def validate!(builder, scope: [])
|
32
33
|
nodes_setup = Hash.new do |h, node_id|
|
33
34
|
h[node_id] = { active: false, node_id: node_id }
|
34
35
|
end
|
@@ -49,7 +50,7 @@ module Karafka
|
|
49
50
|
end
|
50
51
|
|
51
52
|
nodes_setup.each_value do |details|
|
52
|
-
super(details)
|
53
|
+
super(details, scope: scope)
|
53
54
|
end
|
54
55
|
end
|
55
56
|
|
@@ -17,7 +17,10 @@ module Karafka
|
|
17
17
|
# @param config [Karafka::Core::Configurable::Node] app config
|
18
18
|
def post_setup(config)
|
19
19
|
config.monitor.subscribe('app.before_warmup') do
|
20
|
-
Contracts::Routing.new.validate!(
|
20
|
+
Contracts::Routing.new.validate!(
|
21
|
+
config.internal.routing.builder,
|
22
|
+
scope: %w[swarm]
|
23
|
+
)
|
21
24
|
end
|
22
25
|
end
|
23
26
|
end
|
@@ -12,13 +12,23 @@ module Karafka
|
|
12
12
|
dispatcher_class: %i[scheduled_messages dispatcher_class]
|
13
13
|
)
|
14
14
|
|
15
|
+
# In case there is an extremely high turnover of messages, EOF may never kick in,
|
16
|
+
# effectively not changing status from loading to loaded. We use the time consumer instance
|
17
|
+
# was created + a buffer time to detect such a case (loading + messages from the time it
|
18
|
+
# was already running) to switch the state despite no EOF
|
19
|
+
# This is in seconds
|
20
|
+
GRACE_PERIOD = 15
|
21
|
+
|
22
|
+
private_constant :GRACE_PERIOD
|
23
|
+
|
15
24
|
# Prepares the initial state of all stateful components
|
16
25
|
def initialized
|
17
26
|
clear!
|
18
27
|
# Max epoch is always moving forward with the time. Never backwards, hence we do not
|
19
28
|
# reset it at all.
|
20
29
|
@max_epoch = MaxEpoch.new
|
21
|
-
@state = State.new
|
30
|
+
@state = State.new
|
31
|
+
@reloads = 0
|
22
32
|
end
|
23
33
|
|
24
34
|
# Processes messages and runs dispatch (via tick) if needed
|
@@ -27,11 +37,25 @@ module Karafka
|
|
27
37
|
|
28
38
|
messages.each do |message|
|
29
39
|
SchemaValidator.call(message)
|
40
|
+
|
41
|
+
# We always track offsets of messages, even if they would be later on skipped or
|
42
|
+
# ignored for any reason. That way we have debug info that is useful once in a while.
|
43
|
+
@tracker.offsets(message)
|
44
|
+
|
30
45
|
process_message(message)
|
31
46
|
end
|
32
47
|
|
33
48
|
@states_reporter.call
|
34
49
|
|
50
|
+
recent_timestamp = messages.last.timestamp.to_i
|
51
|
+
post_started_timestamp = @tracker.started_at + GRACE_PERIOD
|
52
|
+
|
53
|
+
# If we started getting messages that are beyond the current time, it means we have
|
54
|
+
# loaded enough to start scheduling. The upcoming messages are from the future looking
|
55
|
+
# from perspective of the current consumer start. We add a bit of grace period not to
|
56
|
+
# deal with edge cases
|
57
|
+
loaded! if @state.loading? && recent_timestamp > post_started_timestamp
|
58
|
+
|
35
59
|
eofed if eofed?
|
36
60
|
|
37
61
|
# Unless given day data is fully loaded we should not dispatch any notifications nor
|
@@ -55,11 +79,7 @@ module Karafka
|
|
55
79
|
return if reload!
|
56
80
|
|
57
81
|
# If end of the partition is reached, it always means all data is loaded
|
58
|
-
|
59
|
-
|
60
|
-
tags.add(:state, @state.to_s)
|
61
|
-
|
62
|
-
@states_reporter.call
|
82
|
+
loaded!
|
63
83
|
end
|
64
84
|
|
65
85
|
# Performs periodic operations when no new data is provided to the topic partition
|
@@ -90,6 +110,12 @@ module Karafka
|
|
90
110
|
@states_reporter.call
|
91
111
|
end
|
92
112
|
|
113
|
+
# Move the state to shutdown and publish immediately
|
114
|
+
def shutdown
|
115
|
+
@state.stopped!
|
116
|
+
@states_reporter.call!
|
117
|
+
end
|
118
|
+
|
93
119
|
private
|
94
120
|
|
95
121
|
# Takes each message and adds it to the daily accumulator if needed or performs other
|
@@ -104,7 +130,7 @@ module Karafka
|
|
104
130
|
time = message.headers['schedule_target_epoch']
|
105
131
|
|
106
132
|
# Do not track historical below today as those will be reflected in the daily buffer
|
107
|
-
@tracker.
|
133
|
+
@tracker.future(message) if time >= @today.starts_at
|
108
134
|
|
109
135
|
if time > @today.ends_at || time < @max_epoch.to_i
|
110
136
|
# Clean the message immediately when not needed (won't be scheduled) to preserve
|
@@ -132,6 +158,7 @@ module Karafka
|
|
132
158
|
# If this is a new assignment we always need to seek from beginning to load the data
|
133
159
|
if @state.fresh?
|
134
160
|
clear!
|
161
|
+
@reloads += 1
|
135
162
|
seek(:earliest)
|
136
163
|
|
137
164
|
return true
|
@@ -143,6 +170,7 @@ module Karafka
|
|
143
170
|
# If day has ended we reload and start new day with new schedules
|
144
171
|
if @today.ended?
|
145
172
|
clear!
|
173
|
+
@reloads += 1
|
146
174
|
seek(:earliest)
|
147
175
|
|
148
176
|
return true
|
@@ -151,6 +179,13 @@ module Karafka
|
|
151
179
|
false
|
152
180
|
end
|
153
181
|
|
182
|
+
# Moves the state to loaded and publishes the state update
|
183
|
+
def loaded!
|
184
|
+
@state.loaded!
|
185
|
+
tags.add(:state, @state.to_s)
|
186
|
+
@states_reporter.call!
|
187
|
+
end
|
188
|
+
|
154
189
|
# Resets all buffers and states so we can start a new day with a clean slate
|
155
190
|
# We can fully recreate the dispatcher because any undispatched messages will be dispatched
|
156
191
|
# with the new day dispatcher after it is reloaded.
|
@@ -158,11 +193,13 @@ module Karafka
|
|
158
193
|
@daily_buffer = DailyBuffer.new
|
159
194
|
@today = Day.new
|
160
195
|
@tracker = Tracker.new
|
161
|
-
@state = State.new
|
196
|
+
@state = State.new
|
197
|
+
@state.loading!
|
162
198
|
@dispatcher = dispatcher_class.new(topic.name, partition)
|
163
199
|
@states_reporter = Helpers::IntervalRunner.new do
|
164
200
|
@tracker.today = @daily_buffer.size
|
165
201
|
@tracker.state = @state.to_s
|
202
|
+
@tracker.reloads = @reloads
|
166
203
|
|
167
204
|
@dispatcher.state(@tracker)
|
168
205
|
end
|
@@ -70,7 +70,8 @@ module Karafka
|
|
70
70
|
config.producer.produce_async(
|
71
71
|
topic: "#{@topic}#{config.states_postfix}",
|
72
72
|
payload: @serializer.state(tracker),
|
73
|
-
key
|
73
|
+
# We use the state as a key, so we always have one state transition data available
|
74
|
+
key: "#{tracker.state}_state",
|
74
75
|
partition: @partition,
|
75
76
|
headers: { 'zlib' => 'true' }
|
76
77
|
)
|
@@ -60,7 +60,11 @@ module Karafka
|
|
60
60
|
# We need to ensure that the message we want to proxy is fully legit. Otherwise, since
|
61
61
|
# we envelope details like target topic, we could end up having incorrect data to
|
62
62
|
# schedule
|
63
|
-
MSG_CONTRACT.validate!(
|
63
|
+
MSG_CONTRACT.validate!(
|
64
|
+
message,
|
65
|
+
WaterDrop::Errors::MessageInvalidError,
|
66
|
+
scope: %w[scheduled_messages message]
|
67
|
+
)
|
64
68
|
|
65
69
|
headers = (message[:headers] || {}).merge(
|
66
70
|
'schedule_schema_version' => ScheduledMessages::SCHEMA_VERSION,
|
@@ -166,9 +170,17 @@ module Karafka
|
|
166
170
|
# complies with our requirements
|
167
171
|
# @param proxy_message [Hash] our message envelope
|
168
172
|
def validate!(proxy_message)
|
169
|
-
POST_CONTRACT.validate!(
|
173
|
+
POST_CONTRACT.validate!(
|
174
|
+
proxy_message,
|
175
|
+
scope: %w[scheduled_messages message]
|
176
|
+
)
|
177
|
+
|
170
178
|
# After proxy specific validations we also ensure, that the final form is correct
|
171
|
-
MSG_CONTRACT.validate!(
|
179
|
+
MSG_CONTRACT.validate!(
|
180
|
+
proxy_message,
|
181
|
+
WaterDrop::Errors::MessageInvalidError,
|
182
|
+
scope: %w[scheduled_messages message]
|
183
|
+
)
|
172
184
|
end
|
173
185
|
end
|
174
186
|
end
|
@@ -16,10 +16,8 @@ module Karafka
|
|
16
16
|
def state(tracker)
|
17
17
|
data = {
|
18
18
|
schema_version: ScheduledMessages::STATES_SCHEMA_VERSION,
|
19
|
-
dispatched_at: float_now
|
20
|
-
|
21
|
-
daily: tracker.daily
|
22
|
-
}
|
19
|
+
dispatched_at: float_now
|
20
|
+
}.merge(tracker.to_h)
|
23
21
|
|
24
22
|
compress(
|
25
23
|
serialize(data)
|
@@ -15,38 +15,35 @@ module Karafka
|
|
15
15
|
# - loaded - state in which we finished loading all the schedules and we can dispatch
|
16
16
|
# messages when the time comes and we can process real-time incoming schedules and
|
17
17
|
# changes to schedules as they appear in the stream.
|
18
|
+
# - shutdown - the states are no longer available as the consumer has shut down
|
18
19
|
class State
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
# Available states scheduling of messages may be in
|
21
|
+
STATES = %w[
|
22
|
+
fresh
|
23
|
+
loading
|
24
|
+
loaded
|
25
|
+
stopped
|
26
|
+
].freeze
|
24
27
|
|
25
|
-
|
26
|
-
def fresh?
|
27
|
-
@loaded.nil?
|
28
|
-
end
|
28
|
+
private_constant :STATES
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
@loaded = true
|
30
|
+
def initialize
|
31
|
+
@state = 'fresh'
|
33
32
|
end
|
34
33
|
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
STATES.each do |state|
|
35
|
+
define_method :"#{state}!" do
|
36
|
+
@state = state
|
37
|
+
end
|
38
|
+
|
39
|
+
define_method :"#{state}?" do
|
40
|
+
@state == state
|
41
|
+
end
|
38
42
|
end
|
39
43
|
|
40
44
|
# @return [String] current state string representation
|
41
45
|
def to_s
|
42
|
-
|
43
|
-
when nil
|
44
|
-
'fresh'
|
45
|
-
when false
|
46
|
-
'loading'
|
47
|
-
when true
|
48
|
-
'loaded'
|
49
|
-
end
|
46
|
+
@state
|
50
47
|
end
|
51
48
|
end
|
52
49
|
end
|
@@ -10,25 +10,40 @@ module Karafka
|
|
10
10
|
#
|
11
11
|
# It provides accurate today dispatch taken from daily buffer and estimates for future days
|
12
12
|
class Tracker
|
13
|
-
# @return [Hash<String, Integer>]
|
14
|
-
attr_reader :daily
|
15
|
-
|
16
13
|
# @return [String] current state
|
17
14
|
attr_accessor :state
|
18
15
|
|
16
|
+
attr_writer :reloads
|
17
|
+
|
18
|
+
# @return [Integer] time epoch when this tracker was started
|
19
|
+
attr_reader :started_at
|
20
|
+
|
19
21
|
def initialize
|
20
22
|
@daily = Hash.new { |h, k| h[k] = 0 }
|
21
|
-
@
|
23
|
+
@started_at = Time.now.to_i
|
24
|
+
@offsets = { low: -1, high: -1 }
|
25
|
+
@state = 'fresh'
|
26
|
+
@reloads = 0
|
22
27
|
end
|
23
28
|
|
24
|
-
#
|
29
|
+
# Tracks offsets of visited messages
|
30
|
+
#
|
31
|
+
# @param message [Karafka::Messages::Message]
|
32
|
+
def offsets(message)
|
33
|
+
message_offset = message.offset
|
34
|
+
|
35
|
+
@offsets[:low] = message_offset if @offsets[:low].negative?
|
36
|
+
@offsets[:high] = message.offset
|
37
|
+
end
|
38
|
+
|
39
|
+
# Accurate (because coming from daily buffer) number of things to schedule daily
|
25
40
|
#
|
26
41
|
# @param sum [Integer]
|
27
42
|
def today=(sum)
|
28
|
-
@daily[epoch_to_date(@
|
43
|
+
@daily[epoch_to_date(@started_at)] = sum
|
29
44
|
end
|
30
45
|
|
31
|
-
# Tracks message dispatch
|
46
|
+
# Tracks future message dispatch
|
32
47
|
#
|
33
48
|
# It is only relevant for future days as for today we use accurate metrics from the daily
|
34
49
|
# buffer
|
@@ -37,12 +52,23 @@ module Karafka
|
|
37
52
|
# tombstone message. Tombstone messages cancellations are not tracked because it would
|
38
53
|
# drastically increase complexity. For given day we use the accurate counter and for
|
39
54
|
# future days we use estimates.
|
40
|
-
def
|
55
|
+
def future(message)
|
41
56
|
epoch = message.headers['schedule_target_epoch']
|
42
57
|
|
43
58
|
@daily[epoch_to_date(epoch)] += 1
|
44
59
|
end
|
45
60
|
|
61
|
+
# @return [Hash] hash with details that we want to expose
|
62
|
+
def to_h
|
63
|
+
{
|
64
|
+
state: @state,
|
65
|
+
offsets: @offsets,
|
66
|
+
daily: @daily,
|
67
|
+
started_at: @started_at,
|
68
|
+
reloads: @reloads
|
69
|
+
}.freeze
|
70
|
+
end
|
71
|
+
|
46
72
|
private
|
47
73
|
|
48
74
|
# @param epoch [Integer] epoch time
|
@@ -51,7 +51,10 @@ module Karafka
|
|
51
51
|
|
52
52
|
# @param config [Karafka::Core::Configurable::Node] root node config
|
53
53
|
def post_setup(config)
|
54
|
-
|
54
|
+
ScheduledMessages::Contracts::Config.new.validate!(
|
55
|
+
config.to_h,
|
56
|
+
scope: %w[config]
|
57
|
+
)
|
55
58
|
end
|
56
59
|
|
57
60
|
# Basically since we may have custom producers configured that are not the same as the
|
@@ -50,15 +50,24 @@ module Karafka
|
|
50
50
|
|
51
51
|
# Ensures high-level routing details consistency
|
52
52
|
# Contains checks that require knowledge about all the consumer groups to operate
|
53
|
-
Contracts::Routing.new.validate!(
|
53
|
+
Contracts::Routing.new.validate!(
|
54
|
+
map(&:to_h),
|
55
|
+
scope: %w[routes]
|
56
|
+
)
|
54
57
|
|
55
58
|
each do |consumer_group|
|
56
59
|
# Validate consumer group settings
|
57
|
-
Contracts::ConsumerGroup.new.validate!(
|
60
|
+
Contracts::ConsumerGroup.new.validate!(
|
61
|
+
consumer_group.to_h,
|
62
|
+
scope: ['routes', consumer_group.name]
|
63
|
+
)
|
58
64
|
|
59
65
|
# and then its topics settings
|
60
66
|
consumer_group.topics.each do |topic|
|
61
|
-
Contracts::Topic.new.validate!(
|
67
|
+
Contracts::Topic.new.validate!(
|
68
|
+
topic.to_h,
|
69
|
+
scope: ['routes', consumer_group.name, topic.name]
|
70
|
+
)
|
62
71
|
end
|
63
72
|
|
64
73
|
# Initialize subscription groups after all the routing is done
|
@@ -38,13 +38,19 @@ module Karafka
|
|
38
38
|
|
39
39
|
each do |consumer_group|
|
40
40
|
if scope::Contracts.const_defined?('ConsumerGroup', false)
|
41
|
-
scope::Contracts::ConsumerGroup.new.validate!(
|
41
|
+
scope::Contracts::ConsumerGroup.new.validate!(
|
42
|
+
consumer_group.to_h,
|
43
|
+
scope: ['routes', consumer_group.name]
|
44
|
+
)
|
42
45
|
end
|
43
46
|
|
44
47
|
next unless scope::Contracts.const_defined?('Topic', false)
|
45
48
|
|
46
49
|
consumer_group.topics.each do |topic|
|
47
|
-
scope::Contracts::Topic.new.validate!(
|
50
|
+
scope::Contracts::Topic.new.validate!(
|
51
|
+
topic.to_h,
|
52
|
+
scope: ['routes', consumer_group.name, topic.name]
|
53
|
+
)
|
48
54
|
end
|
49
55
|
end
|
50
56
|
|
data/lib/karafka/server.rb
CHANGED
@@ -51,7 +51,10 @@ module Karafka
|
|
51
51
|
# embedded
|
52
52
|
# We cannot validate this during the start because config needs to be populated and routes
|
53
53
|
# need to be defined.
|
54
|
-
cli_contract.validate!(
|
54
|
+
cli_contract.validate!(
|
55
|
+
activity_manager.to_h,
|
56
|
+
scope: %w[cli]
|
57
|
+
)
|
55
58
|
|
56
59
|
# We clear as we do not want parent handlers in case of working from fork
|
57
60
|
process.clear
|
data/lib/karafka/setup/config.rb
CHANGED
@@ -131,11 +131,20 @@ module Karafka
|
|
131
131
|
# option max_wait_time [Integer] We wait only for this amount of time before raising error
|
132
132
|
# as we intercept this error and retry after checking that the operation was finished or
|
133
133
|
# failed using external factor.
|
134
|
-
|
134
|
+
#
|
135
|
+
# For async this will finish immediately but for sync operations this will wait and we
|
136
|
+
# will get a confirmation. 60 seconds is ok for both cases as for async, the re-wait will
|
137
|
+
# kick in
|
138
|
+
setting :max_wait_time, default: 60 * 1_000
|
139
|
+
|
140
|
+
# How long should we wait on admin operation retrying before giving up and raising an
|
141
|
+
# error that result is not visible
|
142
|
+
setting :max_retries_duration, default: 60_000
|
135
143
|
|
136
|
-
#
|
137
|
-
#
|
138
|
-
|
144
|
+
# In case of fast-finished async work, this `retry_backoff` help us not re-query Kafka
|
145
|
+
# too fast after previous call to check the async operation results. Basically prevents
|
146
|
+
# us from spamming metadata requests to Kafka in a loop
|
147
|
+
setting :retry_backoff, default: 500
|
139
148
|
|
140
149
|
# option poll_timeout [Integer] time in ms
|
141
150
|
# How long should a poll wait before yielding on no results (rdkafka-ruby setting)
|
@@ -352,7 +361,10 @@ module Karafka
|
|
352
361
|
|
353
362
|
configure(&block)
|
354
363
|
|
355
|
-
Contracts::Config.new.validate!(
|
364
|
+
Contracts::Config.new.validate!(
|
365
|
+
config.to_h,
|
366
|
+
scope: %w[config]
|
367
|
+
)
|
356
368
|
|
357
369
|
configure_components
|
358
370
|
|
@@ -42,7 +42,10 @@ module Karafka
|
|
42
42
|
# Creates needed number of forks, installs signals and starts supervision
|
43
43
|
def run
|
44
44
|
# Validate the CLI provided options the same way as we do for the regular server
|
45
|
-
cli_contract.validate!(
|
45
|
+
cli_contract.validate!(
|
46
|
+
activity_manager.to_h,
|
47
|
+
scope: %w[swarm cli]
|
48
|
+
)
|
46
49
|
|
47
50
|
# Close producer just in case. While it should not be used, we do not want even a
|
48
51
|
# theoretical case since librdkafka is not thread-safe.
|
@@ -154,7 +157,7 @@ module Karafka
|
|
154
157
|
# Run forceful kill
|
155
158
|
manager.terminate
|
156
159
|
# And wait until linux kills them
|
157
|
-
# This prevents us from
|
160
|
+
# This prevents us from exiting forcefully with any dead child process still existing
|
158
161
|
# Since we have sent the `KILL` signal, it must die, so we can wait until all dead
|
159
162
|
sleep(supervision_sleep) until manager.stopped?
|
160
163
|
|
data/lib/karafka/version.rb
CHANGED