karafka 2.0.20 → 2.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci.yml +6 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile.lock +2 -2
- data/README.md +1 -1
- data/bin/integrations +2 -2
- data/bin/rspecs +2 -0
- data/config/errors.yml +5 -2
- data/karafka.gemspec +7 -2
- data/lib/karafka/app.rb +12 -2
- data/lib/karafka/base_consumer.rb +1 -10
- data/lib/karafka/cli/info.rb +2 -1
- data/lib/karafka/cli/server.rb +7 -6
- data/lib/karafka/contracts/server_cli_options.rb +60 -3
- data/lib/karafka/contracts/topic.rb +1 -1
- data/lib/karafka/licenser.rb +53 -50
- data/lib/karafka/pro/loader.rb +0 -2
- data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +1 -2
- data/lib/karafka/pro/processing/jobs/revoked_non_blocking.rb +37 -0
- data/lib/karafka/pro/processing/jobs_builder.rb +12 -0
- data/lib/karafka/pro/processing/strategies/default.rb +26 -0
- data/lib/karafka/processing/jobs_queue.rb +1 -1
- data/lib/karafka/processing/strategies/base.rb +5 -0
- data/lib/karafka/processing/strategies/default.rb +19 -0
- data/lib/karafka/railtie.rb +3 -0
- data/lib/karafka/routing/builder.rb +1 -1
- data/lib/karafka/routing/consumer_group.rb +6 -3
- data/lib/karafka/routing/subscription_group.rb +11 -2
- data/lib/karafka/routing/topic.rb +8 -0
- data/lib/karafka/routing/topics.rb +8 -0
- data/lib/karafka/server.rb +11 -10
- data/lib/karafka/setup/config.rb +15 -11
- data/lib/karafka/version.rb +1 -1
- data/lib/karafka.rb +9 -0
- data.tar.gz.sig +0 -0
- metadata +9 -4
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a096a44a1a2988ff394215b8d63f6f2e33b2a3e5100f68f75fcf33eeda4b490
|
4
|
+
data.tar.gz: 2af0875b550f37ef9ea47b2a3011a3ed3e535adc3f6632ab10510d91bc69590c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a81cf3305482e0e925f3a5aa48d978585766826e0990081d74f5f56bc535ae0a8a6be3a4ec986e6a7702d70d0d207ca9acc71b7aeb3909557b44518413ff9bc6
|
7
|
+
data.tar.gz: 6c66140b258135367a120a5dac4eb8a08f4dc94e24fb3a714b32ec6ad909bc9f85bf5e160aefe5f5d307c9d554c6ecab61cbdebf315ce6953476303e9783122c
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/.github/workflows/ci.yml
CHANGED
@@ -101,6 +101,9 @@ jobs:
|
|
101
101
|
- name: Install package dependencies
|
102
102
|
run: "[ -e $APT_DEPS ] || sudo apt-get install -y --no-install-recommends $APT_DEPS"
|
103
103
|
|
104
|
+
- name: Remove libzstd-dev to check no supported compressions
|
105
|
+
run: sudo apt-get -y remove libzstd-dev
|
106
|
+
|
104
107
|
- name: Start Kafka with docker-compose
|
105
108
|
run: |
|
106
109
|
docker-compose up -d
|
@@ -124,5 +127,8 @@ jobs:
|
|
124
127
|
- name: Run integration tests
|
125
128
|
env:
|
126
129
|
KARAFKA_PRO_LICENSE_TOKEN: ${{ secrets.KARAFKA_PRO_LICENSE_TOKEN }}
|
130
|
+
KARAFKA_PRO_USERNAME: ${{ secrets.KARAFKA_PRO_USERNAME }}
|
131
|
+
KARAFKA_PRO_PASSWORD: ${{ secrets.KARAFKA_PRO_PASSWORD }}
|
132
|
+
KARAFKA_PRO_VERSION: ${{ secrets.KARAFKA_PRO_VERSION }}
|
127
133
|
GITHUB_COVERAGE: ${{matrix.coverage}}
|
128
134
|
run: bin/integrations
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
# Karafka framework changelog
|
2
2
|
|
3
|
+
## 2.0.22 (2022-12-02)
|
4
|
+
- [Improvement] Load Pro components upon Karafka require so they can be altered prior to setup.
|
5
|
+
- [Improvement] Do not run LRJ jobs that were added to the jobs queue but were revoked meanwhile.
|
6
|
+
- [Improvement] Allow running particular named subscription groups similar to consumer groups.
|
7
|
+
- [Improvement] Allow running particular topics similar to consumer groups.
|
8
|
+
- [Improvement] Raise configuration error when trying to run Karafka with options leading to no subscriptions.
|
9
|
+
- [Fix] Fix `karafka info` subscription groups count reporting as it was misleading.
|
10
|
+
- [Fix] Allow for defining subscription groups with symbols similar to consumer groups and topics to align the API.
|
11
|
+
- [Fix] Do not allow for an explicit `nil` as a `subscription_group` block argument.
|
12
|
+
- [Fix] Fix instability in subscription groups static members ids when using `--consumer_groups` CLI flag.
|
13
|
+
- [Fix] Fix a case in routing, where anonymous subscription group could not be used inside of a consumer group.
|
14
|
+
- [Fix] Fix a case where shutdown prior to listeners build would crash the server initialization.
|
15
|
+
- [Fix] Duplicated logs in development environment for Rails when logger set to `$stdout`.
|
16
|
+
|
17
|
+
## 20.0.21 (2022-11-25)
|
18
|
+
- [Improvement] Make revocation jobs for LRJ topics non-blocking to prevent blocking polling when someone uses non-revocation aware LRJ jobs and revocation happens.
|
19
|
+
|
3
20
|
## 2.0.20 (2022-11-24)
|
4
21
|
- [Improvement] Support `group.instance.id` assignment (static group membership) for a case where a single consumer group has multiple subscription groups (#1173).
|
5
22
|
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -13,7 +13,7 @@ Karafka is a Ruby and Rails multi-threaded efficient Kafka processing framework
|
|
13
13
|
- Supports parallel processing in [multiple threads](https://karafka.io/docs/Concurrency-and-multithreading) (also for a [single topic partition](https://karafka.io/docs/Pro-Virtual-Partitions) work)
|
14
14
|
- [Automatically integrates](https://karafka.io/docs/Integrating-with-Ruby-on-Rails-and-other-frameworks#integrating-with-ruby-on-rails) with Ruby on Rails
|
15
15
|
- Has [ActiveJob backend](https://karafka.io/docs/Active-Job) support (including [ordered jobs](https://karafka.io/docs/Pro-Enhanced-Active-Job#ordered-jobs))
|
16
|
-
- Has a seamless [Dead Letter Queue](karafka.io/docs/Dead-Letter-Queue/) functionality built-in
|
16
|
+
- Has a seamless [Dead Letter Queue](https://karafka.io/docs/Dead-Letter-Queue/) functionality built-in
|
17
17
|
- Supports in-development [code reloading](https://karafka.io/docs/Auto-reload-of-code-changes-in-development)
|
18
18
|
- Is powered by [librdkafka](https://github.com/edenhill/librdkafka) (the Apache Kafka C/C++ client library)
|
19
19
|
- Has an out-of the box [StatsD/DataDog monitoring](https://karafka.io/docs/Monitoring-and-logging) with a dashboard template.
|
data/bin/integrations
CHANGED
@@ -74,8 +74,8 @@ class Scenario
|
|
74
74
|
def type
|
75
75
|
scenario_dir = File.dirname(@path)
|
76
76
|
|
77
|
-
return :poro if scenario_dir.
|
78
|
-
return :pristine if scenario_dir.
|
77
|
+
return :poro if scenario_dir.include?('_poro')
|
78
|
+
return :pristine if scenario_dir.include?('_pristine')
|
79
79
|
|
80
80
|
:regular
|
81
81
|
end
|
data/bin/rspecs
CHANGED
data/config/errors.yml
CHANGED
@@ -25,7 +25,10 @@ en:
|
|
25
25
|
|
26
26
|
server_cli_options:
|
27
27
|
missing: needs to be present
|
28
|
-
consumer_groups_inclusion: Unknown consumer group
|
28
|
+
consumer_groups_inclusion: Unknown consumer group name
|
29
|
+
subscription_groups_inclusion: Unknown subscription group name
|
30
|
+
topics_inclusion: Unknown topic name
|
31
|
+
topics_missing: No topics to subscribe to
|
29
32
|
|
30
33
|
topic:
|
31
34
|
missing: needs to be present
|
@@ -34,7 +37,7 @@ en:
|
|
34
37
|
consumer_format: needs to be present
|
35
38
|
id_format: 'needs to be a string with a Kafka accepted format'
|
36
39
|
initial_offset_format: needs to be either earliest or latest
|
37
|
-
subscription_group_format: must be
|
40
|
+
subscription_group_format: must be a non-empty string
|
38
41
|
manual_offset_management.active_format: needs to be either true or false
|
39
42
|
consumer_active_job_missing: ActiveJob needs to be available
|
40
43
|
manual_offset_management_must_be_enabled: cannot be disabled for ActiveJob topics
|
data/karafka.gemspec
CHANGED
@@ -12,9 +12,14 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.authors = ['Maciej Mensfeld']
|
13
13
|
spec.email = %w[contact@karafka.io]
|
14
14
|
spec.homepage = 'https://karafka.io'
|
15
|
-
spec.summary = 'Efficient Kafka processing framework for Ruby and Rails'
|
16
|
-
spec.description = 'Framework used to simplify Apache Kafka based Ruby applications development'
|
17
15
|
spec.licenses = ['LGPL-3.0', 'Commercial']
|
16
|
+
spec.summary = 'Karafka is Ruby and Rails efficient Kafka processing framework.'
|
17
|
+
spec.description = <<-DESC
|
18
|
+
Karafka is Ruby and Rails efficient Kafka processing framework.
|
19
|
+
|
20
|
+
Karafka allows you to capture everything that happens in your systems in large scale,
|
21
|
+
without having to focus on things that are not your business domain.
|
22
|
+
DESC
|
18
23
|
|
19
24
|
spec.add_dependency 'karafka-core', '>= 2.0.4', '< 3.0.0'
|
20
25
|
spec.add_dependency 'rdkafka', '>= 0.12'
|
data/lib/karafka/app.rb
CHANGED
@@ -16,9 +16,19 @@ module Karafka
|
|
16
16
|
|
17
17
|
# @return [Hash] active subscription groups grouped based on consumer group in a hash
|
18
18
|
def subscription_groups
|
19
|
+
# We first build all the subscription groups, so they all get the same position, despite
|
20
|
+
# later narrowing that. It allows us to maintain same position number for static members
|
21
|
+
# even when we want to run subset of consumer groups or subscription groups
|
22
|
+
#
|
23
|
+
# We then narrow this to active consumer groups from which we select active subscription
|
24
|
+
# groups.
|
19
25
|
consumer_groups
|
20
|
-
.
|
21
|
-
.
|
26
|
+
.map { |cg| [cg, cg.subscription_groups] }
|
27
|
+
.select { |cg, _| cg.active? }
|
28
|
+
.select { |_, sgs| sgs.delete_if { |sg| !sg.active? } }
|
29
|
+
.delete_if { |_, sgs| sgs.empty? }
|
30
|
+
.each { |_, sgs| sgs.each { |sg| sg.topics.delete_if { |top| !top.active? } } }
|
31
|
+
.each { |_, sgs| sgs.delete_if { |sg| sg.topics.empty? } }
|
22
32
|
.to_h
|
23
33
|
end
|
24
34
|
|
@@ -62,14 +62,8 @@ module Karafka
|
|
62
62
|
# that may not yet kick in when error occurs. That way we pause always on the last processed
|
63
63
|
# message.
|
64
64
|
def on_consume
|
65
|
-
|
66
|
-
consume
|
67
|
-
end
|
68
|
-
|
69
|
-
coordinator.consumption(self).success!
|
65
|
+
handle_consume
|
70
66
|
rescue StandardError => e
|
71
|
-
coordinator.consumption(self).failure!(e)
|
72
|
-
|
73
67
|
Karafka.monitor.instrument(
|
74
68
|
'error.occurred',
|
75
69
|
error: e,
|
@@ -77,9 +71,6 @@ module Karafka
|
|
77
71
|
seek_offset: coordinator.seek_offset,
|
78
72
|
type: 'consumer.consume.error'
|
79
73
|
)
|
80
|
-
ensure
|
81
|
-
# We need to decrease number of jobs that this coordinator coordinates as it has finished
|
82
|
-
coordinator.decrement
|
83
74
|
end
|
84
75
|
|
85
76
|
# @private
|
data/lib/karafka/cli/info.rb
CHANGED
@@ -37,7 +37,8 @@ module Karafka
|
|
37
37
|
"Karafka version: #{Karafka::VERSION}#{postfix}",
|
38
38
|
"Ruby version: #{RUBY_DESCRIPTION}",
|
39
39
|
"Rdkafka version: #{::Rdkafka::VERSION}",
|
40
|
-
"
|
40
|
+
"Consumer groups count: #{Karafka::App.consumer_groups.size}",
|
41
|
+
"Subscription groups count: #{Karafka::App.subscription_groups.values.flatten.size}",
|
41
42
|
"Workers count: #{Karafka::App.config.concurrency}",
|
42
43
|
"Application client id: #{config.client_id}",
|
43
44
|
"Boot file: #{Karafka.boot_file}",
|
data/lib/karafka/cli/server.rb
CHANGED
@@ -9,18 +9,19 @@ module Karafka
|
|
9
9
|
|
10
10
|
desc 'Start the Karafka server (short-cut alias: "s")'
|
11
11
|
option aliases: 's'
|
12
|
-
option :consumer_groups, type: :array, default:
|
12
|
+
option :consumer_groups, type: :array, default: [], aliases: :g
|
13
|
+
option :subscription_groups, type: :array, default: []
|
14
|
+
option :topics, type: :array, default: []
|
13
15
|
|
14
16
|
# Start the Karafka server
|
15
17
|
def call
|
16
18
|
# Print our banner and info in the dev mode
|
17
19
|
print_marketing_info if Karafka::App.env.development?
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
Karafka::Server.consumer_groups = cli.options[:consumer_groups]
|
21
|
+
active_routing_config = Karafka::App.config.internal.routing.active
|
22
|
+
active_routing_config.consumer_groups = cli.options[:consumer_groups]
|
23
|
+
active_routing_config.subscription_groups = cli.options[:subscription_groups]
|
24
|
+
active_routing_config.topics = cli.options[:topics]
|
24
25
|
|
25
26
|
Karafka::Server.run
|
26
27
|
end
|
@@ -12,7 +12,9 @@ module Karafka
|
|
12
12
|
).fetch('en').fetch('validations').fetch('server_cli_options')
|
13
13
|
end
|
14
14
|
|
15
|
-
optional(:consumer_groups) { |cg| cg.is_a?(Array)
|
15
|
+
optional(:consumer_groups) { |cg| cg.is_a?(Array) }
|
16
|
+
optional(:subscription_groups) { |sg| sg.is_a?(Array) }
|
17
|
+
optional(:topics) { |topics| topics.is_a?(Array) }
|
16
18
|
|
17
19
|
virtual do |data, errors|
|
18
20
|
next unless errors.empty?
|
@@ -22,11 +24,66 @@ module Karafka
|
|
22
24
|
|
23
25
|
# If there were no consumer_groups declared in the server cli, it means that we will
|
24
26
|
# run all of them and no need to validate them here at all
|
25
|
-
next if value.
|
26
|
-
next if (value - Karafka::App.
|
27
|
+
next if value.empty?
|
28
|
+
next if (value - Karafka::App.consumer_groups.map(&:name)).empty?
|
27
29
|
|
30
|
+
# Found unknown consumer groups
|
28
31
|
[[%i[consumer_groups], :consumer_groups_inclusion]]
|
29
32
|
end
|
33
|
+
|
34
|
+
virtual do |data, errors|
|
35
|
+
next unless errors.empty?
|
36
|
+
next unless data.key?(:subscription_groups)
|
37
|
+
|
38
|
+
value = data.fetch(:subscription_groups)
|
39
|
+
|
40
|
+
# If there were no subscription_groups declared in the server cli, it means that we will
|
41
|
+
# run all of them and no need to validate them here at all
|
42
|
+
next if value.empty?
|
43
|
+
|
44
|
+
subscription_groups = Karafka::App
|
45
|
+
.consumer_groups
|
46
|
+
.map(&:subscription_groups)
|
47
|
+
.flatten
|
48
|
+
.map(&:name)
|
49
|
+
|
50
|
+
next if (value - subscription_groups).empty?
|
51
|
+
|
52
|
+
# Found unknown subscription groups
|
53
|
+
[[%i[subscription_groups], :subscription_groups_inclusion]]
|
54
|
+
end
|
55
|
+
|
56
|
+
virtual do |data, errors|
|
57
|
+
next unless errors.empty?
|
58
|
+
next unless data.key?(:topics)
|
59
|
+
|
60
|
+
value = data.fetch(:topics)
|
61
|
+
|
62
|
+
# If there were no topics declared in the server cli, it means that we will
|
63
|
+
# run all of them and no need to validate them here at all
|
64
|
+
next if value.empty?
|
65
|
+
|
66
|
+
topics = Karafka::App
|
67
|
+
.consumer_groups
|
68
|
+
.map(&:subscription_groups)
|
69
|
+
.flatten
|
70
|
+
.map(&:topics)
|
71
|
+
.map { |gtopics| gtopics.map(&:name) }
|
72
|
+
.flatten
|
73
|
+
|
74
|
+
next if (value - topics).empty?
|
75
|
+
|
76
|
+
# Found unknown topics
|
77
|
+
[[%i[topics], :topics_inclusion]]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Makes sure we have anything to subscribe to when we start the server
|
81
|
+
virtual do |_, errors|
|
82
|
+
next unless errors.empty?
|
83
|
+
next unless Karafka::App.subscription_groups.empty?
|
84
|
+
|
85
|
+
[[%i[topics], :topics_missing]]
|
86
|
+
end
|
30
87
|
end
|
31
88
|
end
|
32
89
|
end
|
@@ -20,7 +20,7 @@ module Karafka
|
|
20
20
|
required(:initial_offset) { |val| %w[earliest latest].include?(val) }
|
21
21
|
required(:max_wait_time) { |val| val.is_a?(Integer) && val >= 10 }
|
22
22
|
required(:name) { |val| val.is_a?(String) && Contracts::TOPIC_REGEXP.match?(val) }
|
23
|
-
required(:subscription_group) { |val| val.
|
23
|
+
required(:subscription_group) { |val| val.is_a?(String) && !val.empty? }
|
24
24
|
|
25
25
|
virtual do |data, errors|
|
26
26
|
next unless errors.empty?
|
data/lib/karafka/licenser.rb
CHANGED
@@ -8,68 +8,71 @@ module Karafka
|
|
8
8
|
|
9
9
|
private_constant :PUBLIC_KEY_LOCATION
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
verify(license_config)
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
11
|
+
class << self
|
12
|
+
# Tries to load the license and yields if successful
|
13
|
+
def detect
|
14
|
+
# If required, do not require again
|
15
|
+
require('karafka-license') unless const_defined?('::Karafka::License')
|
20
16
|
|
21
|
-
|
22
|
-
def prepare(license_config)
|
23
|
-
# If there is token, no action needed
|
24
|
-
# We support a case where someone would put the token in instead of using one from the
|
25
|
-
# license. That's in case there are limitations to using external package sources, etc
|
26
|
-
return if license_config.token
|
17
|
+
yield
|
27
18
|
|
28
|
-
|
29
|
-
license_config.token || require('karafka-license')
|
19
|
+
true
|
30
20
|
rescue LoadError
|
31
|
-
|
21
|
+
false
|
32
22
|
end
|
33
23
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
# If no license, it will just run LGPL components without anything extra
|
41
|
-
return unless license_config.token
|
24
|
+
# Tries to prepare license and verifies it
|
25
|
+
#
|
26
|
+
# @param license_config [Karafka::Core::Configurable::Node] config related to the licensing
|
27
|
+
def prepare_and_verify(license_config)
|
28
|
+
# If license is not loaded, nothing to do
|
29
|
+
return unless const_defined?('::Karafka::License')
|
42
30
|
|
43
|
-
|
31
|
+
prepare(license_config)
|
32
|
+
verify(license_config)
|
33
|
+
end
|
44
34
|
|
45
|
-
|
46
|
-
formatted_token = license_config.token.strip.delete("\n").delete(' ')
|
47
|
-
decoded_token = Base64.decode64(formatted_token)
|
35
|
+
private
|
48
36
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
data = nil
|
37
|
+
# @param license_config [Karafka::Core::Configurable::Node] config related to the licensing
|
38
|
+
def prepare(license_config)
|
39
|
+
license_config.token = Karafka::License.token
|
53
40
|
end
|
54
41
|
|
55
|
-
|
42
|
+
# Check license and setup license details (if needed)
|
43
|
+
# @param license_config [Karafka::Core::Configurable::Node] config related to the licensing
|
44
|
+
def verify(license_config)
|
45
|
+
public_key = OpenSSL::PKey::RSA.new(File.read(PUBLIC_KEY_LOCATION))
|
56
46
|
|
57
|
-
|
58
|
-
|
47
|
+
# We gsub and strip in case someone copy-pasted it as a multi line string
|
48
|
+
formatted_token = license_config.token.strip.delete("\n").delete(' ')
|
49
|
+
decoded_token = Base64.decode64(formatted_token)
|
59
50
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
51
|
+
begin
|
52
|
+
data = public_key.public_decrypt(decoded_token)
|
53
|
+
rescue OpenSSL::OpenSSLError
|
54
|
+
data = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
details = data ? JSON.parse(data) : raise_invalid_license_token(license_config)
|
58
|
+
|
59
|
+
license_config.entity = details.fetch('entity')
|
60
|
+
end
|
61
|
+
|
62
|
+
# Raises an error with info, that used token is invalid
|
63
|
+
# @param license_config [Karafka::Core::Configurable::Node]
|
64
|
+
def raise_invalid_license_token(license_config)
|
65
|
+
# We set it to false so `Karafka.pro?` method behaves as expected
|
66
|
+
license_config.token = false
|
67
|
+
|
68
|
+
raise(
|
69
|
+
Errors::InvalidLicenseTokenError,
|
70
|
+
<<~MSG.tr("\n", ' ')
|
71
|
+
License key you provided is invalid.
|
72
|
+
Please reach us at contact@karafka.io or visit https://karafka.io to obtain a valid one.
|
73
|
+
MSG
|
74
|
+
)
|
75
|
+
end
|
73
76
|
end
|
74
77
|
end
|
75
78
|
end
|
data/lib/karafka/pro/loader.rb
CHANGED
@@ -18,8 +18,7 @@ module Karafka
|
|
18
18
|
# Pro jobs
|
19
19
|
module Jobs
|
20
20
|
# The main job type in a non-blocking variant.
|
21
|
-
# This variant works "like" the regular consumption but
|
22
|
-
# as it is needed until a job is done.
|
21
|
+
# This variant works "like" the regular consumption but does not block the queue.
|
23
22
|
#
|
24
23
|
# It can be useful when having long lasting jobs that would exceed `max.poll.interval`
|
25
24
|
# if would block.
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This Karafka component is a Pro component under a commercial license.
|
4
|
+
# This Karafka component is NOT licensed under LGPL.
|
5
|
+
#
|
6
|
+
# All of the commercial components are present in the lib/karafka/pro directory of this
|
7
|
+
# repository and their usage requires commercial license agreement.
|
8
|
+
#
|
9
|
+
# Karafka has also commercial-friendly license, commercial support and commercial components.
|
10
|
+
#
|
11
|
+
# By sending a pull request to the pro components, you are agreeing to transfer the copyright of
|
12
|
+
# your code to Maciej Mensfeld.
|
13
|
+
|
14
|
+
module Karafka
|
15
|
+
module Pro
|
16
|
+
# Pro components related to processing part of Karafka
|
17
|
+
module Processing
|
18
|
+
# Pro jobs
|
19
|
+
module Jobs
|
20
|
+
# The revoked job type in a non-blocking variant.
|
21
|
+
# This variant works "like" the regular revoked but does not block the queue.
|
22
|
+
#
|
23
|
+
# It can be useful when having long lasting jobs that would exceed `max.poll.interval`
|
24
|
+
# in scenarios where there are more jobs than threads, without this being async we
|
25
|
+
# would potentially stop polling
|
26
|
+
class RevokedNonBlocking < ::Karafka::Processing::Jobs::Revoked
|
27
|
+
# Makes this job non-blocking from the start
|
28
|
+
# @param args [Array] any arguments accepted by `::Karafka::Processing::Jobs::Revoked`
|
29
|
+
def initialize(*args)
|
30
|
+
super
|
31
|
+
@non_blocking = true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -28,6 +28,18 @@ module Karafka
|
|
28
28
|
super
|
29
29
|
end
|
30
30
|
end
|
31
|
+
|
32
|
+
# @param executor [Karafka::Processing::Executor]
|
33
|
+
# @return [Karafka::Processing::Jobs::Revoked] revocation job for non LRJ
|
34
|
+
# @return [Karafka::Processing::Jobs::RevokedNonBlocking] revocation job that is
|
35
|
+
# non-blocking, so when revocation job is scheduled for LRJ it also will not block
|
36
|
+
def revoked(executor)
|
37
|
+
if executor.topic.long_running_job?
|
38
|
+
Jobs::RevokedNonBlocking.new(executor)
|
39
|
+
else
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
31
43
|
end
|
32
44
|
end
|
33
45
|
end
|
@@ -22,6 +22,7 @@ module Karafka
|
|
22
22
|
# Nothing. Just standard, automatic flow
|
23
23
|
module Default
|
24
24
|
include Base
|
25
|
+
include ::Karafka::Processing::Strategies::Default
|
25
26
|
|
26
27
|
# Apply strategy for a non-feature based flow
|
27
28
|
FEATURES = %i[].freeze
|
@@ -39,6 +40,31 @@ module Karafka
|
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
43
|
+
# Run the user consumption code
|
44
|
+
def handle_consume
|
45
|
+
# We should not run the work at all on a partition that was revoked
|
46
|
+
# This can happen primarily when an LRJ job gets to the internal worker queue and
|
47
|
+
# this partition is revoked prior processing.
|
48
|
+
unless revoked?
|
49
|
+
Karafka.monitor.instrument('consumer.consumed', caller: self) do
|
50
|
+
consume
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Mark job as successful
|
55
|
+
coordinator.consumption(self).success!
|
56
|
+
rescue StandardError => e
|
57
|
+
# If failed, mark as failed
|
58
|
+
coordinator.consumption(self).failure!(e)
|
59
|
+
|
60
|
+
# Re-raise so reported in the consumer
|
61
|
+
raise e
|
62
|
+
ensure
|
63
|
+
# We need to decrease number of jobs that this coordinator coordinates as it has
|
64
|
+
# finished
|
65
|
+
coordinator.decrement
|
66
|
+
end
|
67
|
+
|
42
68
|
# Standard flow without any features
|
43
69
|
def handle_after_consume
|
44
70
|
coordinator.on_finished do |last_group_message|
|
@@ -22,6 +22,11 @@ module Karafka
|
|
22
22
|
raise NotImplementedError, 'Implement in a subclass'
|
23
23
|
end
|
24
24
|
|
25
|
+
# What should happen in the processing
|
26
|
+
def handle_consume
|
27
|
+
raise NotImplementedError, 'Implement in a subclass'
|
28
|
+
end
|
29
|
+
|
25
30
|
# Post-consumption handling
|
26
31
|
def handle_after_consume
|
27
32
|
raise NotImplementedError, 'Implement in a subclass'
|
@@ -23,6 +23,25 @@ module Karafka
|
|
23
23
|
coordinator.pause_tracker.increment
|
24
24
|
end
|
25
25
|
|
26
|
+
# Run the user consumption code
|
27
|
+
def handle_consume
|
28
|
+
Karafka.monitor.instrument('consumer.consumed', caller: self) do
|
29
|
+
consume
|
30
|
+
end
|
31
|
+
|
32
|
+
# Mark job as successful
|
33
|
+
coordinator.consumption(self).success!
|
34
|
+
rescue StandardError => e
|
35
|
+
# If failed, mark as failed
|
36
|
+
coordinator.consumption(self).failure!(e)
|
37
|
+
|
38
|
+
# Re-raise so reported in the consumer
|
39
|
+
raise e
|
40
|
+
ensure
|
41
|
+
# We need to decrease number of jobs that this coordinator coordinates as it has finished
|
42
|
+
coordinator.decrement
|
43
|
+
end
|
44
|
+
|
26
45
|
# Standard flow marks work as consumed and moves on if everything went ok.
|
27
46
|
# If there was a processing error, we will pause and continue from the next message
|
28
47
|
# (next that is +1 from the last one that was successfully marked as consumed)
|
data/lib/karafka/railtie.rb
CHANGED
@@ -45,6 +45,9 @@ if rails
|
|
45
45
|
|
46
46
|
next unless Rails.env.development?
|
47
47
|
next unless ENV.key?('KARAFKA_CLI')
|
48
|
+
# If we are already publishing to STDOUT, no need to add it again.
|
49
|
+
# If added again, would print stuff twice
|
50
|
+
next if ActiveSupport::Logger.logger_outputs_to?(Rails.logger, $stdout)
|
48
51
|
|
49
52
|
logger = ActiveSupport::Logger.new($stdout)
|
50
53
|
# Inherit the logger level from Rails, otherwise would always run with the debug level
|
@@ -82,7 +82,7 @@ module Karafka
|
|
82
82
|
# @param block [Proc] further topics definitions
|
83
83
|
def subscription_group(subscription_group_name = SecureRandom.uuid, &block)
|
84
84
|
consumer_group('app') do
|
85
|
-
target.public_send(:subscription_group=, subscription_group_name, &block)
|
85
|
+
target.public_send(:subscription_group=, subscription_group_name.to_s, &block)
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
@@ -31,7 +31,10 @@ module Karafka
|
|
31
31
|
|
32
32
|
# @return [Boolean] true if this consumer group should be active in our current process
|
33
33
|
def active?
|
34
|
-
Karafka::
|
34
|
+
cgs = Karafka::App.config.internal.routing.active.consumer_groups
|
35
|
+
|
36
|
+
# When empty it means no groups were specified, hence all should be used
|
37
|
+
cgs.empty? || cgs.include?(name)
|
35
38
|
end
|
36
39
|
|
37
40
|
# Builds a topic representation inside of a current consumer group route
|
@@ -50,9 +53,9 @@ module Karafka
|
|
50
53
|
|
51
54
|
# Assigns the current subscription group id based on the defined one and allows for further
|
52
55
|
# topic definition
|
53
|
-
# @param name [String, Symbol]
|
56
|
+
# @param name [String, Symbol] name of the current subscription group
|
54
57
|
# @param block [Proc] block that may include topics definitions
|
55
|
-
def subscription_group=(name, &block)
|
58
|
+
def subscription_group=(name = SecureRandom.uuid, &block)
|
56
59
|
# We cast it here, so the routing supports symbol based but that's anyhow later on
|
57
60
|
# validated as a string
|
58
61
|
@current_subscription_group_id = name
|
@@ -8,7 +8,7 @@ module Karafka
|
|
8
8
|
# @note One subscription group will always belong to one consumer group, but one consumer
|
9
9
|
# group can have multiple subscription groups.
|
10
10
|
class SubscriptionGroup
|
11
|
-
attr_reader :id, :topics, :kafka
|
11
|
+
attr_reader :id, :name, :topics, :kafka
|
12
12
|
|
13
13
|
# @param position [Integer] position of this subscription group in all the subscriptions
|
14
14
|
# groups array. We need to have this value for sake of static group memberships, where
|
@@ -16,7 +16,8 @@ module Karafka
|
|
16
16
|
# @param topics [Karafka::Routing::Topics] all the topics that share the same key settings
|
17
17
|
# @return [SubscriptionGroup] built subscription group
|
18
18
|
def initialize(position, topics)
|
19
|
-
@
|
19
|
+
@name = topics.first.subscription_group
|
20
|
+
@id = "#{@name}_#{position}"
|
20
21
|
@position = position
|
21
22
|
@topics = topics
|
22
23
|
@kafka = build_kafka
|
@@ -38,6 +39,14 @@ module Karafka
|
|
38
39
|
@topics.first.max_wait_time
|
39
40
|
end
|
40
41
|
|
42
|
+
# @return [Boolean] is this subscription group one of active once
|
43
|
+
def active?
|
44
|
+
sgs = Karafka::App.config.internal.routing.active.subscription_groups
|
45
|
+
|
46
|
+
# When empty it means no groups were specified, hence all should be used
|
47
|
+
sgs.empty? || sgs.include?(name)
|
48
|
+
end
|
49
|
+
|
41
50
|
private
|
42
51
|
|
43
52
|
# @return [Hash] kafka settings are a bit special. They are exactly the same for all of the
|
@@ -75,6 +75,14 @@ module Karafka
|
|
75
75
|
consumer
|
76
76
|
end
|
77
77
|
|
78
|
+
# @return [Boolean] should this topic be in use
|
79
|
+
def active?
|
80
|
+
topics = Karafka::App.config.internal.routing.active.topics
|
81
|
+
|
82
|
+
# When empty it means no topics were specified, hence all should be used
|
83
|
+
topics.empty? || topics.include?(name)
|
84
|
+
end
|
85
|
+
|
78
86
|
# @return [Hash] hash with all the topic attributes
|
79
87
|
# @note This is being used when we validate the consumer_group and its topics
|
80
88
|
def to_h
|
@@ -23,6 +23,14 @@ module Karafka
|
|
23
23
|
@accumulator.each(&block)
|
24
24
|
end
|
25
25
|
|
26
|
+
# Allows us to remove elements from the topics
|
27
|
+
#
|
28
|
+
# Block to decide what to delete
|
29
|
+
# @param block [Proc]
|
30
|
+
def delete_if(&block)
|
31
|
+
@accumulator.delete_if(&block)
|
32
|
+
end
|
33
|
+
|
26
34
|
# Finds topic by its name
|
27
35
|
#
|
28
36
|
# @param topic_name [String] topic name
|
data/lib/karafka/server.rb
CHANGED
@@ -20,11 +20,19 @@ module Karafka
|
|
20
20
|
# Set of workers
|
21
21
|
attr_accessor :workers
|
22
22
|
|
23
|
-
# Writer for list of consumer groups that we want to consume in our current process context
|
24
|
-
attr_writer :consumer_groups
|
25
|
-
|
26
23
|
# Method which runs app
|
27
24
|
def run
|
25
|
+
self.listeners = []
|
26
|
+
self.workers = []
|
27
|
+
|
28
|
+
# We need to validate this prior to running because it may be executed also from the
|
29
|
+
# embedded
|
30
|
+
# We cannot validate this during the start because config needs to be populated and routes
|
31
|
+
# need to be defined.
|
32
|
+
Contracts::ServerCliOptions.new.validate!(
|
33
|
+
Karafka::App.config.internal.routing.active.to_h
|
34
|
+
)
|
35
|
+
|
28
36
|
process.on_sigint { stop }
|
29
37
|
process.on_sigquit { stop }
|
30
38
|
process.on_sigterm { stop }
|
@@ -49,13 +57,6 @@ module Karafka
|
|
49
57
|
raise e
|
50
58
|
end
|
51
59
|
|
52
|
-
# @return [Array<String>] array with names of consumer groups that should be consumed in a
|
53
|
-
# current server context
|
54
|
-
def consumer_groups
|
55
|
-
# If not specified, a server will listen on all the topics
|
56
|
-
@consumer_groups ||= Karafka::App.consumer_groups.map(&:name).freeze
|
57
|
-
end
|
58
|
-
|
59
60
|
# Starts Karafka with a supervision
|
60
61
|
# @note We don't need to sleep because Karafka::Fetcher is locking and waiting to
|
61
62
|
# finish loop (and it won't happen until we explicitly want to stop)
|
data/lib/karafka/setup/config.rb
CHANGED
@@ -107,6 +107,14 @@ module Karafka
|
|
107
107
|
# option subscription_groups_builder [Routing::SubscriptionGroupsBuilder] subscription
|
108
108
|
# group builder
|
109
109
|
setting :subscription_groups_builder, default: Routing::SubscriptionGroupsBuilder.new
|
110
|
+
|
111
|
+
# Internally assigned list of limits on routings active for the current process
|
112
|
+
# This should be overwritten by the CLI command
|
113
|
+
setting :active do
|
114
|
+
setting :consumer_groups, default: [].freeze
|
115
|
+
setting :subscription_groups, default: [].freeze
|
116
|
+
setting :topics, default: [].freeze
|
117
|
+
end
|
110
118
|
end
|
111
119
|
|
112
120
|
setting :processing do
|
@@ -142,16 +150,18 @@ module Karafka
|
|
142
150
|
# Configuring method
|
143
151
|
# @param block [Proc] block we want to execute with the config instance
|
144
152
|
def setup(&block)
|
153
|
+
# Will prepare and verify license if present
|
154
|
+
Licenser.prepare_and_verify(config.license)
|
155
|
+
# Will configure all the pro components
|
156
|
+
# This needs to happen before end user configuration as the end user may overwrite some
|
157
|
+
# of the pro defaults with custom components
|
158
|
+
Pro::Loader.setup(config) if Karafka.pro?
|
159
|
+
|
145
160
|
configure(&block)
|
146
161
|
merge_kafka_defaults!(config)
|
147
162
|
|
148
163
|
Contracts::Config.new.validate!(config.to_h)
|
149
164
|
|
150
|
-
licenser = Licenser.new
|
151
|
-
|
152
|
-
# Tries to load our license gem and if present will try to load the correct license
|
153
|
-
licenser.prepare_and_verify(config.license)
|
154
|
-
|
155
165
|
configure_components
|
156
166
|
|
157
167
|
Karafka::App.initialized!
|
@@ -188,12 +198,6 @@ module Karafka
|
|
188
198
|
producer_config.kafka = AttributesMap.producer(config.kafka.dup)
|
189
199
|
producer_config.logger = config.logger
|
190
200
|
end
|
191
|
-
|
192
|
-
return unless Karafka.pro?
|
193
|
-
|
194
|
-
# Runs the pro loader that includes all the pro components
|
195
|
-
require 'karafka/pro/loader'
|
196
|
-
Pro::Loader.setup(config)
|
197
201
|
end
|
198
202
|
end
|
199
203
|
end
|
data/lib/karafka/version.rb
CHANGED
data/lib/karafka.rb
CHANGED
@@ -100,5 +100,14 @@ loader.eager_load
|
|
100
100
|
# nor included here
|
101
101
|
::Karafka::Routing::Features::Base.load_all
|
102
102
|
|
103
|
+
# We need to detect and require (not setup) Pro components during the gem load, because we need
|
104
|
+
# to make pro components available in case anyone wants to use them as a base to their own
|
105
|
+
# custom components. Otherwise inheritance would not work.
|
106
|
+
Karafka::Licenser.detect do
|
107
|
+
require 'karafka/pro/loader'
|
108
|
+
|
109
|
+
Karafka::Pro::Loader.require_all
|
110
|
+
end
|
111
|
+
|
103
112
|
# Load railtie after everything else is ready so we know we can rely on it.
|
104
113
|
require 'karafka/railtie'
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: karafka
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.22
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maciej Mensfeld
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
Qf04B9ceLUaC4fPVEz10FyobjaFoY4i32xRto3XnrzeAgfEe4swLq8bQsR3w/EF3
|
36
36
|
MGU0FeSV2Yj7Xc2x/7BzLK8xQn5l7Yy75iPF+KP3vVmDHnNl
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date: 2022-
|
38
|
+
date: 2022-12-02 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: karafka-core
|
@@ -119,7 +119,11 @@ dependencies:
|
|
119
119
|
- - "~>"
|
120
120
|
- !ruby/object:Gem::Version
|
121
121
|
version: '2.3'
|
122
|
-
description:
|
122
|
+
description: |2
|
123
|
+
Karafka is Ruby and Rails efficient Kafka processing framework.
|
124
|
+
|
125
|
+
Karafka allows you to capture everything that happens in your systems in large scale,
|
126
|
+
without having to focus on things that are not your business domain.
|
123
127
|
email:
|
124
128
|
- contact@karafka.io
|
125
129
|
executables:
|
@@ -227,6 +231,7 @@ files:
|
|
227
231
|
- lib/karafka/pro/performance_tracker.rb
|
228
232
|
- lib/karafka/pro/processing/coordinator.rb
|
229
233
|
- lib/karafka/pro/processing/jobs/consume_non_blocking.rb
|
234
|
+
- lib/karafka/pro/processing/jobs/revoked_non_blocking.rb
|
230
235
|
- lib/karafka/pro/processing/jobs_builder.rb
|
231
236
|
- lib/karafka/pro/processing/partitioner.rb
|
232
237
|
- lib/karafka/pro/processing/scheduler.rb
|
@@ -353,5 +358,5 @@ requirements: []
|
|
353
358
|
rubygems_version: 3.3.7
|
354
359
|
signing_key:
|
355
360
|
specification_version: 4
|
356
|
-
summary:
|
361
|
+
summary: Karafka is Ruby and Rails efficient Kafka processing framework.
|
357
362
|
test_files: []
|
metadata.gz.sig
CHANGED
Binary file
|