karafka 2.3.1 → 2.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +20 -0
  5. data/Gemfile.lock +6 -6
  6. data/README.md +2 -2
  7. data/bin/integrations +2 -1
  8. data/bin/rspecs +6 -2
  9. data/config/locales/errors.yml +33 -8
  10. data/config/locales/pro_errors.yml +6 -0
  11. data/docker-compose.yml +1 -1
  12. data/lib/karafka/app.rb +14 -0
  13. data/lib/karafka/cli/base.rb +19 -0
  14. data/lib/karafka/cli/server.rb +62 -76
  15. data/lib/karafka/cli/swarm.rb +30 -0
  16. data/lib/karafka/connection/client.rb +7 -0
  17. data/lib/karafka/constraints.rb +3 -3
  18. data/lib/karafka/contracts/config.rb +41 -0
  19. data/lib/karafka/errors.rb +12 -0
  20. data/lib/karafka/helpers/config_importer.rb +30 -0
  21. data/lib/karafka/instrumentation/logger_listener.rb +31 -0
  22. data/lib/karafka/instrumentation/notifications.rb +9 -0
  23. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +2 -0
  24. data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +34 -4
  25. data/lib/karafka/instrumentation/vendors/kubernetes/base_listener.rb +72 -0
  26. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +11 -40
  27. data/lib/karafka/instrumentation/vendors/kubernetes/swarm_liveness_listener.rb +54 -0
  28. data/lib/karafka/pro/active_job/job_options_contract.rb +1 -1
  29. data/lib/karafka/pro/base_consumer.rb +16 -0
  30. data/lib/karafka/pro/connection/manager.rb +6 -1
  31. data/lib/karafka/pro/processing/coordinator.rb +13 -3
  32. data/lib/karafka/pro/processing/coordinators/errors_tracker.rb +74 -0
  33. data/lib/karafka/pro/processing/coordinators/filters_applier.rb +107 -0
  34. data/lib/karafka/pro/processing/coordinators/virtual_offset_manager.rb +180 -0
  35. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +5 -7
  36. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +5 -7
  37. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom.rb +8 -10
  38. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +8 -16
  39. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +5 -7
  40. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +5 -7
  41. data/lib/karafka/pro/processing/strategies/aj/dlq_mom.rb +8 -10
  42. data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +7 -9
  43. data/lib/karafka/pro/processing/strategies/dlq/default.rb +36 -10
  44. data/lib/karafka/pro/processing/strategies/dlq/ftr.rb +3 -7
  45. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj.rb +4 -8
  46. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +6 -9
  47. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +5 -15
  48. data/lib/karafka/pro/processing/strategies/dlq/lrj.rb +4 -8
  49. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +6 -9
  50. data/lib/karafka/pro/processing/strategies/dlq/mom.rb +10 -20
  51. data/lib/karafka/pro/processing/strategies/vp/default.rb +7 -0
  52. data/lib/karafka/pro/routing/features/dead_letter_queue/contracts/topic.rb +6 -0
  53. data/lib/karafka/pro/routing/features/dead_letter_queue/topic.rb +39 -0
  54. data/lib/karafka/pro/routing/features/swarm/config.rb +31 -0
  55. data/lib/karafka/pro/routing/features/swarm/contracts/topic.rb +67 -0
  56. data/lib/karafka/pro/routing/features/swarm/topic.rb +54 -0
  57. data/lib/karafka/pro/routing/features/swarm.rb +25 -0
  58. data/lib/karafka/pro/swarm/liveness_listener.rb +171 -0
  59. data/lib/karafka/process.rb +27 -1
  60. data/lib/karafka/routing/features/dead_letter_queue/config.rb +2 -0
  61. data/lib/karafka/routing/subscription_group.rb +44 -9
  62. data/lib/karafka/server.rb +11 -13
  63. data/lib/karafka/setup/config.rb +41 -2
  64. data/lib/karafka/status.rb +4 -2
  65. data/lib/karafka/swarm/liveness_listener.rb +55 -0
  66. data/lib/karafka/swarm/manager.rb +229 -0
  67. data/lib/karafka/swarm/node.rb +179 -0
  68. data/lib/karafka/swarm/pidfd.rb +147 -0
  69. data/lib/karafka/swarm/supervisor.rb +187 -0
  70. data/lib/karafka/swarm.rb +27 -0
  71. data/lib/karafka/version.rb +1 -1
  72. data/lib/karafka.rb +1 -1
  73. data.tar.gz.sig +0 -0
  74. metadata +21 -4
  75. metadata.gz.sig +0 -0
  76. data/lib/karafka/pro/processing/filters_applier.rb +0 -105
  77. data/lib/karafka/pro/processing/virtual_offset_manager.rb +0 -177
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d28565a68d29a1f8f14be1878bf3a1f41611028069586fb4ece1572b8eb72eb
4
- data.tar.gz: 8c7f6ea1ec67e07ed107c7347d594a26ba923cecc7282c346eb24863e0324412
3
+ metadata.gz: 707ae730a3d16f7daa69b4d7fc91f27420e408f0c441aee1b3072a45a03ab18b
4
+ data.tar.gz: a06597a2361af03cbca21f98a4efc339c0cf582bf21a5794a8b9db1179c8b6c9
5
5
  SHA512:
