karafka 2.0.0 → 2.1.0
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/FUNDING.yml +1 -0
- data/.github/workflows/ci.yml +40 -16
- data/.rspec +4 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +459 -796
- data/Gemfile +3 -2
- data/Gemfile.lock +44 -32
- data/LICENSE +1 -1
- data/README.md +22 -17
- data/bin/integrations +86 -26
- data/bin/karafka +1 -13
- data/bin/rspecs +6 -0
- data/bin/verify_license_integrity +37 -0
- data/certs/cert_chain.pem +26 -0
- data/config/{errors.yml → locales/errors.yml} +34 -6
- data/config/locales/pro_errors.yml +34 -0
- data/docker-compose.yml +4 -30
- data/karafka.gemspec +17 -10
- data/lib/active_job/karafka.rb +3 -7
- data/lib/active_job/queue_adapters/karafka_adapter.rb +7 -1
- data/lib/karafka/active_job/consumer.rb +29 -6
- data/lib/karafka/active_job/current_attributes/loading.rb +36 -0
- data/lib/karafka/active_job/current_attributes/persistence.rb +28 -0
- data/lib/karafka/active_job/current_attributes.rb +42 -0
- data/lib/karafka/active_job/dispatcher.rb +34 -3
- data/lib/karafka/active_job/job_options_contract.rb +13 -2
- data/lib/karafka/admin.rb +246 -0
- data/lib/karafka/app.rb +30 -7
- data/lib/karafka/base_consumer.rb +124 -82
- data/lib/karafka/cli/base.rb +20 -0
- data/lib/karafka/cli/console.rb +13 -8
- data/lib/karafka/cli/info.rb +3 -2
- data/lib/karafka/cli/server.rb +55 -8
- data/lib/karafka/cli/topics.rb +146 -0
- data/lib/karafka/connection/client.rb +118 -61
- data/lib/karafka/connection/consumer_group_coordinator.rb +48 -0
- data/lib/karafka/connection/listener.rb +78 -30
- data/lib/karafka/connection/listeners_batch.rb +18 -2
- data/lib/karafka/connection/rebalance_manager.rb +3 -0
- data/lib/karafka/contracts/config.rb +1 -1
- data/lib/karafka/contracts/consumer_group.rb +22 -9
- data/lib/karafka/contracts/server_cli_options.rb +71 -11
- data/lib/karafka/contracts/topic.rb +65 -0
- data/lib/karafka/contracts.rb +1 -1
- data/lib/karafka/embedded.rb +36 -0
- data/lib/karafka/errors.rb +6 -4
- data/lib/karafka/helpers/colorize.rb +6 -0
- data/lib/karafka/instrumentation/callbacks/error.rb +3 -4
- data/lib/karafka/instrumentation/callbacks/statistics.rb +14 -4
- data/lib/karafka/instrumentation/logger_listener.rb +151 -22
- data/lib/karafka/instrumentation/notifications.rb +26 -7
- data/lib/karafka/instrumentation/proctitle_listener.rb +7 -16
- data/lib/karafka/instrumentation/vendors/datadog/dashboard.json +1 -1
- data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +153 -0
- data/lib/karafka/instrumentation/vendors/datadog/{listener.rb → metrics_listener.rb} +62 -35
- data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +166 -0
- data/lib/karafka/licenser.rb +53 -50
- data/lib/karafka/messages/builders/batch_metadata.rb +8 -8
- data/lib/karafka/messages/builders/messages.rb +6 -2
- data/lib/karafka/messages/message.rb +14 -2
- data/lib/karafka/messages/messages.rb +5 -0
- data/lib/karafka/messages/parser.rb +14 -0
- data/lib/karafka/patches/rdkafka/bindings.rb +132 -0
- data/lib/karafka/pro/active_job/consumer.rb +10 -9
- data/lib/karafka/pro/active_job/dispatcher.rb +28 -3
- data/lib/karafka/pro/active_job/job_options_contract.rb +16 -3
- data/lib/karafka/pro/encryption/cipher.rb +58 -0
- data/lib/karafka/pro/encryption/contracts/config.rb +79 -0
- data/lib/karafka/pro/encryption/errors.rb +24 -0
- data/lib/karafka/pro/encryption/messages/middleware.rb +46 -0
- data/lib/karafka/pro/encryption/messages/parser.rb +56 -0
- data/lib/karafka/pro/encryption/setup/config.rb +48 -0
- data/lib/karafka/pro/encryption.rb +47 -0
- data/lib/karafka/pro/iterator.rb +253 -0
- data/lib/karafka/pro/loader.rb +52 -26
- data/lib/karafka/pro/performance_tracker.rb +10 -6
- data/lib/karafka/pro/processing/collapser.rb +62 -0
- data/lib/karafka/pro/processing/coordinator.rb +92 -16
- data/lib/karafka/pro/processing/filters/base.rb +61 -0
- data/lib/karafka/pro/processing/filters/delayer.rb +70 -0
- data/lib/karafka/pro/processing/filters/expirer.rb +51 -0
- data/lib/karafka/pro/processing/filters/throttler.rb +84 -0
- data/lib/karafka/pro/processing/filters/virtual_limiter.rb +52 -0
- data/lib/karafka/pro/processing/filters_applier.rb +104 -0
- data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +7 -5
- data/lib/karafka/pro/processing/jobs/revoked_non_blocking.rb +37 -0
- data/lib/karafka/pro/processing/jobs_builder.rb +22 -4
- data/lib/karafka/pro/processing/partitioner.rb +33 -24
- data/lib/karafka/pro/processing/scheduler.rb +27 -8
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +68 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +74 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom.rb +72 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +76 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +64 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +68 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_mom.rb +64 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +69 -0
- data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom.rb +38 -0
- data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom_vp.rb +64 -0
- data/lib/karafka/pro/processing/strategies/aj/ftr_mom.rb +38 -0
- data/lib/karafka/pro/processing/strategies/aj/ftr_mom_vp.rb +58 -0
- data/lib/karafka/pro/processing/strategies/aj/lrj_mom.rb +37 -0
- data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +79 -0
- data/lib/karafka/pro/processing/strategies/aj/mom.rb +36 -0
- data/lib/karafka/pro/processing/strategies/aj/mom_vp.rb +52 -0
- data/lib/karafka/pro/processing/strategies/base.rb +26 -0
- data/lib/karafka/pro/processing/strategies/default.rb +105 -0
- data/lib/karafka/pro/processing/strategies/dlq/default.rb +131 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr.rb +61 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj.rb +75 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +71 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom_vp.rb +43 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_vp.rb +41 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +69 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_mom_vp.rb +41 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_vp.rb +40 -0
- data/lib/karafka/pro/processing/strategies/dlq/lrj.rb +63 -0
- data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +63 -0
- data/lib/karafka/pro/processing/strategies/dlq/lrj_mom_vp.rb +36 -0
- data/lib/karafka/pro/processing/strategies/dlq/lrj_vp.rb +39 -0
- data/lib/karafka/pro/processing/strategies/dlq/mom.rb +68 -0
- data/lib/karafka/pro/processing/strategies/dlq/mom_vp.rb +37 -0
- data/lib/karafka/pro/processing/strategies/dlq/vp.rb +40 -0
- data/lib/karafka/pro/processing/strategies/ftr/default.rb +104 -0
- data/lib/karafka/pro/processing/strategies/ftr/vp.rb +40 -0
- data/lib/karafka/pro/processing/strategies/lrj/default.rb +87 -0
- data/lib/karafka/pro/processing/strategies/lrj/ftr.rb +69 -0
- data/lib/karafka/pro/processing/strategies/lrj/ftr_mom.rb +67 -0
- data/lib/karafka/pro/processing/strategies/lrj/ftr_mom_vp.rb +40 -0
- data/lib/karafka/pro/processing/strategies/lrj/ftr_vp.rb +39 -0
- data/lib/karafka/pro/processing/strategies/lrj/mom.rb +80 -0
- data/lib/karafka/pro/processing/strategies/lrj/mom_vp.rb +38 -0
- data/lib/karafka/pro/processing/strategies/lrj/vp.rb +36 -0
- data/lib/karafka/pro/processing/strategies/mom/default.rb +46 -0
- data/lib/karafka/pro/processing/strategies/mom/ftr.rb +53 -0
- data/lib/karafka/pro/processing/strategies/mom/ftr_vp.rb +37 -0
- data/lib/karafka/pro/processing/strategies/mom/vp.rb +35 -0
- data/lib/karafka/pro/processing/strategies/vp/default.rb +104 -0
- data/lib/karafka/pro/{contracts/base.rb → processing/strategies.rb} +6 -5
- data/lib/karafka/pro/processing/strategy_selector.rb +84 -0
- data/lib/karafka/pro/processing/virtual_offset_manager.rb +147 -0
- data/lib/karafka/pro/routing/features/base.rb +24 -0
- data/lib/karafka/pro/routing/features/dead_letter_queue/contract.rb +50 -0
- data/lib/karafka/pro/routing/features/dead_letter_queue.rb +27 -0
- data/lib/karafka/pro/routing/{builder_extensions.rb → features/delaying/config.rb} +8 -11
- data/lib/karafka/pro/routing/features/delaying/contract.rb +38 -0
- data/lib/karafka/pro/routing/features/delaying/topic.rb +59 -0
- data/lib/karafka/pro/routing/features/delaying.rb +29 -0
- data/lib/karafka/pro/routing/features/expiring/config.rb +27 -0
- data/lib/karafka/pro/routing/features/expiring/contract.rb +38 -0
- data/lib/karafka/pro/routing/features/expiring/topic.rb +59 -0
- data/lib/karafka/pro/routing/features/expiring.rb +27 -0
- data/lib/karafka/pro/routing/features/filtering/config.rb +40 -0
- data/lib/karafka/pro/routing/features/filtering/contract.rb +41 -0
- data/lib/karafka/pro/routing/features/filtering/topic.rb +51 -0
- data/lib/karafka/pro/routing/features/filtering.rb +27 -0
- data/lib/karafka/pro/routing/features/long_running_job/config.rb +28 -0
- data/lib/karafka/pro/routing/features/long_running_job/contract.rb +37 -0
- data/lib/karafka/pro/routing/features/long_running_job/topic.rb +42 -0
- data/lib/karafka/pro/routing/features/long_running_job.rb +28 -0
- data/lib/karafka/pro/routing/features/throttling/config.rb +32 -0
- data/lib/karafka/pro/routing/features/throttling/contract.rb +41 -0
- data/lib/karafka/pro/routing/features/throttling/topic.rb +69 -0
- data/lib/karafka/pro/routing/features/throttling.rb +30 -0
- data/lib/karafka/pro/routing/features/virtual_partitions/config.rb +30 -0
- data/lib/karafka/pro/routing/features/virtual_partitions/contract.rb +52 -0
- data/lib/karafka/pro/routing/features/virtual_partitions/topic.rb +56 -0
- data/lib/karafka/pro/routing/features/virtual_partitions.rb +27 -0
- data/lib/karafka/process.rb +23 -8
- data/lib/karafka/processing/coordinator.rb +102 -23
- data/lib/karafka/processing/coordinators_buffer.rb +5 -1
- data/lib/karafka/processing/executor.rb +51 -21
- data/lib/karafka/processing/executors_buffer.rb +10 -26
- data/lib/karafka/processing/jobs/base.rb +6 -2
- data/lib/karafka/processing/jobs/consume.rb +8 -5
- data/lib/karafka/processing/jobs/idle.rb +24 -0
- data/lib/karafka/processing/jobs_builder.rb +2 -3
- data/lib/karafka/processing/jobs_queue.rb +14 -8
- data/lib/karafka/processing/partitioner.rb +3 -1
- data/lib/karafka/processing/result.rb +14 -1
- data/lib/karafka/processing/strategies/aj_dlq_mom.rb +44 -0
- data/lib/karafka/processing/strategies/aj_mom.rb +21 -0
- data/lib/karafka/processing/strategies/base.rb +52 -0
- data/lib/karafka/processing/strategies/default.rb +142 -0
- data/lib/karafka/processing/strategies/dlq.rb +88 -0
- data/lib/karafka/processing/strategies/dlq_mom.rb +49 -0
- data/lib/karafka/processing/strategies/mom.rb +29 -0
- data/lib/karafka/processing/strategy_selector.rb +47 -0
- data/lib/karafka/processing/worker.rb +8 -3
- data/lib/karafka/railtie.rb +24 -12
- data/lib/karafka/routing/activity_manager.rb +84 -0
- data/lib/karafka/routing/builder.rb +20 -0
- data/lib/karafka/routing/consumer_group.rb +38 -4
- data/lib/karafka/routing/consumer_mapper.rb +0 -10
- data/lib/karafka/routing/features/active_job/builder.rb +33 -0
- data/lib/karafka/routing/features/active_job/config.rb +15 -0
- data/lib/karafka/routing/features/active_job/contract.rb +41 -0
- data/lib/karafka/routing/features/active_job/topic.rb +33 -0
- data/lib/karafka/routing/features/active_job.rb +13 -0
- data/lib/karafka/routing/features/base/expander.rb +53 -0
- data/lib/karafka/routing/features/base.rb +34 -0
- data/lib/karafka/routing/features/dead_letter_queue/config.rb +19 -0
- data/lib/karafka/routing/features/dead_letter_queue/contract.rb +42 -0
- data/lib/karafka/routing/features/dead_letter_queue/topic.rb +41 -0
- data/lib/karafka/routing/features/dead_letter_queue.rb +16 -0
- data/lib/karafka/routing/features/declaratives/config.rb +18 -0
- data/lib/karafka/routing/features/declaratives/contract.rb +30 -0
- data/lib/karafka/routing/features/declaratives/topic.rb +44 -0
- data/lib/karafka/routing/features/declaratives.rb +14 -0
- data/lib/karafka/routing/features/manual_offset_management/config.rb +15 -0
- data/lib/karafka/routing/features/manual_offset_management/contract.rb +24 -0
- data/lib/karafka/routing/features/manual_offset_management/topic.rb +35 -0
- data/lib/karafka/routing/features/manual_offset_management.rb +18 -0
- data/lib/karafka/routing/proxy.rb +18 -20
- data/lib/karafka/routing/router.rb +27 -2
- data/lib/karafka/routing/subscription_group.rb +43 -5
- data/lib/karafka/routing/subscription_groups_builder.rb +6 -1
- data/lib/karafka/routing/topic.rb +17 -5
- data/lib/karafka/routing/topics.rb +8 -0
- data/lib/karafka/runner.rb +1 -0
- data/lib/karafka/serialization/json/deserializer.rb +1 -0
- data/lib/karafka/server.rb +58 -28
- data/lib/karafka/setup/attributes_map.rb +344 -0
- data/lib/karafka/setup/config.rb +48 -19
- data/lib/karafka/status.rb +35 -7
- data/lib/karafka/templates/karafka.rb.erb +14 -6
- data/lib/karafka/time_trackers/base.rb +1 -6
- data/lib/karafka/time_trackers/pause.rb +49 -19
- data/lib/karafka/time_trackers/poll.rb +7 -3
- data/lib/karafka/version.rb +1 -1
- data/lib/karafka.rb +56 -3
- data/renovate.json +6 -0
- data.tar.gz.sig +0 -0
- metadata +189 -61
- metadata.gz.sig +0 -0
- data/bin/wait_for_kafka +0 -20
- data/certs/mensfeld.pem +0 -25
- data/lib/karafka/active_job/routing/extensions.rb +0 -31
- data/lib/karafka/contracts/consumer_group_topic.rb +0 -42
- data/lib/karafka/instrumentation.rb +0 -21
- data/lib/karafka/pro/base_consumer.rb +0 -82
- data/lib/karafka/pro/contracts/consumer_group.rb +0 -34
- data/lib/karafka/pro/contracts/consumer_group_topic.rb +0 -33
- data/lib/karafka/pro/routing/topic_extensions.rb +0 -38
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
en:
|
|
2
|
+
validations:
|
|
3
|
+
topic:
|
|
4
|
+
virtual_partitions.partitioner_respond_to_call: needs to be defined and needs to respond to `#call`
|
|
5
|
+
virtual_partitions.max_partitions_format: needs to be equal or more than 1
|
|
6
|
+
|
|
7
|
+
long_running_job.active_format: needs to be either true or false
|
|
8
|
+
|
|
9
|
+
dead_letter_queue_with_virtual_partitions: when using Dead Letter Queue with Virtual Partitions, at least one retry is required.
|
|
10
|
+
|
|
11
|
+
throttling.active_format: needs to be either true or false
|
|
12
|
+
throttling.limit_format: needs to be equal or more than 1
|
|
13
|
+
throttling.interval_format: needs to be equal or more than 1
|
|
14
|
+
|
|
15
|
+
filtering.active_missing: needs to be present
|
|
16
|
+
filtering.factory_format: 'needs to respond to #call'
|
|
17
|
+
filtering.factories_format: 'needs to contain only factories responding to #call'
|
|
18
|
+
filtering.active_format: 'needs to be boolean'
|
|
19
|
+
|
|
20
|
+
expiring.ttl_format: 'needs to be equal or more than 0 and an integer'
|
|
21
|
+
expiring.active_format: 'needs to be boolean'
|
|
22
|
+
|
|
23
|
+
delaying.delay_format: 'needs to be equal or more than 0 and an integer'
|
|
24
|
+
delaying.active_format: 'needs to be boolean'
|
|
25
|
+
|
|
26
|
+
config:
|
|
27
|
+
encryption.active_format: 'needs to be either true or false'
|
|
28
|
+
encryption.public_key_invalid: 'is not a valid public RSA key'
|
|
29
|
+
encryption.public_key_needs_to_be_public: 'is a private RSA key not a public one'
|
|
30
|
+
encryption.private_keys_format: 'needs to be a hash of version and private key value'
|
|
31
|
+
encryption.private_keys_need_to_be_private: 'all keys need to be private'
|
|
32
|
+
encryption.version_format: must be a non-empty string
|
|
33
|
+
encryption.public_key_format: 'is not a valid public RSA key'
|
|
34
|
+
encryption.private_keys_invalid: 'contains an invalid private RSA key string'
|
data/docker-compose.yml
CHANGED
|
@@ -3,8 +3,10 @@ services:
|
|
|
3
3
|
zookeeper:
|
|
4
4
|
container_name: karafka_20_zookeeper
|
|
5
5
|
image: wurstmeister/zookeeper
|
|
6
|
+
restart: on-failure
|
|
6
7
|
ports:
|
|
7
8
|
- '2181:2181'
|
|
9
|
+
|
|
8
10
|
kafka:
|
|
9
11
|
container_name: karafka_20_kafka
|
|
10
12
|
image: wurstmeister/kafka
|
|
@@ -16,38 +18,10 @@ services:
|
|
|
16
18
|
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
|
17
19
|
KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true'
|
|
18
20
|
KAFKA_CREATE_TOPICS:
|
|
19
|
-
"
|
|
20
|
-
integrations_01_02:2:1,\
|
|
21
|
-
integrations_02_02:2:1,\
|
|
22
|
-
integrations_03_02:2:1,\
|
|
23
|
-
integrations_04_02:2:1,\
|
|
24
|
-
integrations_05_02:2:1,\
|
|
25
|
-
integrations_06_02:2:1,\
|
|
26
|
-
integrations_07_02:2:1,\
|
|
27
|
-
integrations_08_02:2:1,\
|
|
28
|
-
integrations_09_02:2:1,\
|
|
29
|
-
integrations_10_02:2:1,\
|
|
30
|
-
integrations_11_02:2:1,\
|
|
31
|
-
integrations_12_02:2:1,\
|
|
32
|
-
integrations_13_02:2:1,\
|
|
33
|
-
integrations_14_02:2:1,\
|
|
34
|
-
integrations_15_02:2:1,\
|
|
35
|
-
integrations_16_02:2:1,\
|
|
36
|
-
integrations_17_02:2:1,\
|
|
37
|
-
integrations_18_02:2:1,\
|
|
38
|
-
integrations_19_02:2:1,\
|
|
39
|
-
integrations_20_02:2:1,\
|
|
40
|
-
integrations_21_02:2:1,\
|
|
41
|
-
integrations_00_03:3:1,\
|
|
42
|
-
integrations_01_03:3:1,\
|
|
43
|
-
integrations_02_03:3:1,\
|
|
44
|
-
integrations_03_03:3:1,\
|
|
45
|
-
integrations_04_03:3:1,\
|
|
46
|
-
integrations_00_10:10:1,\
|
|
47
|
-
integrations_01_10:10:1,\
|
|
48
|
-
benchmarks_00_01:1:1,\
|
|
21
|
+
"benchmarks_00_01:1:1,\
|
|
49
22
|
benchmarks_00_05:5:1,\
|
|
50
23
|
benchmarks_01_05:5:1,\
|
|
51
24
|
benchmarks_00_10:10:1"
|
|
52
25
|
volumes:
|
|
53
26
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
27
|
+
restart: on-failure
|
data/karafka.gemspec
CHANGED
|
@@ -10,31 +10,38 @@ Gem::Specification.new do |spec|
|
|
|
10
10
|
spec.version = ::Karafka::VERSION
|
|
11
11
|
spec.platform = Gem::Platform::RUBY
|
|
12
12
|
spec.authors = ['Maciej Mensfeld']
|
|
13
|
-
spec.email = %w[
|
|
13
|
+
spec.email = %w[contact@karafka.io]
|
|
14
14
|
spec.homepage = 'https://karafka.io'
|
|
15
|
-
spec.
|
|
16
|
-
spec.
|
|
17
|
-
spec.
|
|
15
|
+
spec.licenses = %w[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.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
23
|
+
|
|
24
|
+
spec.add_dependency 'karafka-core', '>= 2.0.12', '< 3.0.0'
|
|
21
25
|
spec.add_dependency 'thor', '>= 0.20'
|
|
22
|
-
spec.add_dependency 'waterdrop', '>= 2.4.
|
|
26
|
+
spec.add_dependency 'waterdrop', '>= 2.4.10', '< 3.0.0'
|
|
23
27
|
spec.add_dependency 'zeitwerk', '~> 2.3'
|
|
24
28
|
|
|
25
|
-
spec.required_ruby_version = '>= 2.7.0'
|
|
26
|
-
|
|
27
29
|
if $PROGRAM_NAME.end_with?('gem')
|
|
28
30
|
spec.signing_key = File.expand_path('~/.ssh/gem-private_key.pem')
|
|
29
31
|
end
|
|
30
32
|
|
|
31
|
-
spec.cert_chain = %w[certs/
|
|
33
|
+
spec.cert_chain = %w[certs/cert_chain.pem]
|
|
32
34
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
|
|
33
35
|
spec.executables = %w[karafka]
|
|
34
36
|
spec.require_paths = %w[lib]
|
|
35
37
|
|
|
36
38
|
spec.metadata = {
|
|
39
|
+
'funding_uri' => 'https://karafka.io/#become-pro',
|
|
40
|
+
'homepage_uri' => 'https://karafka.io',
|
|
41
|
+
'changelog_uri' => 'https://github.com/karafka/karafka/blob/master/CHANGELOG.md',
|
|
42
|
+
'bug_tracker_uri' => 'https://github.com/karafka/karafka/issues',
|
|
37
43
|
'source_code_uri' => 'https://github.com/karafka/karafka',
|
|
44
|
+
'documentation_uri' => 'https://karafka.io/docs',
|
|
38
45
|
'rubygems_mfa_required' => 'true'
|
|
39
46
|
}
|
|
40
47
|
end
|
data/lib/active_job/karafka.rb
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
begin
|
|
4
|
-
|
|
4
|
+
# Do not load active job if already loaded
|
|
5
|
+
require 'active_job' unless Object.const_defined?('ActiveJob')
|
|
6
|
+
|
|
5
7
|
require_relative 'queue_adapters/karafka_adapter'
|
|
6
8
|
|
|
7
9
|
module ActiveJob
|
|
@@ -10,12 +12,6 @@ begin
|
|
|
10
12
|
module Karafka
|
|
11
13
|
end
|
|
12
14
|
end
|
|
13
|
-
|
|
14
|
-
# We extend routing builder by adding a simple wrapper for easier jobs topics defining
|
|
15
|
-
# This needs to be extended here as it is going to be used in karafka routes, hence doing that in
|
|
16
|
-
# the railtie initializer would be too late
|
|
17
|
-
::Karafka::Routing::Builder.include ::Karafka::ActiveJob::Routing::Extensions
|
|
18
|
-
::Karafka::Routing::Proxy.include ::Karafka::ActiveJob::Routing::Extensions
|
|
19
15
|
rescue LoadError
|
|
20
16
|
# We extend ActiveJob stuff in the railtie
|
|
21
17
|
end
|
|
@@ -11,7 +11,13 @@ module ActiveJob
|
|
|
11
11
|
#
|
|
12
12
|
# @param job [Object] job that should be enqueued
|
|
13
13
|
def enqueue(job)
|
|
14
|
-
::Karafka::App.config.internal.active_job.dispatcher.
|
|
14
|
+
::Karafka::App.config.internal.active_job.dispatcher.dispatch(job)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Enqueues multiple jobs in one go
|
|
18
|
+
# @param jobs [Array<Object>] jobs that we want to enqueue
|
|
19
|
+
def enqueue_all(jobs)
|
|
20
|
+
::Karafka::App.config.internal.active_job.dispatcher.dispatch_many(jobs)
|
|
15
21
|
end
|
|
16
22
|
|
|
17
23
|
# Raises info, that Karafka backend does not support scheduling jobs
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Karafka
|
|
4
|
+
# Namespace for all the ActiveJob related things from within Karafka
|
|
4
5
|
module ActiveJob
|
|
5
6
|
# This is the consumer for ActiveJob that eats the messages enqueued with it one after another.
|
|
6
7
|
# It marks the offset after each message, so we make sure, none of the jobs is executed twice
|
|
@@ -11,16 +12,38 @@ module Karafka
|
|
|
11
12
|
messages.each do |message|
|
|
12
13
|
break if Karafka::App.stopping?
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
# We technically speaking could set this as deserializer and reference it from the
|
|
16
|
-
# message instead of using the `#raw_payload`. This is not done on purpose to simplify
|
|
17
|
-
# the ActiveJob setup here
|
|
18
|
-
::ActiveSupport::JSON.decode(message.raw_payload)
|
|
19
|
-
)
|
|
15
|
+
consume_job(message)
|
|
20
16
|
|
|
21
17
|
mark_as_consumed(message)
|
|
22
18
|
end
|
|
23
19
|
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# Consumes a message with the job and runs needed instrumentation
|
|
24
|
+
#
|
|
25
|
+
# @param job_message [Karafka::Messages::Message] message with active job
|
|
26
|
+
def consume_job(job_message)
|
|
27
|
+
with_deserialized_job(job_message) do |job|
|
|
28
|
+
tags.add(:job_class, job['job_class'])
|
|
29
|
+
|
|
30
|
+
payload = { caller: self, job: job, message: job_message }
|
|
31
|
+
|
|
32
|
+
# We publish both to make it consistent with `consumer.x` events
|
|
33
|
+
Karafka.monitor.instrument('active_job.consume', payload)
|
|
34
|
+
Karafka.monitor.instrument('active_job.consumed', payload) do
|
|
35
|
+
::ActiveJob::Base.execute(job)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @param job_message [Karafka::Messages::Message] message with active job
|
|
41
|
+
def with_deserialized_job(job_message)
|
|
42
|
+
# We technically speaking could set this as deserializer and reference it from the
|
|
43
|
+
# message instead of using the `#raw_payload`. This is not done on purpose to simplify
|
|
44
|
+
# the ActiveJob setup here
|
|
45
|
+
yield ::ActiveSupport::JSON.decode(job_message.raw_payload)
|
|
46
|
+
end
|
|
24
47
|
end
|
|
25
48
|
end
|
|
26
49
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module ActiveJob
|
|
5
|
+
module CurrentAttributes
|
|
6
|
+
# Module expanding the job deserialization to extract current attributes and load them
|
|
7
|
+
# for the time of the job execution
|
|
8
|
+
module Loading
|
|
9
|
+
# @param job_message [Karafka::Messages::Message] message with active job
|
|
10
|
+
def with_deserialized_job(job_message)
|
|
11
|
+
super(job_message) do |job|
|
|
12
|
+
resetable = []
|
|
13
|
+
|
|
14
|
+
_cattr_klasses.each do |key, cattr_klass_str|
|
|
15
|
+
next unless job.key?(key)
|
|
16
|
+
|
|
17
|
+
attributes = job.delete(key)
|
|
18
|
+
|
|
19
|
+
cattr_klass = cattr_klass_str.constantize
|
|
20
|
+
|
|
21
|
+
attributes.each do |name, value|
|
|
22
|
+
cattr_klass.public_send("#{name}=", value)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
resetable << cattr_klass
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
yield(job)
|
|
29
|
+
|
|
30
|
+
resetable.each(&:reset)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module ActiveJob
|
|
5
|
+
module CurrentAttributes
|
|
6
|
+
# Module adding the current attributes persistence into the ActiveJob jobs
|
|
7
|
+
module Persistence
|
|
8
|
+
# Alters the job serialization to inject the current attributes into the json before we
|
|
9
|
+
# send it to Kafka
|
|
10
|
+
#
|
|
11
|
+
# @param job [ActiveJob::Base] job
|
|
12
|
+
def serialize_job(job)
|
|
13
|
+
json = super(job)
|
|
14
|
+
|
|
15
|
+
_cattr_klasses.each do |key, cattr_klass_str|
|
|
16
|
+
next if json.key?(key)
|
|
17
|
+
|
|
18
|
+
attrs = cattr_klass_str.constantize.attributes
|
|
19
|
+
|
|
20
|
+
json[key] = attrs unless attrs.empty?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
json
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/current_attributes'
|
|
4
|
+
require_relative 'current_attributes/loading'
|
|
5
|
+
require_relative 'current_attributes/persistence'
|
|
6
|
+
|
|
7
|
+
# This code is based on Sidekiqs approach to persisting current attributes
|
|
8
|
+
# @see https://github.com/sidekiq/sidekiq/blob/main/lib/sidekiq/middleware/current_attributes.rb
|
|
9
|
+
module Karafka
|
|
10
|
+
module ActiveJob
|
|
11
|
+
# Module that allows to persist current attributes on Karafka jobs
|
|
12
|
+
module CurrentAttributes
|
|
13
|
+
# Allows for persistence of given current attributes via AJ + Karafka
|
|
14
|
+
#
|
|
15
|
+
# @param klasses [Array<String, Class>] classes or names of the current attributes classes
|
|
16
|
+
def persist(*klasses)
|
|
17
|
+
# Support for providing multiple classes
|
|
18
|
+
klasses = Array(klasses).flatten
|
|
19
|
+
|
|
20
|
+
[Dispatcher, Consumer]
|
|
21
|
+
.reject { |expandable| expandable.respond_to?(:_cattr_klasses) }
|
|
22
|
+
.each { |expandable| expandable.class_attribute :_cattr_klasses, default: {} }
|
|
23
|
+
|
|
24
|
+
# Do not double inject in case of running persist multiple times
|
|
25
|
+
Dispatcher.prepend(Persistence) unless Dispatcher.ancestors.include?(Persistence)
|
|
26
|
+
Consumer.prepend(Loading) unless Consumer.ancestors.include?(Loading)
|
|
27
|
+
|
|
28
|
+
klasses.map(&:to_s).each do |stringified_klass|
|
|
29
|
+
# Prevent registering same klass multiple times
|
|
30
|
+
next if Dispatcher._cattr_klasses.value?(stringified_klass)
|
|
31
|
+
|
|
32
|
+
key = "cattr_#{Dispatcher._cattr_klasses.count}"
|
|
33
|
+
|
|
34
|
+
Dispatcher._cattr_klasses[key] = stringified_klass
|
|
35
|
+
Consumer._cattr_klasses[key] = stringified_klass
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module_function :persist
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -7,20 +7,45 @@ module Karafka
|
|
|
7
7
|
# Defaults for dispatching
|
|
8
8
|
# The can be updated by using `#karafka_options` on the job
|
|
9
9
|
DEFAULTS = {
|
|
10
|
-
dispatch_method: :produce_async
|
|
10
|
+
dispatch_method: :produce_async,
|
|
11
|
+
dispatch_many_method: :produce_many_async
|
|
11
12
|
}.freeze
|
|
12
13
|
|
|
13
14
|
private_constant :DEFAULTS
|
|
14
15
|
|
|
15
16
|
# @param job [ActiveJob::Base] job
|
|
16
|
-
def
|
|
17
|
+
def dispatch(job)
|
|
17
18
|
::Karafka.producer.public_send(
|
|
18
19
|
fetch_option(job, :dispatch_method, DEFAULTS),
|
|
19
20
|
topic: job.queue_name,
|
|
20
|
-
payload: ::ActiveSupport::JSON.encode(job
|
|
21
|
+
payload: ::ActiveSupport::JSON.encode(serialize_job(job))
|
|
21
22
|
)
|
|
22
23
|
end
|
|
23
24
|
|
|
25
|
+
# Bulk dispatches multiple jobs using the Rails 7.1+ API
|
|
26
|
+
# @param jobs [Array<ActiveJob::Base>] jobs we want to dispatch
|
|
27
|
+
def dispatch_many(jobs)
|
|
28
|
+
# Group jobs by their desired dispatch method
|
|
29
|
+
# It can be configured per job class, so we need to make sure we divide them
|
|
30
|
+
dispatches = Hash.new { |hash, key| hash[key] = [] }
|
|
31
|
+
|
|
32
|
+
jobs.each do |job|
|
|
33
|
+
d_method = fetch_option(job, :dispatch_many_method, DEFAULTS)
|
|
34
|
+
|
|
35
|
+
dispatches[d_method] << {
|
|
36
|
+
topic: job.queue_name,
|
|
37
|
+
payload: ::ActiveSupport::JSON.encode(serialize_job(job))
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
dispatches.each do |type, messages|
|
|
42
|
+
::Karafka.producer.public_send(
|
|
43
|
+
type,
|
|
44
|
+
messages
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
24
49
|
private
|
|
25
50
|
|
|
26
51
|
# @param job [ActiveJob::Base] job
|
|
@@ -33,6 +58,12 @@ module Karafka
|
|
|
33
58
|
.karafka_options
|
|
34
59
|
.fetch(key, defaults.fetch(key))
|
|
35
60
|
end
|
|
61
|
+
|
|
62
|
+
# @param job [ActiveJob::Base] job
|
|
63
|
+
# @return [Hash] json representation of the job
|
|
64
|
+
def serialize_job(job)
|
|
65
|
+
job.serialize
|
|
66
|
+
end
|
|
36
67
|
end
|
|
37
68
|
end
|
|
38
69
|
end
|
|
@@ -10,12 +10,23 @@ module Karafka
|
|
|
10
10
|
configure do |config|
|
|
11
11
|
config.error_messages = YAML.safe_load(
|
|
12
12
|
File.read(
|
|
13
|
-
File.join(Karafka.gem_root, 'config', 'errors.yml')
|
|
13
|
+
File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
|
|
14
14
|
)
|
|
15
15
|
).fetch('en').fetch('validations').fetch('job_options')
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
optional(:dispatch_method)
|
|
18
|
+
optional(:dispatch_method) do |val|
|
|
19
|
+
%i[
|
|
20
|
+
produce_async
|
|
21
|
+
produce_sync
|
|
22
|
+
].include?(val)
|
|
23
|
+
end
|
|
24
|
+
optional(:dispatch_many_method) do |val|
|
|
25
|
+
%i[
|
|
26
|
+
produce_many_async
|
|
27
|
+
produce_many_sync
|
|
28
|
+
].include?(val)
|
|
29
|
+
end
|
|
19
30
|
end
|
|
20
31
|
end
|
|
21
32
|
end
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
# Simple admin actions that we can perform via Karafka on our Kafka cluster
|
|
5
|
+
#
|
|
6
|
+
# @note It always initializes a new admin instance as we want to ensure it is always closed
|
|
7
|
+
# Since admin actions are not performed that often, that should be ok.
|
|
8
|
+
#
|
|
9
|
+
# @note It always uses the primary defined cluster and does not support multi-cluster work.
|
|
10
|
+
# If you need this, just replace the cluster info for the time you use this
|
|
11
|
+
module Admin
|
|
12
|
+
# A fake admin topic representation that we use for messages fetched using this API
|
|
13
|
+
# We cannot use the topics directly because we may want to request data from topics that we
|
|
14
|
+
# do not have in the routing
|
|
15
|
+
Topic = Struct.new(:name, :deserializer)
|
|
16
|
+
|
|
17
|
+
# We wait only for this amount of time before raising error as we intercept this error and
|
|
18
|
+
# retry after checking that the operation was finished or failed using external factor.
|
|
19
|
+
MAX_WAIT_TIMEOUT = 1
|
|
20
|
+
|
|
21
|
+
# How many times should be try. 1 x 60 => 60 seconds wait in total
|
|
22
|
+
MAX_ATTEMPTS = 60
|
|
23
|
+
|
|
24
|
+
# Defaults for config
|
|
25
|
+
CONFIG_DEFAULTS = {
|
|
26
|
+
'group.id': 'karafka_admin',
|
|
27
|
+
# We want to know when there is no more data not to end up with an endless loop
|
|
28
|
+
'enable.partition.eof': true,
|
|
29
|
+
'statistics.interval.ms': 0,
|
|
30
|
+
# Fetch at most 5 MBs when using admin
|
|
31
|
+
'fetch.message.max.bytes': 5 * 1_048_576,
|
|
32
|
+
# Do not commit offset automatically, this prevents offset tracking for operations involving
|
|
33
|
+
# a consumer instance
|
|
34
|
+
'enable.auto.commit': false
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
private_constant :Topic, :CONFIG_DEFAULTS, :MAX_WAIT_TIMEOUT, :MAX_ATTEMPTS
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
# Allows us to read messages from the topic
|
|
41
|
+
#
|
|
42
|
+
# @param name [String, Symbol] topic name
|
|
43
|
+
# @param partition [Integer] partition
|
|
44
|
+
# @param count [Integer] how many messages we want to get at most
|
|
45
|
+
# @param start_offset [Integer] offset from which we should start. If -1 is provided
|
|
46
|
+
# (default) we will start from the latest offset
|
|
47
|
+
# @param settings [Hash] kafka extra settings (optional)
|
|
48
|
+
#
|
|
49
|
+
# @return [Array<Karafka::Messages::Message>] array with messages
|
|
50
|
+
def read_topic(name, partition, count, start_offset = -1, settings = {})
|
|
51
|
+
messages = []
|
|
52
|
+
tpl = Rdkafka::Consumer::TopicPartitionList.new
|
|
53
|
+
low_offset, high_offset = nil
|
|
54
|
+
|
|
55
|
+
with_consumer(settings) do |consumer|
|
|
56
|
+
low_offset, high_offset = consumer.query_watermark_offsets(name, partition)
|
|
57
|
+
|
|
58
|
+
# Select offset dynamically if -1 or less
|
|
59
|
+
start_offset = high_offset - count if start_offset.negative?
|
|
60
|
+
|
|
61
|
+
# Build the requested range - since first element is on the start offset we need to
|
|
62
|
+
# subtract one from requested count to end up with expected number of elements
|
|
63
|
+
requested_range = (start_offset..start_offset + (count - 1))
|
|
64
|
+
# Establish theoretical available range. Note, that this does not handle cases related to
|
|
65
|
+
# log retention or compaction
|
|
66
|
+
available_range = (low_offset..high_offset)
|
|
67
|
+
# Select only offset that we can select. This will remove all the potential offsets that
|
|
68
|
+
# are below the low watermark offset
|
|
69
|
+
possible_range = requested_range.select { |offset| available_range.include?(offset) }
|
|
70
|
+
|
|
71
|
+
start_offset = possible_range.first
|
|
72
|
+
count = possible_range.count
|
|
73
|
+
|
|
74
|
+
tpl.add_topic_and_partitions_with_offsets(name, partition => start_offset)
|
|
75
|
+
consumer.assign(tpl)
|
|
76
|
+
|
|
77
|
+
# We should poll as long as we don't have all the messages that we need or as long as
|
|
78
|
+
# we do not read all the messages from the topic
|
|
79
|
+
loop do
|
|
80
|
+
# If we've got as many messages as we've wanted stop
|
|
81
|
+
break if messages.size >= count
|
|
82
|
+
|
|
83
|
+
message = consumer.poll(200)
|
|
84
|
+
|
|
85
|
+
next unless message
|
|
86
|
+
|
|
87
|
+
# If the message we've got is beyond the requested range, stop
|
|
88
|
+
break unless possible_range.include?(message.offset)
|
|
89
|
+
|
|
90
|
+
messages << message
|
|
91
|
+
rescue Rdkafka::RdkafkaError => e
|
|
92
|
+
# End of partition
|
|
93
|
+
break if e.code == :partition_eof
|
|
94
|
+
|
|
95
|
+
raise e
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Use topic from routes if we can match it or create a dummy one
|
|
100
|
+
# Dummy one is used in case we cannot match the topic with routes. This can happen
|
|
101
|
+
# when admin API is used to read topics that are not part of the routing
|
|
102
|
+
topic = ::Karafka::Routing::Router.find_or_initialize_by_name(name)
|
|
103
|
+
|
|
104
|
+
messages.map! do |message|
|
|
105
|
+
Messages::Builders::Message.call(
|
|
106
|
+
message,
|
|
107
|
+
topic,
|
|
108
|
+
Time.now
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Creates Kafka topic with given settings
|
|
114
|
+
#
|
|
115
|
+
# @param name [String] topic name
|
|
116
|
+
# @param partitions [Integer] number of partitions we expect
|
|
117
|
+
# @param replication_factor [Integer] number of replicas
|
|
118
|
+
# @param topic_config [Hash] topic config details as described here:
|
|
119
|
+
# https://kafka.apache.org/documentation/#topicconfigs
|
|
120
|
+
def create_topic(name, partitions, replication_factor, topic_config = {})
|
|
121
|
+
with_admin do |admin|
|
|
122
|
+
handler = admin.create_topic(name, partitions, replication_factor, topic_config)
|
|
123
|
+
|
|
124
|
+
with_re_wait(
|
|
125
|
+
-> { handler.wait(max_wait_timeout: MAX_WAIT_TIMEOUT) },
|
|
126
|
+
-> { topics_names.include?(name) }
|
|
127
|
+
)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Deleted a given topic
|
|
132
|
+
#
|
|
133
|
+
# @param name [String] topic name
|
|
134
|
+
def delete_topic(name)
|
|
135
|
+
with_admin do |admin|
|
|
136
|
+
handler = admin.delete_topic(name)
|
|
137
|
+
|
|
138
|
+
with_re_wait(
|
|
139
|
+
-> { handler.wait(max_wait_timeout: MAX_WAIT_TIMEOUT) },
|
|
140
|
+
-> { !topics_names.include?(name) }
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Creates more partitions for a given topic
|
|
146
|
+
#
|
|
147
|
+
# @param name [String] topic name
|
|
148
|
+
# @param partitions [Integer] total number of partitions we expect to end up with
|
|
149
|
+
def create_partitions(name, partitions)
|
|
150
|
+
with_admin do |admin|
|
|
151
|
+
handler = admin.create_partitions(name, partitions)
|
|
152
|
+
|
|
153
|
+
with_re_wait(
|
|
154
|
+
-> { handler.wait(max_wait_timeout: MAX_WAIT_TIMEOUT) },
|
|
155
|
+
-> { topic(name).fetch(:partition_count) >= partitions }
|
|
156
|
+
)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Fetches the watermark offsets for a given topic partition
|
|
161
|
+
#
|
|
162
|
+
# @param name [String, Symbol] topic name
|
|
163
|
+
# @param partition [Integer] partition
|
|
164
|
+
# @return [Array<Integer, Integer>] low watermark offset and high watermark offset
|
|
165
|
+
def read_watermark_offsets(name, partition)
|
|
166
|
+
with_consumer do |consumer|
|
|
167
|
+
consumer.query_watermark_offsets(name, partition)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# @return [Rdkafka::Metadata] cluster metadata info
|
|
172
|
+
def cluster_info
|
|
173
|
+
with_admin do |admin|
|
|
174
|
+
Rdkafka::Metadata.new(admin.instance_variable_get('@native_kafka'))
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Creates consumer instance and yields it. After usage it closes the consumer instance
|
|
179
|
+
# This API can be used in other pieces of code and allows for low-level consumer usage
|
|
180
|
+
#
|
|
181
|
+
# @param settings [Hash] extra settings to customize consumer
|
|
182
|
+
def with_consumer(settings = {})
|
|
183
|
+
consumer = config(:consumer, settings).consumer
|
|
184
|
+
yield(consumer)
|
|
185
|
+
ensure
|
|
186
|
+
consumer&.close
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
private
|
|
190
|
+
|
|
191
|
+
# @return [Array<String>] topics names
|
|
192
|
+
def topics_names
|
|
193
|
+
cluster_info.topics.map { |topic| topic.fetch(:topic_name) }
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Finds details about given topic
|
|
197
|
+
# @param name [String] topic name
|
|
198
|
+
# @return [Hash] topic details
|
|
199
|
+
def topic(name)
|
|
200
|
+
cluster_info.topics.find { |topic| topic[:topic_name] == name }
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Creates admin instance and yields it. After usage it closes the admin instance
|
|
204
|
+
def with_admin
|
|
205
|
+
admin = config(:producer, {}).admin
|
|
206
|
+
yield(admin)
|
|
207
|
+
ensure
|
|
208
|
+
admin&.close
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# There are some cases where rdkafka admin operations finish successfully but without the
|
|
212
|
+
# callback being triggered to materialize the post-promise object. Until this is fixed we
|
|
213
|
+
# can figure out, that operation we wanted to do finished successfully by checking that the
|
|
214
|
+
# effect of the command (new topic, more partitions, etc) is handled. Exactly for that we
|
|
215
|
+
# use the breaker. It we get a timeout, we can check that what we wanted to achieve has
|
|
216
|
+
# happened via the breaker check, hence we do not need to wait any longer.
|
|
217
|
+
#
|
|
218
|
+
# @param handler [Proc] the wait handler operation
|
|
219
|
+
# @param breaker [Proc] extra condition upon timeout that indicates things were finished ok
|
|
220
|
+
def with_re_wait(handler, breaker)
|
|
221
|
+
attempt ||= 0
|
|
222
|
+
attempt += 1
|
|
223
|
+
|
|
224
|
+
handler.call
|
|
225
|
+
rescue Rdkafka::AbstractHandle::WaitTimeoutError
|
|
226
|
+
return if breaker.call
|
|
227
|
+
|
|
228
|
+
retry if attempt <= MAX_ATTEMPTS
|
|
229
|
+
|
|
230
|
+
raise
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# @param type [Symbol] type of config we want
|
|
234
|
+
# @param settings [Hash] extra settings for config (if needed)
|
|
235
|
+
# @return [::Rdkafka::Config] rdkafka config
|
|
236
|
+
def config(type, settings)
|
|
237
|
+
config_hash = Karafka::Setup::AttributesMap.public_send(
|
|
238
|
+
type,
|
|
239
|
+
Karafka::App.config.kafka.dup.merge(CONFIG_DEFAULTS).merge!(settings)
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
::Rdkafka::Config.new(config_hash)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|