karafka 2.0.0.alpha6 → 2.0.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +42 -2
  5. data/Gemfile.lock +9 -9
  6. data/bin/integrations +36 -14
  7. data/bin/scenario +29 -0
  8. data/config/errors.yml +1 -0
  9. data/docker-compose.yml +3 -0
  10. data/karafka.gemspec +1 -1
  11. data/lib/active_job/karafka.rb +2 -2
  12. data/lib/karafka/active_job/routing/extensions.rb +31 -0
  13. data/lib/karafka/base_consumer.rb +74 -6
  14. data/lib/karafka/connection/client.rb +39 -16
  15. data/lib/karafka/connection/listener.rb +103 -34
  16. data/lib/karafka/connection/listeners_batch.rb +24 -0
  17. data/lib/karafka/connection/messages_buffer.rb +48 -61
  18. data/lib/karafka/connection/pauses_manager.rb +2 -2
  19. data/lib/karafka/connection/raw_messages_buffer.rb +101 -0
  20. data/lib/karafka/contracts/config.rb +10 -1
  21. data/lib/karafka/helpers/async.rb +33 -0
  22. data/lib/karafka/instrumentation/logger_listener.rb +37 -10
  23. data/lib/karafka/instrumentation/monitor.rb +4 -0
  24. data/lib/karafka/licenser.rb +26 -7
  25. data/lib/karafka/messages/batch_metadata.rb +26 -3
  26. data/lib/karafka/messages/builders/batch_metadata.rb +17 -29
  27. data/lib/karafka/messages/builders/message.rb +1 -0
  28. data/lib/karafka/messages/builders/messages.rb +4 -12
  29. data/lib/karafka/pro/active_job/consumer.rb +48 -0
  30. data/lib/karafka/pro/active_job/dispatcher.rb +3 -3
  31. data/lib/karafka/pro/active_job/job_options_contract.rb +2 -2
  32. data/lib/karafka/pro/base_consumer_extensions.rb +66 -0
  33. data/lib/karafka/pro/loader.rb +27 -4
  34. data/lib/karafka/pro/performance_tracker.rb +80 -0
  35. data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +37 -0
  36. data/lib/karafka/pro/processing/jobs_builder.rb +31 -0
  37. data/lib/karafka/pro/routing/extensions.rb +32 -0
  38. data/lib/karafka/pro/scheduler.rb +54 -0
  39. data/lib/karafka/processing/executor.rb +26 -11
  40. data/lib/karafka/processing/executors_buffer.rb +15 -7
  41. data/lib/karafka/processing/jobs/base.rb +28 -0
  42. data/lib/karafka/processing/jobs/consume.rb +11 -4
  43. data/lib/karafka/processing/jobs_builder.rb +28 -0
  44. data/lib/karafka/processing/jobs_queue.rb +28 -16
  45. data/lib/karafka/processing/worker.rb +39 -10
  46. data/lib/karafka/processing/workers_batch.rb +5 -0
  47. data/lib/karafka/routing/consumer_group.rb +1 -1
  48. data/lib/karafka/routing/subscription_group.rb +2 -2
  49. data/lib/karafka/routing/subscription_groups_builder.rb +3 -2
  50. data/lib/karafka/routing/topics.rb +38 -0
  51. data/lib/karafka/runner.rb +19 -27
  52. data/lib/karafka/scheduler.rb +20 -0
  53. data/lib/karafka/server.rb +24 -23
  54. data/lib/karafka/setup/config.rb +6 -1
  55. data/lib/karafka/status.rb +1 -3
  56. data/lib/karafka/time_trackers/pause.rb +10 -2
  57. data/lib/karafka/version.rb +1 -1
  58. data.tar.gz.sig +0 -0
  59. metadata +19 -4
  60. metadata.gz.sig +0 -0
  61. data/lib/karafka/active_job/routing_extensions.rb +0 -18
@@ -33,6 +33,8 @@ module Karafka
33
33
 
34
34
  return if license_config.expires_on > Date.today
35
35
 
36
+ raise_expired_license_token_in_dev(license_config.expires_on)
37
+
36
38
  notify_if_license_expired(license_config.expires_on)
37
39
  end
38
40
 
@@ -53,24 +55,41 @@ module Karafka
53
55
  )
54
56
  end
55
57
 