6
- metadata.gz: 73d43079b8e83babe2f722acdd11a67d6a5c361998ffada9b46b0f28b3ebc9c430d7b28cf56cb2aba808e25c234fffc7b2ee945e9e265160fb7cba6f821fcc70
7
- data.tar.gz: 465b1088763ddc3ea63e53ef92765ae5719d6d1a83522288976321d1a64d24616b06d6571ce11425fd8a0dbf36d8c595871b92abfc8de8bcecc444181afe5123
6
+ metadata.gz: dbe0771e0764a74a7e86870c208b5c845fbfe9f022e9692db7dc1efe2e95ba65cb79f2a771c249e161cd2e540c2e18e4c4af72419849481a97e01ec70112a0bb
7
+ data.tar.gz: 9c6e32a5cc8fa7a82f8f0b9d50510863767eb11bfb57a4c356eb836d497e31981b0e0a19c814dfc438f3520320dd2eb973bf9c362cccec994bb4fa6dd0db1e27
checksums.yaml.gz.sig CHANGED
Binary file
data/.rspec CHANGED
@@ -3,3 +3,5 @@
3
3
  --exclude-pattern "spec/integrations/**/*_spec.rb"
4
4
  # Do not run pro at the same time as default specs as it would cause state conflicts
5
5
  --tag ~type:pro
6
+ # Do not run fork because they need to run in isolation
7
+ --tag ~mode:fork
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Karafka framework changelog
2
2
 
3
+ ## 2.3.3 (2024-02-26)
4
+ - [Enhancement] Routing based topics allocation for swarm (Pro)
5
+ - [Enhancement] Publish the `-1` shutdown reason status for a non-responding node in swarm.
6
+ - [Enhancement] Allow for using the `distribution` mode for DataDog listener histogram reporting (Aerdayne).
7
+ - [Change] Change `internal.swarm.node_report_timeout` to 60 seconds from 30 seconds to compensate for long pollings.
8
+ - [Fix] Static membership routing evaluation happens too early in swarm.
9
+ - [Fix] Close producer in supervisor prior to forking and warmup to prevent invalid memory states.
10
+
11
+ ## 2.3.2 (2024-02-16)
12
+ - **[Feature]** Provide swarm capabilities to OSS and Pro.
13
+ - **[Feature]** Provide ability to use complex strategies in DLQ (Pro).
14
+ - [Enhancement] Support using `:partition` as the partition key for ActiveJob assignments.
15
+ - [Enhancement] Expand Logger listener with swarm notifications.
16
+ - [Enhancement] Introduce K8s swarm liveness listener.
17
+ - [Enhancement] Use `Process.warmup` in Ruby 3.3+ prior to forks (in swarm) and prior to app start.
18
+ - [Enhancement] Provide `app.before_warmup` event to allow hooking code loading tools prior to final warmup.
19
+ - [Enhancement] Provide `Consumer#errors_tracker` to be able to get errors that occurred while doing complex recovery.
20
+ - [Fix] Infinite consecutive error flow with VPs and without DLQ can cause endless offsets accumulation.
21
+ - [Fix] Quieting mode causes too early unsubscribe.
22
+
3
23
  ## 2.3.1 (2024-02-08)
4
24
  - [Refactor] Ensure that `Karafka::Helpers::Async#async_call` can run from multiple threads.
5
25
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- karafka (2.3.1)
4
+ karafka (2.3.3)
5
5
  karafka-core (>= 2.3.0, < 2.4.0)
