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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +5 -5
  3. data/.github/workflows/push.yml +2 -3
  4. data/CHANGELOG.md +16 -1
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +20 -20
  7. data/bin/integrations +1 -0
  8. data/config/locales/errors.yml +2 -1
  9. data/docker-compose.yml +1 -1
  10. data/examples/payloads/avro/.gitkeep +0 -0
  11. data/karafka.gemspec +2 -2
  12. data/lib/karafka/active_job/job_extensions.rb +4 -1
  13. data/lib/karafka/admin.rb +27 -15
  14. data/lib/karafka/connection/client.rb +9 -0
  15. data/lib/karafka/connection/proxy.rb +1 -1
  16. data/lib/karafka/contracts/base.rb +3 -2
  17. data/lib/karafka/contracts/config.rb +2 -1
  18. data/lib/karafka/helpers/interval_runner.rb +8 -0
  19. data/lib/karafka/instrumentation/logger_listener.rb +11 -11
  20. data/lib/karafka/instrumentation/vendors/kubernetes/base_listener.rb +17 -2
  21. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +29 -6
  22. data/lib/karafka/instrumentation/vendors/kubernetes/swarm_liveness_listener.rb +9 -0
  23. data/lib/karafka/pro/encryption.rb +4 -1
  24. data/lib/karafka/pro/processing/coordinators/errors_tracker.rb +5 -0
  25. data/lib/karafka/pro/processing/strategies/dlq/default.rb +4 -3
  26. data/lib/karafka/pro/recurring_tasks.rb +8 -2
  27. data/lib/karafka/pro/routing/features/swarm/contracts/routing.rb +3 -2
  28. data/lib/karafka/pro/routing/features/swarm.rb +4 -1
  29. data/lib/karafka/pro/scheduled_messages/consumer.rb +45 -8
  30. data/lib/karafka/pro/scheduled_messages/dispatcher.rb +2 -1
  31. data/lib/karafka/pro/scheduled_messages/proxy.rb +15 -3
  32. data/lib/karafka/pro/scheduled_messages/serializer.rb +2 -4
  33. data/lib/karafka/pro/scheduled_messages/state.rb +20 -23
  34. data/lib/karafka/pro/scheduled_messages/tracker.rb +34 -8
  35. data/lib/karafka/pro/scheduled_messages.rb +4 -1
  36. data/lib/karafka/routing/builder.rb +12 -3
  37. data/lib/karafka/routing/features/base/expander.rb +8 -2
  38. data/lib/karafka/server.rb +4 -1
  39. data/lib/karafka/setup/config.rb +17 -5
  40. data/lib/karafka/swarm/supervisor.rb +5 -2
  41. data/lib/karafka/version.rb +1 -1
  42. metadata +10 -9
  43. /data/examples/payloads/json/{enrollment_event.json → sample_set_01/enrollment_event.json} +0 -0
  44. /data/examples/payloads/json/{ingestion_event.json → sample_set_01/ingestion_event.json} +0 -0
  45. /data/examples/payloads/json/{transaction_event.json → sample_set_01/transaction_event.json} +0 -0
  46. /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 true, it indicates unrecoverable error like fencing
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 = true
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 @pollings.values.any? { |tick| (time - tick) > @polling_ttl }
149
- return false if @consumptions.values.any? { |tick| (time - tick) > @consuming_ttl }
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!(config.to_h)
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: source_partition,
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
- 'source_key' => skippable_message.raw_key.to_s,
160
- 'source_attempts' => attempt.to_s
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!(task.to_h)
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!(config.to_h)
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
- def validate!(builder)
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!(config.internal.routing.builder)
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(nil)
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
- @state.loaded!
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.track(message) if time >= @today.starts_at
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(false)
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: 'state',
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!(message, WaterDrop::Errors::MessageInvalidError)
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!(proxy_message)
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!(proxy_message, WaterDrop::Errors::MessageInvalidError)
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
- state: tracker.state,
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
- # @param loaded [nil, false, true] is the state loaded or not yet. `nil` indicates, it is
20
- # a fresh, pre-seek state.
21
- def initialize(loaded = nil)
22
- @loaded = loaded
23
- end
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
- # @return [Boolean] are we in a fresh, pre-bootstrap state
26
- def fresh?
27
- @loaded.nil?
28
- end
28
+ private_constant :STATES
29
29
 
30
- # Marks the current state as fully loaded
31
- def loaded!
32
- @loaded = true
30
+ def initialize
31
+ @state = 'fresh'
33
32
  end
34
33
 
35
- # @return [Boolean] are we in a loaded state
36
- def loaded?
37
- @loaded == true
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
- case @loaded
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
- @created_at = Time.now.to_i
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
- # Accurate (because coming from daily buffer) number of things to schedule
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(@created_at)] = sum
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 track(message)
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
- RecurringTasks::Contracts::Config.new.validate!(config.to_h)
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!(map(&:to_h))
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!(consumer_group.to_h)
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!(topic.to_h)
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!(consumer_group.to_h)
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!(topic.to_h)
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
 
@@ -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!(activity_manager.to_h)
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
@@ -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
- setting :max_wait_time, default: 1_000
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
- # How many times should be try. 1 000 ms x 60 => 60 seconds wait in total and then we give
137
- # up on pending operations
138
- setting :max_attempts, default: 60
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!(config.to_h)
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!(activity_manager.to_h)
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 existing forcefully with any dead child process still existing
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
 
@@ -3,5 +3,5 @@
3
3
  # Main module namespace
4
4
  module Karafka
5
5
  # Current Karafka version
6
- VERSION = '2.5.0.beta2'
6
+ VERSION = '2.5.0.rc2'
7
7
  end