58
+ # Raises an error for test and dev environments if running pro with expired license
59
+ # We never want to cause any non-dev problems and we should never crash anything else than
60
+ # tests and development envs.
61
+ #
62
+ # @param expires_on [Date] when the license expires
63
+ def raise_expired_license_token_in_dev(expires_on)
64
+ env = Karafka::App.env
65
+
66
+ return unless env.development? || env.test?
67
+
68
+ raise Errors::ExpiredLicenseTokenError.new, expired_message(expires_on)
69
+ end
70
+
56
71
  # We do not raise an error here as we don't want to cause any problems to someone that runs
57
72
  # Karafka on production. Error message is enough.
58
73
  #
59
74
  # @param expires_on [Date] when the license expires
60
75
  def notify_if_license_expired(expires_on)
61
- message = <<~MSG.tr("\n", ' ')
62
- Your license expired on #{expires_on}.
63
- Please reach us at contact@karafka.io or visit https://karafka.io to obtain a valid one.
64
- MSG
65
-
66
- Karafka.logger.error(message)
76
+ Karafka.logger.error(expired_message(expires_on))
67
77
 
68
78
  Karafka.monitor.instrument(
69
79
  'error.occurred',
70
80
  caller: self,
71
- error: Errors::ExpiredLicenseTokenError.new(message),
81
+ error: Errors::ExpiredLicenseTokenError.new(expired_message(expires_on)),
72
82
  type: 'licenser.expired'
73
83
  )
74
84
  end
85
+
86
+ # @param expires_on [Date] when the license expires
87
+ # @return [String] expired message
88
+ def expired_message(expires_on)
89
+ <<~MSG.tr("\n", ' ')
90
+ Your license expired on #{expires_on}.
91
+ Please reach us at contact@karafka.io or visit https://karafka.io to obtain a valid one.
92
+ MSG
93
+ end
75
94
  end
76
95
  end
@@ -13,10 +13,33 @@ module Karafka
13
13
  :deserializer,
14
14
  :partition,
15
15
  :topic,
16
+ :created_at,
16
17
  :scheduled_at,
17
- :consumption_lag,
18
- :processing_lag,
18
+ :processed_at,
19
19
  keyword_init: true
20
- )
20
+ ) do
21
+ # This lag describes how long did it take for a message to be consumed from the moment it was
22
+ # created
23
+ def consumption_lag
24
+ time_distance_in_ms(processed_at, created_at)
25
+ end
26
+
27
+ # This lag describes how long did a batch have to wait before it was picked up by one of the
28
+ # workers
29
+ def processing_lag
30
+ time_distance_in_ms(processed_at, scheduled_at)
31
+ end
32
+
33
+ private
34
+
35
+ # Computes time distance in between two times in ms
36
+ #
37
+ # @param time1 [Time]
38
+ # @param time2 [Time]
39
+ # @return [Integer] distance in between two times in ms
40
+ def time_distance_in_ms(time1, time2)
41
+ ((time1 - time2) * 1_000).round
42
+ end
43
+ end
21
44
  end
22
45
  end
@@ -8,42 +8,30 @@ module Karafka
8
8
  class << self
9
9
  # Creates metadata based on the kafka batch data.
10
10
  #
11
- # @param kafka_batch [Array<Rdkafka::Consumer::Message>] raw fetched messages
11
+ # @param messages [Array<Karafka::Messages::Message>] messages array
12
12
  # @param topic [Karafka::Routing::Topic] topic for which we've fetched the batch
13
13
  # @param scheduled_at [Time] moment when the batch was scheduled for processing
14
14
  # @return [Karafka::Messages::BatchMetadata] batch metadata object