6
6
  waterdrop (>= 2.6.12, < 3.0.0)
7
7
  zeitwerk (~> 2.3)
@@ -41,11 +41,11 @@ GEM
41
41
  concurrent-ruby (~> 1.0)
42
42
  karafka-core (2.3.0)
43
43
  karafka-rdkafka (>= 0.14.8, < 0.15.0)
44
- karafka-rdkafka (0.14.9)
44
+ karafka-rdkafka (0.14.10)
45
45
  ffi (~> 1.15)
46
46
  mini_portile2 (~> 2.6)
47
47
  rake (> 12)
48
- karafka-web (0.8.1)
48
+ karafka-web (0.8.2)
49
49
  erubi (~> 1.4)
50
50
  karafka (>= 2.3.0, < 2.4.0)
51
51
  karafka-core (>= 2.3.0, < 2.4.0)
@@ -56,7 +56,7 @@ GEM
56
56
  mutex_m (0.2.0)
57
57
  rack (3.0.9)
58
58
  rake (13.1.0)
59
- roda (3.76.0)
59
+ roda (3.77.0)
60
60
  rack
61
61
  rspec (3.13.0)
62
62
  rspec-core (~> 3.13.0)
@@ -81,10 +81,10 @@ GEM
81
81
  tilt (2.3.0)
82
82
  tzinfo (2.0.6)
83
83
  concurrent-ruby (~> 1.0)
84
- waterdrop (2.6.13)
84
+ waterdrop (2.6.14)
85
85
  karafka-core (>= 2.2.3, < 3.0.0)
86
86
  zeitwerk (~> 2.3)
87
- zeitwerk (2.6.12)
87
+ zeitwerk (2.6.13)
88
88
 
89
89
  PLATFORMS
90
90
  ruby
data/README.md CHANGED
@@ -9,13 +9,13 @@
9
9
  Karafka is a Ruby and Rails multi-threaded efficient Kafka processing framework that:
10
10
 
11
11
  - Has a built-in [Web UI](https://karafka.io/docs/Web-UI-Features/) providing a convenient way to monitor and manage Karafka-based applications.
12
- - 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)
12
+ - 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) and [processes](https://karafka.io/docs/Swarm-Multi-Process).
13
13
  - [Automatically integrates](https://karafka.io/docs/Integrating-with-Ruby-on-Rails-and-other-frameworks#integrating-with-ruby-on-rails) with Ruby on Rails
14
14
  - Has [ActiveJob backend](https://karafka.io/docs/Active-Job) support (including [ordered jobs](https://karafka.io/docs/Pro-Enhanced-Active-Job#ordered-jobs))
15
15
  - Has a seamless [Dead Letter Queue](https://karafka.io/docs/Dead-Letter-Queue/) functionality built-in
16
16
  - Supports in-development [code reloading](https://karafka.io/docs/Auto-reload-of-code-changes-in-development)
17
17
  - Is powered by [librdkafka](https://github.com/edenhill/librdkafka) (the Apache Kafka C/C++ client library)
18
- - Has an out-of the box [StatsD/DataDog monitoring](https://karafka.io/docs/Monitoring-and-logging) with a dashboard template.
18
+ - Has an out-of the box [AppSignal](https://karafka.io/docs/Monitoring-and-Logging/#appsignal-metrics-and-error-tracking) and [StatsD/DataDog](https://karafka.io/docs/Monitoring-and-Logging/#datadog-and-statsd-integration) monitoring with dashboard templates.
19
19
 
20
20
  ```ruby
21
21
  # Define what topics you want to consume with which consumers in karafka.rb
data/bin/integrations CHANGED
@@ -43,7 +43,8 @@ class Scenario
43
43
  'consumption/worker_critical_error_behaviour_spec.rb' => [0, 2].freeze,
44
44
  'shutdown/on_hanging_jobs_and_a_shutdown_spec.rb' => [2].freeze,
45
45
  'shutdown/on_hanging_on_shutdown_job_and_a_shutdown_spec.rb' => [2].freeze,
46
- 'shutdown/on_hanging_listener_and_shutdown_spec.rb' => [2].freeze
46
+ 'shutdown/on_hanging_listener_and_shutdown_spec.rb' => [2].freeze,
47
+ 'swarm/forceful_shutdown_of_hanging_spec.rb' => [2].freeze
47
48
  }.freeze
48
49
 
49
50
  private_constant :MAX_RUN_TIME, :EXIT_CODES
data/bin/rspecs CHANGED
@@ -2,5 +2,9 @@
2
2
 
3
3
  set -e
4
4
 
5
- SPECS_TYPE=regular bundle exec rspec --tag ~type:pro
6
- SPECS_TYPE=pro bundle exec rspec --tag type:pro
5
+ # Run only regular non-forking specs first
6
+ SPECS_TYPE=regular bundle exec rspec --tag ~type:pro --tag ~mode:fork
7
+ # Run forking specs, they need to run in isolation not to crash because of librdkafka
8
+ SPECS_TYPE=regular bundle exec rspec --tag mode:fork
9
+ # Run pro specs at the end
10
+ SPECS_TYPE=pro bundle exec rspec --tag type:pro --tag ~mode:fork
@@ -1,11 +1,12 @@
1
1
  en:
2
2
  validations:
3
3
  config:
4
- missing: needs to be present
5
- client_id_format: 'needs to be a string with a Kafka accepted format'
6
4
  license.entity_format: needs to be a string
7
5
  license.token_format: needs to be either false or a string
8
6
  license.expires_on_format: needs to be a valid date
7
+
8
+ missing: needs to be present
9
+ client_id_format: 'needs to be a string with a Kafka accepted format'
9
10
  concurrency_format: needs to be an integer bigger than 0
10
11
  consumer_mapper_format: needs to be present
11
12
  consumer_persistence_format: needs to be either true or false
@@ -14,7 +15,14 @@ en:
14
15
  pause_with_exponential_backoff_format: needs to be either true or false
15
16
  shutdown_timeout_format: needs to be an integer bigger than 0
16
17
  max_wait_time_format: needs to be an integer bigger than 0
18
+ max_wait_time_max_wait_time_vs_swarm_node_report_timeout: >
19
+ cannot be more than 80% of internal.swarm.node_report_timeout.
20
+ Decrease max_wait_time or increase node_report_timeout
17
21
  kafka_format: needs to be a filled hash
22
+ key_must_be_a_symbol: All keys under the kafka settings scope need to be symbols
23
+ max_timeout_vs_pause_max_timeout: pause_timeout must be less or equal to pause_max_timeout
24
+ shutdown_timeout_vs_max_wait_time: shutdown_timeout must be more than max_wait_time
25
+
18
26
  internal.processing.jobs_builder_format: cannot be nil
19
27
  internal.processing.jobs_queue_class_format: cannot be nil
20
28
  internal.processing.scheduler_class_format: cannot be nil
@@ -22,14 +30,20 @@ en:
22
30
  internal.processing.partitioner_class_format: cannot be nil
23
31
  internal.processing.strategy_selector_format: cannot be nil
24
32
  internal.processing.expansions_selector_format: cannot be nil
25
- internal.active_job.dispatcher: cannot be nil
26
- internal.active_job.job_options_contract: cannot be nil
27
- internal.active_job.consumer_class: cannot be nil
33
+
34
+ internal.active_job.dispatcher_format: cannot be nil
35
+ internal.active_job.job_options_contract_format: cannot be nil
36
+ internal.active_job.consumer_class_format: cannot be nil
37
+
28
38
  internal.status_format: needs to be present
29
39
  internal.process_format: needs to be present
30
40
  internal.tick_interval_format: needs to be an integer bigger or equal to 1000
41
+ internal.supervision_sleep_format: needs to be an integer bigger than 0
42
+ internal.forceful_exit_code_format: needs to be an integer bigger or equal to 0
43
+
31
44
  internal.routing.builder_format: needs to be present
32
45
  internal.routing.subscription_groups_builder_format: needs to be present
46
+
33
47
  internal.connection.manager_format: needs to be present
34
48
  internal.connection.conductor_format: needs to be present
35
49
  internal.connection.proxy.query_watermark_offsets.timeout_format: needs to be an integer bigger than 0
@@ -41,14 +55,25 @@ en:
41
55
  internal.connection.proxy.committed.timeout_format: needs to be an integer bigger than 0
42
56
  internal.connection.proxy.committed.max_attempts_format: needs to be an integer bigger than 0
43
57
  internal.connection.proxy.committed.wait_time_format: needs to be an integer bigger than 0
44
- key_must_be_a_symbol: All keys under the kafka settings scope need to be symbols
45
- max_timeout_vs_pause_max_timeout: pause_timeout must be less or equal to pause_max_timeout
46
- shutdown_timeout_vs_max_wait_time: shutdown_timeout must be more than max_wait_time
58
+
59
+ internal.swarm.manager_format: cannot be nil
60
+ internal.swarm.orphaned_exit_code_format: needs to be an integer bigger or equal to 0
61
+ internal.swarm.pidfd_open_syscall_format: needs to be an integer bigger or equal to 0
62
+ internal.swarm.pidfd_signal_syscall_format: needs to be an integer bigger or equal to 0
63
+ internal.swarm.supervision_interval_format: needs to be an integer bigger or equal to 1000
64
+ internal.swarm.liveness_interval_format: needs to be an integer bigger or equal to 1000
65
+ internal.swarm.liveness_listener_format: cannot be nil
66
+ internal.swarm.node_report_timeout_format: needs to be an integer bigger or equal to 1000
67
+ internal.swarm.node_restart_timeout_format: needs to be an integer bigger or equal to 1000
68
+
47
69
  admin.kafka_format: needs to be a hash
48
70
  admin.group_id_format: 'needs to be a string with a Kafka accepted format'
49
71
  admin.max_wait_time_format: 'needs to be an integer bigger than 0'
50
72
  admin.max_attempts_format: 'needs to be an integer bigger than 0'
51
73
 
74
+ swarm.nodes_format: 'needs to be an integer bigger than 0'
75
+ swarm.node_format: needs to be false or node instance
76
+
52
77
  server_cli_options:
53
78
  missing: needs to be present
54
79
  consumer_groups_inclusion: Unknown consumer group name
@@ -8,6 +8,8 @@ en:
8
8
  long_running_job.active_format: needs to be either true or false
9
9
 
10
10
  dead_letter_queue_with_virtual_partitions: when using Dead Letter Queue with Virtual Partitions, at least one retry is required.
11
+ dead_letter_queue.strategy_format: 'needs to respond to #call'
12
+ dead_letter_queue.strategy_missing: needs to be present
11
13
 
12
14
  throttling.active_format: needs to be either true or false
13
15
  throttling.limit_format: needs to be equal or more than 1
@@ -56,6 +58,10 @@ en:
56
58
  subscription_group_details.multiplexing_boot_format: 'needs to be an integer equal or more than 1'
57
59
  subscription_group_details.multiplexing_boot_not_dynamic: 'needs to be equal to max when not in dynamic mode'
58
60
 
61
+ swarm.active_format: needs to be true
62
+ swarm.nodes_format: needs to be a range or an array of nodes ids
63
+ swarm_nodes_with_non_existent_nodes: includes unreachable nodes ids
64
+
59
65
  consumer_group:
60
66
  patterns_format: must be an array with hashes
61
67
  patterns_missing: needs to be present
data/docker-compose.yml CHANGED
@@ -3,7 +3,7 @@ version: '2'
3
3
  services:
4
4
  kafka:
5
5
  container_name: kafka
6
- image: confluentinc/cp-kafka:7.5.3
6
+ image: confluentinc/cp-kafka:7.6.0
7
7
 
8
8
  ports:
9
9
  - 9092:9092
data/lib/karafka/app.rb CHANGED
@@ -6,6 +6,20 @@ module Karafka
6
6
  extend Setup::Dsl
7
7
 
8
8
  class << self
9
+ # Notifies the Ruby virtual machine that the boot sequence is finished, and that now is a
10
+ # good time to optimize the application. In case of older Ruby versions, runs compacting,
11
+ # which is part of the full warmup introduced in Ruby 3.3.
12
+ def warmup
13
+ # Per recommendation, this should not run in children nodes
14
+ return if Karafka::App.config.swarm.node
15
+
16
+ monitor.instrument('app.before_warmup', caller: self)
17
+
18
+ return GC.compact unless ::Process.respond_to?(:warmup)
19
+
20
+ ::Process.warmup
21
+ end
22
+
9
23
  # @return [Karafka::Routing::Builder] consumers builder instance alias
10
24
  def consumer_groups
11
25
  config
@@ -5,6 +5,8 @@ module Karafka
5
5
  # Base class for all the command that we want to define
6
6
  # This base class provides an interface to easier separate single independent commands
7
7
  class Base
8
+ include Helpers::Colorize
9
+
8
10
  # @return [Hash] given command cli options
9
11
  attr_reader :options
10
12
 
@@ -19,6 +21,23 @@ module Karafka
19
21
  raise NotImplementedError, 'Implement this in a subclass'
20
22
  end
21
23
 
24
+ private
25
+
26
+ # Prints marketing info
27
+ def print_marketing_info
28
+ Karafka.logger.info Info::BANNER
29
+
30
+ if Karafka.pro?
31
+ Karafka.logger.info(
32
+ green('Thank you for using Karafka Pro!')
33
+ )
34
+ else
35
+ Karafka.logger.info(
36
+ red('Upgrade to Karafka Pro for more features and support: https://karafka.io')
37
+ )
38
+ end
39
+ end
40
+
22
41
  class << self
23
42
  # Loads proper environment with what is needed to run the CLI
24
43
  def load
@@ -5,8 +5,6 @@ module Karafka
5
5
  class Cli
6
6
  # Server Karafka Cli action
7
7
  class Server < Base
8
- include Helpers::Colorize
9
-
10
8
  # Types of things we can include / exclude from the routing via the CLI options
11
9
  SUPPORTED_TYPES = ::Karafka::Routing::ActivityManager::SUPPORTED_TYPES
12
10
 
@@ -16,63 +14,68 @@ module Karafka
16
14
 
17
15
  aliases :s, :consumer
18
16
 
19
- option(
20
- :consumer_groups,
21
- 'Runs server only with specified consumer groups',
22
- Array,
23
- %w[
24
- -g
25
- --consumer_groups
26
- --include_consumer_groups
27
- ]
28
- )
29
-
30
- option(
31
- :subscription_groups,
32
- 'Runs server only with specified subscription groups',
33
- Array,
34
- %w[
35
- --subscription_groups
36
- --include_subscription_groups
37
- ]
38
- )
39
-
40
- option(
41
- :topics,
42
- 'Runs server only with specified topics',
43
- Array,
44
- %w[
45
- --topics
46
- --include_topics
47
- ]
48
- )
49
-
50
- option(
51
- :exclude_consumer_groups,
52
- 'Runs server without specified consumer groups',
53
- Array,
54
- %w[
55
- --exclude_consumer_groups
56
- ]
57
- )
58
-
59
- option(
60
- :exclude_subscription_groups,
61
- 'Runs server without specified subscription groups',
62
- Array,
63
- %w[
64
- --exclude_subscription_groups
65
- ]
66
- )
67
-
68
- option(
69
- :exclude_topics,
70
- 'Runs server without specified topics',
71
- Array,
72
- %w[
73
- --exclude_topics
74
- ]
75
- )
17
+ # Those options can also be used when in swarm mode, hence we re-use
18
+ OPTIONS_BUILDER = lambda do
19
+ option(
20
+ :consumer_groups,
21
+ 'Runs server only with specified consumer groups',
22
+ Array,
23
+ %w[
24
+ -g
25
+ --consumer_groups
26
+ --include_consumer_groups
27
+ ]
28
+ )
29
+
30
+ option(
31
+ :subscription_groups,
32
+ 'Runs server only with specified subscription groups',
33
+ Array,
34
+ %w[
35
+ --subscription_groups
36
+ --include_subscription_groups
37
+ ]
38
+ )
39
+
40
+ option(
41
+ :topics,
42
+ 'Runs server only with specified topics',
43
+ Array,
44
+ %w[
45
+ --topics
46
+ --include_topics
47
+ ]
48
+ )
49
+
50
+ option(
51
+ :exclude_consumer_groups,
52
+ 'Runs server without specified consumer groups',
53
+ Array,
54
+ %w[
55
+ --exclude_consumer_groups
56
+ ]
57
+ )
58
+
59
+ option(
60
+ :exclude_subscription_groups,
61
+ 'Runs server without specified subscription groups',
62
+ Array,
63
+ %w[
64
+ --exclude_subscription_groups
65
+ ]
66
+ )
67
+
68
+ option(
69
+ :exclude_topics,
70
+ 'Runs server without specified topics',
71
+ Array,
72
+ %w[
73
+ --exclude_topics
74
+ ]
75
+ )
76
+ end
77
+
78
+ instance_exec(&OPTIONS_BUILDER)
76
79
 
77
80
  # Start the Karafka server
78
81
  def call
@@ -85,8 +88,6 @@ module Karafka
85
88
  Karafka::Server.run
86
89
  end
87
90
 
88
- private
89
-
90
91
  # Registers things we want to include (if defined)
91
92
  def register_inclusions
92
93
  activities = ::Karafka::App.config.internal.routing.activity_manager
@@ -108,21 +109,6 @@ module Karafka
108
109
  names.each { |name| activities.exclude(type, name) }
109
110
  end
110
111
  end
111
-
112
- # Prints marketing info
113
- def print_marketing_info
114
- Karafka.logger.info Info::BANNER
115
-
116
- if Karafka.pro?
117
- Karafka.logger.info(
118
- green('Thank you for using Karafka Pro!')
119
- )
120
- else
121
- Karafka.logger.info(
122
- red('Upgrade to Karafka Pro for more features and support: https://karafka.io')
123
- )
124
- end
125
- end
126
112
  end
127
113
  end
128
114
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ # Karafka framework Cli
5
+ class Cli
6
+ # Starts swarm of consumers forked from the supervisor
7
+ class Swarm < Base
8
+ desc 'Starts swarm of Karafka consumers with a supervisor'
9
+
10
+ aliases :swarm
11
+
12
+ instance_exec(&Server::OPTIONS_BUILDER)
13
+
14
+ # Starts the swarm
15
+ def call
16
+ ::Karafka::Swarm.ensure_supported!
17
+
18
+ # Print our banner and info in the dev mode
19
+ print_marketing_info if Karafka::App.env.development?
20
+
21
+ # This will register inclusions and exclusions in the routing, so all forks will use it
22
+ server = Server.new
23
+ server.register_inclusions
24
+ server.register_exclusions
25
+
26
+ Karafka::Swarm::Supervisor.new.run
27
+ end
28
+ end
29
+ end
30
+ end
@@ -600,6 +600,13 @@ module Karafka
600
600
  # @return [Rdkafka::Consumer]
601
601
  def build_consumer
602
602
  ::Rdkafka::Config.logger = ::Karafka::App.config.logger
603
+
604
+ # We need to refresh the setup of this subscription group in case we started running in a
605
+ # swarm. The initial configuration for validation comes from the parent node, but it needs
606
+ # to be altered in case of a static group membership usage for correct mapping of the
607
+ # group instance id.
608
+ @subscription_group.refresh
609
+
603
610
  config = ::Rdkafka::Config.new(@subscription_group.kafka)
604
611
  config.consumer_rebalance_listener = @rebalance_callback
605
612
  # We want to manage the events queue independently from the messages queue. Thanks to that
@@ -15,13 +15,13 @@ module Karafka
15
15
  # Skip verification if web is not used at all
16
16
  return unless require_version('karafka/web')
17
17
 
18
- # All good if version higher than 0.7.x because we expect 0.8.0 or higher
19
- return if version(Karafka::Web::VERSION) >= version('0.7.100')
18
+ # All good if version higher than 0.8.1 because we expect 0.8.2 or higher
19
+ return if version(Karafka::Web::VERSION) >= version('0.8.2')
20
20
 
21
21
  # If older web-ui used, we cannot allow it
22
22
  raise(
23
23
  Errors::DependencyConstraintsError,
24
- 'karafka-web < 0.8.0 is not compatible with this karafka version'
24
+ 'karafka-web < 0.8.2 is not compatible with this karafka version'
25
25
  )
26
26
  end
27
27
 
@@ -34,6 +34,11 @@ module Karafka
34
34
  required(:max_wait_time) { |val| val.is_a?(Integer) && val.positive? }
35
35
  required(:kafka) { |val| val.is_a?(Hash) && !val.empty? }
36
36
 
37
+ nested(:swarm) do
38
+ required(:nodes) { |val| val.is_a?(Integer) && val.positive? }
39
+ required(:node) { |val| val == false || val.is_a?(Karafka::Swarm::Node) }
40
+ end
41
+
37
42
  nested(:admin) do
38
43
  # Can be empty because inherits values from the root kafka
39
44
  required(:kafka) { |val| val.is_a?(Hash) }
@@ -49,6 +54,20 @@ module Karafka
49
54
  # In theory this could be less than a second, however this would impact the maximum time
50
55
  # of a single consumer queue poll, hence we prevent it
51
56
  required(:tick_interval) { |val| val.is_a?(Integer) && val >= 1_000 }
57
+ required(:supervision_sleep) { |val| val.is_a?(Numeric) && val.positive? }
58
+ required(:forceful_exit_code) { |val| val.is_a?(Integer) && val >= 0 }
59
+
60
+ nested(:swarm) do
61
+ required(:manager) { |val| !val.nil? }
62
+ required(:orphaned_exit_code) { |val| val.is_a?(Integer) && val >= 0 }
63
+ required(:pidfd_open_syscall) { |val| val.is_a?(Integer) && val >= 0 }
64
+ required(:pidfd_signal_syscall) { |val| val.is_a?(Integer) && val >= 0 }
65
+ required(:supervision_interval) { |val| val.is_a?(Integer) && val >= 1_000 }
66
+ required(:liveness_interval) { |val| val.is_a?(Integer) && val >= 1_000 }
67
+ required(:liveness_listener) { |val| !val.nil? }
68
+ required(:node_report_timeout) { |val| val.is_a?(Integer) && val >= 1_000 }
69
+ required(:node_restart_timeout) { |val| val.is_a?(Integer) && val >= 1_000 }
70
+ end
52
71
 
53
72
  nested(:connection) do
54
73
  required(:manager) { |val| !val.nil? }
@@ -143,6 +162,28 @@ module Karafka
143
162
 
144
163
  [[%i[shutdown_timeout], :shutdown_timeout_vs_max_wait_time]]
145
164
  end
165
+
166
+ # `internal.swarm.node_report_timeout` should not be close to `max_wait_time` otherwise
167
+ # there may be a case where node cannot report often enough because it is clogged by waiting
168
+ # on more data.
169
+ #
170
+ # We handle that at a config level to make sure that this is correctly configured.
171
+ #
172
+ # We do not validate this in the context of swarm usage (validate only if...) because it is
173
+ # often that swarm only runs on prod and we do not want to crash it surprisingly.
174
+ virtual do |data, errors|
175
+ next unless errors.empty?
176
+
177
+ max_wait_time = data.fetch(:max_wait_time)
178
+ node_report_timeout = data.fetch(:internal)[:swarm][:node_report_timeout] || false
179
+
180
+ next unless node_report_timeout
181
+ # max wait time should be at least 20% smaller than the reporting time to have enough
182
+ # time for reporting
183
+ next if max_wait_time < node_report_timeout * 0.8
184
+
185
+ [[%i[max_wait_time], :max_wait_time_vs_swarm_node_report_timeout]]
186
+ end
146
187
  end
147
188
  end
148
189
  end
@@ -87,5 +87,17 @@ module Karafka
87
87
  # Because we do not want to require web out of the box and we do not want to lock web with
88
88
  # karafka 1:1, we do such a sanity check
89
89
  DependencyConstraintsError = Class.new(BaseError)
90
+
91
+ # Raised when we were not able to open pidfd for given pid
92
+ # This should not happen. If you see it, please report.
93
+ PidfdOpenFailedError = Class.new(BaseError)
94
+
95
+ # Failed to send signal to a process via pidfd
96
+ # This should not happen. If you see it, please report.
97
+ PidfdSignalFailedError = Class.new(BaseError)
98
+
99
+ # Raised when given option/feature is not supported on a given platform or when given option
100
+ # is not supported in a given configuration
101
+ UnsupportedOptionError = Class.new(BaseError)
90
102
  end
91
103
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Helpers
5
+ # Module allowing for configuration injections. By default injects whole app config
6
+ # Allows for granular config injection
7
+ class ConfigImporter < Module
8
+ # @param attributes [Hash<Symbol, Array<Symbol>>] map defining what we want to inject.
9
+ # The key is the name under which attribute will be visible and the value is the full
10
+ # path to the attribute
11
+ def initialize(attributes = { config: %i[itself] })
12
+ super()
13
+ @attributes = attributes
14
+ end
15
+
16
+ # @param model [Object] object to which we want to add the config fetcher
17
+ def included(model)
18
+ super
19
+
20
+ @attributes.each do |name, path|
21
+ model.class_eval <<~RUBY, __FILE__, __LINE__ + 1
22
+ def #{name}
23
+ @#{name} ||= ::Karafka::App.config.#{path.join('.')}
24
+ end
25
+ RUBY
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end