15
- # @note Regarding the time lags: we can use the current time here, as batch metadata is
16
- # created in the worker. So whenever this is being built, it means that the processing
17
- # of this batch has already started.
18
- def call(kafka_batch, topic, scheduled_at)
19
- now = Time.now
20
-
15
+ #
16
+ # @note We do not set `processed_at` as this needs to be assigned when the batch is
17
+ # picked up for processing.
18
+ def call(messages, topic, scheduled_at)
21
19
  Karafka::Messages::BatchMetadata.new(
22
- size: kafka_batch.count,
23
- first_offset: kafka_batch.first.offset,
24
- last_offset: kafka_batch.last.offset,
20
+ size: messages.count,
21
+ first_offset: messages.first.offset,
22
+ last_offset: messages.last.offset,
25
23
  deserializer: topic.deserializer,
26
- partition: kafka_batch[0].partition,
24
+ partition: messages.first.partition,
27
25
  topic: topic.name,
26
+ # We go with the assumption that the creation of the whole batch is the last message
27
+ # creation time
28
+ created_at: messages.last.timestamp,
29
+ # When this batch was built and scheduled for execution
28
30
  scheduled_at: scheduled_at,
29
- # This lag describes how long did it take for a message to be consumed from the
30
- # moment it was created
31
- consumption_lag: time_distance_in_ms(now, kafka_batch.last.timestamp),
32
- # This lag describes how long did a batch have to wait before it was picked up by
33
- # one of the workers
34
- processing_lag: time_distance_in_ms(now, scheduled_at)
35
- ).freeze
36
- end
37
-
38
- private
39
-
40
- # Computes time distance in between two times in ms
41
- #
42
- # @param time1 [Time]
43
- # @param time2 [Time]
44
- # @return [Integer] distance in between two times in ms
45
- def time_distance_in_ms(time1, time2)
46
- ((time1 - time2) * 1_000).round
31
+ # We build the batch metadata when we pick up the job in the worker, thus we can use
32
+ # current time here
33
+ processed_at: Time.now
34
+ )
47
35
  end
48
36
  end
49
37
  end
@@ -26,6 +26,7 @@ module Karafka
26
26
  received_at: received_at
27
27
  ).freeze
28
28
 
29
+ # Karafka messages cannot be frozen because of the lazy deserialization feature
29
30
  Karafka::Messages::Message.new(
30
31
  kafka_message.payload,
31
32
  metadata
@@ -9,27 +9,19 @@ module Karafka
9
9
  # Creates messages batch with messages inside based on the incoming messages and the
10
10
  # topic from which it comes.
11
11
  #
12
- # @param kafka_messages [Array<Rdkafka::Consumer::Message>] raw fetched messages
12
+ # @param messages [Array<Karafka::Messages::Message>] karafka messages array
13
13
  # @param topic [Karafka::Routing::Topic] topic for which we're received messages
14
14
  # @param received_at [Time] moment in time when the messages were received
15
15
  # @return [Karafka::Messages::Messages] messages batch object
16
- def call(kafka_messages, topic, received_at)
17
- messages_array = kafka_messages.map do |message|
18
- Karafka::Messages::Builders::Message.call(
19
- message,
20
- topic,
21
- received_at
22
- )
23
- end
24
-
16
+ def call(messages, topic, received_at)
25
17
  metadata = BatchMetadata.call(
26
- kafka_messages,
18
+ messages,
27
19
  topic,
28
20
  received_at
29
21
  ).freeze
30
22
 
31
23
  Karafka::Messages::Messages.new(
32
- messages_array,
24
+ messages,
33
25
  metadata
34
26
  ).freeze
35
27
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component.
4
+ # All of the commercial components are present in the lib/karafka/pro directory of this
5
+ # repository and their usage requires commercial license agreement.
6
+ #
7
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
8
+ #
9
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
10
+ # your code to Maciej Mensfeld.
11
+
12
+ module Karafka
13
+ module Pro
14
+ module ActiveJob
15
+ # Pro ActiveJob consumer that is suppose to handle long-running jobs as well as short
16
+ # running jobs
17
+ #
18
+ # When in LRJ, it will pause a given partition forever and will resume its processing only
19
+ # when all the jobs are done processing.
20
+ #
21
+ # It contains slightly better revocation warranties than the regular blocking consumer as
22
+ # it can stop processing batch of jobs in the middle after the revocation.
23
+ class Consumer < Karafka::ActiveJob::Consumer
24
+ # Runs ActiveJob jobs processing and handles lrj if needed
25
+ def consume
26
+ messages.each do |message|
27
+ # If for any reason we've lost this partition, not worth iterating over new messages
28
+ # as they are no longer ours
29
+ return if revoked?
30
+ break if Karafka::App.stopping?
31
+
32
+ ::ActiveJob::Base.execute(
33
+ ::ActiveSupport::JSON.decode(message.raw_payload)
34
+ )
35
+
36
+ # We check it twice as the job may be long running
37
+ return if revoked?
38
+
39
+ mark_as_consumed(message)
40
+
41
+ # Do not process more if we are shutting down
42
+ break if Karafka::App.stopping?
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # This Karafka component is a Pro component.
4
- # All of the commercial components are present in the lib/karafka/pro directory of this repository
5
- # and their usage requires commercial license agreement.
4
+ # All of the commercial components are present in the lib/karafka/pro directory of this
5
+ # repository and their usage requires commercial license agreement.
6
6
  #
7
7
  # Karafka has also commercial-friendly license, commercial support and commercial components.
8
8
  #
@@ -18,7 +18,7 @@ module Karafka
18
18
  # much better and more granular control over the dispatch and consumption process.
19
19
  class Dispatcher < ::Karafka::ActiveJob::Dispatcher
20
20
  # Defaults for dispatching
21
- # The can be updated by using `#karafka_options` on the job
21
+ # They can be updated by using `#karafka_options` on the job
22
22
  DEFAULTS = {
23
23
  dispatch_method: :produce_async,
24
24
  # We don't create a dummy proc based partitioner as we would have to evaluate it with
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # This Karafka component is a Pro component.
4
- # All of the commercial components are present in the lib/karafka/pro directory of this repository
5
- # and their usage requires commercial license agreement.
4
+ # All of the commercial components are present in the lib/karafka/pro directory of this
5
+ # repository and their usage requires commercial license agreement.
6
6
  #
7
7
  # Karafka has also commercial-friendly license, commercial support and commercial components.
8
8
  #
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component.
4
+ # All of the commercial components are present in the lib/karafka/pro directory of this
5
+ # repository and their usage requires commercial license agreement.
6
+ #
7
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
8
+ #
9
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
10
+ # your code to Maciej Mensfeld.
11
+
12
+ module Karafka
13
+ module Pro
14
+ # Extensions to the base consumer that make it more pro and fancy
15
+ #
16
+ # @note In case of using lrj, manual pausing may not be the best idea as resume needs to happen
17
+ # after each batch is processed.
18
+ #
19
+ # They need to be added to the consumer via `#prepend`
20
+ module BaseConsumerExtensions
21
+ # Pause for tops 31 years
22
+ MAX_PAUSE_TIME = 1_000_000_000_000
23
+
24
+ private_constant :MAX_PAUSE_TIME
25
+
26
+ # Pauses processing of a given partition until we're done with the processing
27
+ # This ensures, that we can easily poll not reaching the `max.poll.interval`
28
+ def on_prepare
29
+ # Pause at the first message in a batch. That way in case of a crash, we will not loose
30
+ # any messages
31
+ pause(messages.first.offset, MAX_PAUSE_TIME) if topic.long_running_job?
32
+
33
+ super
34
+ end
35
+
36
+ # After user code, we seek and un-pause our partition
37
+ def on_consume
38
+ # If anything went wrong here, we should not run any partition management as it's Karafka
39
+ # core that will handle the backoff
40
+ return unless super
41
+
42
+ return unless topic.long_running_job?
43
+
44
+ # Nothing to resume if it was revoked
45
+ return if revoked?
46
+
47
+ # Once processing is done, we move to the new offset based on commits
48
+ seek(@seek_offset || messages.first.offset)
49
+ resume
50
+ end
51
+
52
+ # Marks this consumer revoked state as true
53
+ # This allows us for things like lrj to finish early as this state may change during lrj
54
+ # execution
55
+ def on_revoked
56
+ @revoked = true
57
+ super
58
+ end
59
+
60
+ # @return [Boolean] true if partition was revoked from the current consumer
61
+ def revoked?
62
+ @revoked || false
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,27 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # This Karafka component is a Pro component.
4
- # All of the commercial components are present in the lib/karafka/pro directory of this repository
5
- # and their usage requires commercial license agreement.
4
+ # All of the commercial components are present in the lib/karafka/pro directory of this
5
+ # repository and their usage requires commercial license agreement.
6
6
  #
7
7
  # Karafka has also commercial-friendly license, commercial support and commercial components.
8
8
  #
9
9
  # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
10
10
  # your code to Maciej Mensfeld.
11
+
11
12
  module Karafka
12
13
  module Pro
13
14
  # Loader requires and loads all the pro components only when they are needed
14
15
  class Loader
16
+ # All the pro components that need to be loaded
17
+ COMPONENTS = %w[
18
+ performance_tracker
19
+ scheduler
20
+ base_consumer_extensions
21
+ processing/jobs/consume_non_blocking
22
+ processing/jobs_builder
23
+ routing/extensions
24
+ active_job/consumer
25
+ active_job/dispatcher
26
+ active_job/job_options_contract
27
+ ].freeze
28
+
29
+ private_constant :COMPONENTS
30
+
15
31
  class << self
16
32
  # Loads all the pro components and configures them wherever it is expected
17
33
  # @param config [Dry::Configurable::Config] whole app config that we can alter with pro
18
34
  # components
19
35
  def setup(config)
20
- require_relative 'active_job/dispatcher'
21
- require_relative 'active_job/job_options_contract'
36
+ COMPONENTS.each { |component| require_relative(component) }
22
37
 
38
+ config.internal.scheduler = Scheduler.new
39
+ config.internal.jobs_builder = Processing::JobsBuilder.new
40
+ config.internal.active_job.consumer = ActiveJob::Consumer
23
41
  config.internal.active_job.dispatcher = ActiveJob::Dispatcher.new
24
42
  config.internal.active_job.job_options_contract = ActiveJob::JobOptionsContract.new
43
+
44
+ ::Karafka::Routing::Topic.include(Routing::Extensions)
45
+ ::Karafka::BaseConsumer.prepend(BaseConsumerExtensions)
46
+
47
+ config.monitor.subscribe(PerformanceTracker.instance)
25
48
  end
26
49
  end
27
50
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component.
4
+ # All of the commercial components are present in the lib/karafka/pro directory of this
5
+ # repository and their usage requires commercial license agreement.
6
+ #
7
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
8
+ #
9
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
10
+ # your code to Maciej Mensfeld.
11
+
12
+ module Karafka
13
+ module Pro
14
+ # Tracker used to keep track of performance metrics
15
+ # It provides insights that can be used to optimize processing flow
16
+ class PerformanceTracker
17
+ include Singleton
18
+
19
+ # How many samples do we collect per topic partition
20
+ SAMPLES_COUNT = 200
21
+
22
+ private_constant :SAMPLES_COUNT
23
+
24
+ # Builds up nested concurrent hash for data tracking
25
+ def initialize
26
+ @processing_times = Concurrent::Hash.new do |topics_hash, topic|
27
+ topics_hash[topic] = Concurrent::Hash.new do |partitions_hash, partition|
28
+ # This array does not have to be concurrent because we always access single partition
29
+ # data via instrumentation that operates in a single thread via consumer
30
+ partitions_hash[partition] = []
31
+ end
32
+ end
33
+ end
34
+
35
+ # @param topic [String]
36
+ # @param partition [Integer]
37
+ # @return [Float] p95 processing time of a single message from a single topic partition
38
+ def processing_time_p95(topic, partition)
39
+ values = @processing_times[topic][partition]
40
+
41
+ return 0 if values.empty?
42
+ return values.first if values.size == 1
43
+
44
+ percentile(0.95, values)
45
+ end
46
+
47
+ # @private
48
+ # @param event [Dry::Events::Event] event details
49
+ # Tracks time taken to process a single message of a given topic partition
50
+ def on_consumer_consumed(event)
51
+ consumer = event[:caller]
52
+ messages = consumer.messages
53
+ topic = messages.metadata.topic
54
+ partition = messages.metadata.partition
55
+
56
+ samples = @processing_times[topic][partition]
57
+ samples << event[:time] / messages.count
58
+
59
+ return unless samples.size > SAMPLES_COUNT
60
+
61
+ samples.shift
62
+ end
63
+
64
+ private
65
+
66
+ # Computers the requested percentile out of provided values
67
+ # @param percentile [Float]
68
+ # @param values [Array<String>] all the values based on which we should
69
+ # @return [Float] computed percentile
70
+ def percentile(percentile, values)
71
+ values_sorted = values.sort
72
+
73
+ floor = (percentile * (values_sorted.length - 1) + 1).floor - 1
74
+ mod = (percentile * (values_sorted.length - 1) + 1).modulo(1)
75
+
76
+ values_sorted[floor] + (mod * (values_sorted[floor + 1] - values_sorted[floor]))
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component.
4
+ # All of the commercial components are present in the lib/karafka/pro directory of this
5
+ # repository and their usage requires commercial license agreement.
6
+ #
7
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
8
+ #
9
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
10
+ # your code to Maciej Mensfeld.
11
+
12
+ module Karafka
13
+ module Pro
14
+ # Pro components related to processing part of Karafka
15
+ module Processing
16
+ # Pro jobs
17
+ module Jobs
18
+ # The main job type in a non-blocking variant.
19
+ # This variant works "like" the regular consumption but pauses the partition for as long
20
+ # as it is needed until a job is done.
21
+ #
22
+ # It can be useful when having long lasting jobs that would exceed `max.poll.interval`
23
+ # if would block.
24
+ #
25
+ # @note It needs to be working with a proper consumer that will handle the partition
26
+ # management. This layer of the framework knows nothing about Kafka messages consumption.
27
+ class ConsumeNonBlocking < ::Karafka::Processing::Jobs::Consume
28
+ # Releases the blocking lock after it is done with the preparation phase for this job
29
+ def prepare
30
+ super
31
+ @non_blocking = true
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component.
4
+ # All of the commercial components are present in the lib/karafka/pro directory of this
5
+ # repository and their usage requires commercial license agreement.
6
+ #
7
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
8
+ #
9
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
10
+ # your code to Maciej Mensfeld.
11
+
12
+ module Karafka
13
+ module Pro
14
+ module Processing
15
+ # Pro jobs builder that supports lrj
16
+ class JobsBuilder < ::Karafka::Processing::JobsBuilder
17
+ # @param executor [Karafka::Processing::Executor]
18
+ # @param messages [Karafka::Messages::Messages] messages batch to be consumed
19
+ # @return [Karafka::Processing::Jobs::Consume] blocking job
20
+ # @return [Karafka::Pro::Processing::Jobs::ConsumeNonBlocking] non blocking for lrj
21
+ def consume(executor, messages)
22
+ if executor.topic.long_running_job?
23
+ Jobs::ConsumeNonBlocking.new(executor, messages)
24
+ else
25
+ super
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component.
4
+ # All of the commercial components are present in the lib/karafka/pro directory of this
5
+ # repository and their usage requires commercial license agreement.
6
+ #
7
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
8
+ #
9
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
10
+ # your code to Maciej Mensfeld.
11
+
12
+ module Karafka
13
+ module Pro
14
+ # Pro routing components
15
+ module Routing
16
+ # Routing extensions that allow to configure some extra PRO routing options
17
+ module Extensions
18
+ class << self
19
+ # @param base [Class] class we extend
20
+ def included(base)
21
+ base.attr_accessor :long_running_job
22
+ end
23
+ end
24
+
25
+ # @return [Boolean] is a given job on a topic a long running one
26
+ def long_running_job?
27
+ @long_running_job || false
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component.
4
+ # All of the commercial components are present in the lib/karafka/pro directory of this
5
+ # repository and their usage requires commercial license agreement.
6
+ #
7
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
8
+ #
9
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
10
+ # your code to Maciej Mensfeld.
11
+
12
+ module Karafka
13
+ module Pro
14
+ # Optimizes scheduler that takes into consideration of execution time needed to process
15
+ # messages from given topics partitions. It uses the non-preemptive LJF algorithm
16
+ #
17
+ # This scheduler is designed to optimize execution times on jobs that perform IO operations as
18
+ # when taking IO into consideration, the can achieve optimized parallel processing.
19
+ #
20
+ # This scheduler can also work with virtual partitions.
21
+ #
22
+ # Aside from consumption jobs, other jobs do not run often, thus we can leave them with
23
+ # default FIFO scheduler from the default Karafka scheduler
24
+ class Scheduler < ::Karafka::Scheduler
25
+ # Schedules jobs in the LJF order for consumption
26
+ #
27
+ # @param queue [Karafka::Processing::JobsQueue] queue where we want to put the jobs
28
+ # @param jobs_array [Array<Karafka::Processing::Jobs::Base>] jobs we want to schedule
29
+ #
30
+ def schedule_consumption(queue, jobs_array)
31
+ pt = PerformanceTracker.instance
32
+
33
+ ordered = []
34
+
35
+ jobs_array.each do |job|
36
+ messages = job.messages
37
+ message = messages.first
38
+
39
+ cost = pt.processing_time_p95(message.topic, message.partition) * messages.size
40
+
41
+ ordered << [job, cost]
42
+ end
43
+
44
+ ordered.sort_by!(&:last)
45
+ ordered.reverse!
46
+ ordered.map!(&:first)
47
+
48
+ ordered.each do |job|
49
+ queue << job
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end