karafka 2.5.0.rc2 → 2.5.1.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/{ci.yml → ci_linux_ubuntu_x86_64_gnu.yml} +54 -30
  3. data/.github/workflows/ci_macos_arm64.yml +148 -0
  4. data/.github/workflows/push.yml +2 -2
  5. data/.github/workflows/trigger-wiki-refresh.yml +30 -0
  6. data/.github/workflows/verify-action-pins.yml +1 -1
  7. data/.ruby-version +1 -1
  8. data/CHANGELOG.md +29 -2
  9. data/Gemfile +2 -1
  10. data/Gemfile.lock +56 -27
  11. data/README.md +2 -2
  12. data/bin/integrations +3 -1
  13. data/bin/verify_kafka_warnings +2 -1
  14. data/config/locales/errors.yml +153 -152
  15. data/config/locales/pro_errors.yml +135 -134
  16. data/karafka.gemspec +3 -3
  17. data/lib/active_job/queue_adapters/karafka_adapter.rb +30 -1
  18. data/lib/karafka/active_job/dispatcher.rb +19 -9
  19. data/lib/karafka/admin/acl.rb +7 -8
  20. data/lib/karafka/admin/configs/config.rb +2 -2
  21. data/lib/karafka/admin/configs/resource.rb +2 -2
  22. data/lib/karafka/admin/configs.rb +3 -7
  23. data/lib/karafka/admin/consumer_groups.rb +351 -0
  24. data/lib/karafka/admin/topics.rb +206 -0
  25. data/lib/karafka/admin.rb +42 -451
  26. data/lib/karafka/base_consumer.rb +22 -0
  27. data/lib/karafka/{pro/contracts/server_cli_options.rb → cli/contracts/server.rb} +4 -12
  28. data/lib/karafka/cli/info.rb +1 -1
  29. data/lib/karafka/cli/install.rb +0 -2
  30. data/lib/karafka/connection/client.rb +8 -0
  31. data/lib/karafka/connection/listener.rb +5 -1
  32. data/lib/karafka/connection/status.rb +12 -9
  33. data/lib/karafka/errors.rb +0 -8
  34. data/lib/karafka/instrumentation/assignments_tracker.rb +16 -0
  35. data/lib/karafka/instrumentation/logger_listener.rb +109 -50
  36. data/lib/karafka/pro/active_job/dispatcher.rb +5 -0
  37. data/lib/karafka/pro/cleaner/messages/messages.rb +18 -8
  38. data/lib/karafka/pro/cli/contracts/server.rb +106 -0
  39. data/lib/karafka/pro/encryption/contracts/config.rb +1 -1
  40. data/lib/karafka/pro/loader.rb +1 -1
  41. data/lib/karafka/pro/recurring_tasks/contracts/config.rb +1 -1
  42. data/lib/karafka/pro/routing/features/adaptive_iterator/contracts/topic.rb +1 -1
  43. data/lib/karafka/pro/routing/features/adaptive_iterator/topic.rb +9 -0
  44. data/lib/karafka/pro/routing/features/dead_letter_queue/contracts/topic.rb +1 -1
  45. data/lib/karafka/pro/routing/features/dead_letter_queue/topic.rb +9 -0
  46. data/lib/karafka/pro/routing/features/delaying/contracts/topic.rb +1 -1
  47. data/lib/karafka/pro/routing/features/delaying/topic.rb +9 -0
  48. data/lib/karafka/pro/routing/features/direct_assignments/contracts/consumer_group.rb +1 -1
  49. data/lib/karafka/pro/routing/features/direct_assignments/contracts/topic.rb +1 -1
  50. data/lib/karafka/pro/routing/features/direct_assignments/topic.rb +9 -0
  51. data/lib/karafka/pro/routing/features/expiring/contracts/topic.rb +1 -1
  52. data/lib/karafka/pro/routing/features/expiring/topic.rb +9 -0
  53. data/lib/karafka/pro/routing/features/filtering/contracts/topic.rb +1 -1
  54. data/lib/karafka/pro/routing/features/filtering/topic.rb +9 -0
  55. data/lib/karafka/pro/routing/features/inline_insights/contracts/topic.rb +1 -1
  56. data/lib/karafka/pro/routing/features/inline_insights/topic.rb +9 -0
  57. data/lib/karafka/pro/routing/features/long_running_job/contracts/topic.rb +1 -1
  58. data/lib/karafka/pro/routing/features/long_running_job/topic.rb +9 -0
  59. data/lib/karafka/pro/routing/features/multiplexing/contracts/topic.rb +1 -1
  60. data/lib/karafka/pro/routing/features/multiplexing.rb +1 -1
  61. data/lib/karafka/pro/routing/features/offset_metadata/contracts/topic.rb +1 -1
  62. data/lib/karafka/pro/routing/features/offset_metadata/topic.rb +9 -0
  63. data/lib/karafka/pro/routing/features/parallel_segments/contracts/consumer_group.rb +1 -1
  64. data/lib/karafka/pro/routing/features/patterns/contracts/consumer_group.rb +1 -1
  65. data/lib/karafka/pro/routing/features/patterns/contracts/topic.rb +1 -1
  66. data/lib/karafka/pro/routing/features/patterns/topic.rb +9 -0
  67. data/lib/karafka/pro/routing/features/pausing/contracts/topic.rb +1 -1
  68. data/lib/karafka/pro/routing/features/periodic_job/contracts/topic.rb +1 -1
  69. data/lib/karafka/pro/routing/features/periodic_job/topic.rb +9 -0
  70. data/lib/karafka/pro/routing/features/recurring_tasks/contracts/topic.rb +1 -1
  71. data/lib/karafka/pro/routing/features/recurring_tasks/topic.rb +9 -0
  72. data/lib/karafka/pro/routing/features/scheduled_messages/contracts/topic.rb +1 -1
  73. data/lib/karafka/pro/routing/features/scheduled_messages/topic.rb +9 -0
  74. data/lib/karafka/pro/routing/features/swarm/contracts/topic.rb +1 -1
  75. data/lib/karafka/pro/routing/features/swarm/topic.rb +9 -0
  76. data/lib/karafka/pro/routing/features/throttling/contracts/topic.rb +1 -1
  77. data/lib/karafka/pro/routing/features/throttling/topic.rb +9 -0
  78. data/lib/karafka/pro/routing/features/virtual_partitions/contracts/topic.rb +1 -1
  79. data/lib/karafka/pro/routing/features/virtual_partitions/topic.rb +9 -0
  80. data/lib/karafka/pro/scheduled_messages/contracts/config.rb +1 -1
  81. data/lib/karafka/pro/scheduled_messages/daily_buffer.rb +9 -3
  82. data/lib/karafka/pro/swarm/liveness_listener.rb +17 -2
  83. data/lib/karafka/processing/executor.rb +1 -1
  84. data/lib/karafka/routing/builder.rb +0 -3
  85. data/lib/karafka/routing/consumer_group.rb +1 -4
  86. data/lib/karafka/routing/contracts/consumer_group.rb +84 -0
  87. data/lib/karafka/routing/contracts/routing.rb +61 -0
  88. data/lib/karafka/routing/contracts/topic.rb +83 -0
  89. data/lib/karafka/routing/features/active_job/contracts/topic.rb +1 -1
  90. data/lib/karafka/routing/features/active_job/topic.rb +9 -0
  91. data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +1 -1
  92. data/lib/karafka/routing/features/dead_letter_queue/topic.rb +9 -0
  93. data/lib/karafka/routing/features/declaratives/contracts/topic.rb +1 -1
  94. data/lib/karafka/routing/features/declaratives/topic.rb +9 -0
  95. data/lib/karafka/routing/features/deserializers/contracts/topic.rb +1 -1
  96. data/lib/karafka/routing/features/deserializers/topic.rb +9 -0
  97. data/lib/karafka/routing/features/eofed/contracts/topic.rb +1 -1
  98. data/lib/karafka/routing/features/eofed/topic.rb +9 -0
  99. data/lib/karafka/routing/features/inline_insights/contracts/topic.rb +1 -1
  100. data/lib/karafka/routing/features/inline_insights/topic.rb +9 -0
  101. data/lib/karafka/routing/features/manual_offset_management/contracts/topic.rb +1 -1
  102. data/lib/karafka/routing/features/manual_offset_management/topic.rb +9 -0
  103. data/lib/karafka/routing/subscription_group.rb +1 -10
  104. data/lib/karafka/routing/topic.rb +9 -1
  105. data/lib/karafka/server.rb +2 -7
  106. data/lib/karafka/setup/attributes_map.rb +36 -0
  107. data/lib/karafka/setup/config.rb +6 -7
  108. data/lib/karafka/setup/contracts/config.rb +217 -0
  109. data/lib/karafka/setup/defaults_injector.rb +3 -1
  110. data/lib/karafka/swarm/node.rb +66 -6
  111. data/lib/karafka/swarm.rb +2 -2
  112. data/lib/karafka/templates/karafka.rb.erb +2 -7
  113. data/lib/karafka/version.rb +1 -1
  114. data/lib/karafka.rb +17 -18
  115. metadata +18 -15
  116. data/lib/karafka/contracts/config.rb +0 -210
  117. data/lib/karafka/contracts/consumer_group.rb +0 -81
  118. data/lib/karafka/contracts/routing.rb +0 -59
  119. data/lib/karafka/contracts/server_cli_options.rb +0 -92
  120. data/lib/karafka/contracts/topic.rb +0 -81
  121. data/lib/karafka/swarm/pidfd.rb +0 -147
@@ -10,6 +10,15 @@ module Karafka
10
10
  class Expiring < Base
11
11
  # Topic expiring API extensions
12
12
  module Topic
13
+ # This method calls the parent class initializer and then sets up the
14
+ # extra instance variable to nil. The explicit initialization
15
+ # to nil is included as an optimization for Ruby's object shapes system,
16
+ # which improves memory layout and access performance.
17
+ def initialize(...)
18
+ super
19
+ @expiring = nil
20
+ end
21
+
13
22
  # @param ttl [Integer, nil] maximum time in ms a message is considered alive
14
23
  def expiring(ttl = nil)
15
24
  # Those settings are used for validation
@@ -17,7 +17,7 @@ module Karafka
17
17
  File.read(
18
18
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
19
19
  )
20
- ).fetch('en').fetch('validations').fetch('topic')
20
+ ).fetch('en').fetch('validations').fetch('routing').fetch('topic')
21
21
  end
22
22
 
23
23
  nested(:filtering) do
@@ -10,6 +10,15 @@ module Karafka
10
10
  class Filtering < Base
11
11
  # Filtering feature topic extensions
12
12
  module Topic
13
+ # This method calls the parent class initializer and then sets up the
14
+ # extra instance variable to nil. The explicit initialization
15
+ # to nil is included as an optimization for Ruby's object shapes system,
16
+ # which improves memory layout and access performance.
17
+ def initialize(...)
18
+ super
19
+ @filtering = nil
20
+ end
21
+
13
22
  # @param factory [#call, nil] Callable that can produce new filters instances per
14
23
  # assigned topic partition. nil as default so this feature is disabled
15
24
  def filter(factory = nil)
@@ -17,7 +17,7 @@ module Karafka
17
17
  File.read(
18
18
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
19
19
  )
20
- ).fetch('en').fetch('validations').fetch('topic')
20
+ ).fetch('en').fetch('validations').fetch('routing').fetch('topic')
21
21
  end
22
22
 
23
23
  nested :inline_insights do
@@ -10,6 +10,15 @@ module Karafka
10
10
  class InlineInsights < Base
11
11
  # Routing topic inline insights API
12
12
  module Topic
13
+ # This method calls the parent class initializer and then sets up the
14
+ # extra instance variable to nil. The explicit initialization
15
+ # to nil is included as an optimization for Ruby's object shapes system,
16
+ # which improves memory layout and access performance.
17
+ def initialize(...)
18
+ super
19
+ @inline_insights = nil
20
+ end
21
+
13
22
  # @param active [Boolean] should inline insights be activated
14
23
  # @param required [Boolean] are the insights required to operate
15
24
  def inline_insights(active = -1, required: -1)
@@ -17,7 +17,7 @@ module Karafka
17
17
  File.read(
18
18
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
19
19
  )
20
- ).fetch('en').fetch('validations').fetch('topic')
20
+ ).fetch('en').fetch('validations').fetch('routing').fetch('topic')
21
21
  end
22
22
 
23
23
  nested(:long_running_job) do
@@ -10,6 +10,15 @@ module Karafka
10
10
  class LongRunningJob < Base
11
11
  # Long-Running Jobs topic API extensions
12
12
  module Topic
13
+ # This method calls the parent class initializer and then sets up the
14
+ # extra instance variable to nil. The explicit initialization
15
+ # to nil is included as an optimization for Ruby's object shapes system,
16
+ # which improves memory layout and access performance.
17
+ def initialize(...)
18
+ super
19
+ @long_running_job = nil
20
+ end
21
+
13
22
  # @param active [Boolean] do we want to enable long-running job feature for this topic
14
23
  def long_running_job(active = false)
15
24
  @long_running_job ||= Config.new(active: active)
@@ -21,7 +21,7 @@ module Karafka
21
21
  File.read(
22
22
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
23
23
  )
24
- ).fetch('en').fetch('validations').fetch('topic')
24
+ ).fetch('en').fetch('validations').fetch('routing').fetch('topic')
25
25
  end
26
26
 
27
27
  nested(:subscription_group_details) do
@@ -17,7 +17,7 @@ module Karafka
17
17
  # @param _config [Karafka::Core::Configurable::Node] app config node
18
18
  def pre_setup(_config)
19
19
  # Make sure we use proper unique validator for topics definitions
20
- ::Karafka::Contracts::ConsumerGroup.singleton_class.prepend(
20
+ ::Karafka::Routing::Contracts::ConsumerGroup.singleton_class.prepend(
21
21
  Patches::Contracts::ConsumerGroup
22
22
  )
23
23
  end
@@ -17,7 +17,7 @@ module Karafka
17
17
  File.read(
18
18
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
19
19
  )
20
- ).fetch('en').fetch('validations').fetch('topic')
20
+ ).fetch('en').fetch('validations').fetch('routing').fetch('topic')
21
21
  end
22
22
 
23
23
  nested(:offset_metadata) do
@@ -25,6 +25,15 @@ module Karafka
25
25
 
26
26
  # Commit Metadata API extensions
27
27
  module Topic
28
+ # This method calls the parent class initializer and then sets up the
29
+ # extra instance variable to nil. The explicit initialization
30
+ # to nil is included as an optimization for Ruby's object shapes system,
31
+ # which improves memory layout and access performance.
32
+ def initialize(...)
33
+ super
34
+ @offset_metadata = nil
35
+ end
36
+
28
37
  # @param cache [Boolean] should we cache the response until rebalance
29
38
  # @param deserializer [#call] deserializer that will get raw data and should return
30
39
  # deserialized metadata
@@ -17,7 +17,7 @@ module Karafka
17
17
  File.read(
18
18
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
19
19
  )
20
- ).fetch('en').fetch('validations').fetch('consumer_group')
20
+ ).fetch('en').fetch('validations').fetch('routing').fetch('consumer_group')
21
21
 
22
22
  nested(:parallel_segments) do
23
23
  required(:active) { |val| [true, false].include?(val) }
@@ -16,7 +16,7 @@ module Karafka
16
16
  File.read(
17
17
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
18
18
  )
19
- ).fetch('en').fetch('validations').fetch('consumer_group')
19
+ ).fetch('en').fetch('validations').fetch('routing').fetch('consumer_group')
20
20
 
21
21
  required(:patterns) { |val| val.is_a?(Array) && val.all? { |el| el.is_a?(Hash) } }
22
22
 
@@ -17,7 +17,7 @@ module Karafka
17
17
  File.read(
18
18
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
19
19
  )
20
- ).fetch('en').fetch('validations').fetch('topic')
20
+ ).fetch('en').fetch('validations').fetch('routing').fetch('topic')
21
21
  end
22
22
 
23
23
  nested(:patterns) do
@@ -10,6 +10,15 @@ module Karafka
10
10
  class Patterns < Base
11
11
  # Patterns feature topic extensions
12
12
  module Topic
13
+ # This method calls the parent class initializer and then sets up the
14
+ # extra instance variable to nil. The explicit initialization
15
+ # to nil is included as an optimization for Ruby's object shapes system,
16
+ # which improves memory layout and access performance.
17
+ def initialize(...)
18
+ super
19
+ @patterns = nil
20
+ end
21
+
13
22
  # @return [String] subscription name or the regexp string representing matching of
14
23
  # new topics that should be detected.
15
24
  def subscription_name
@@ -17,7 +17,7 @@ module Karafka
17
17
  File.read(
18
18
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
19
19
  )
20
- ).fetch('en').fetch('validations').fetch('topic')
20
+ ).fetch('en').fetch('validations').fetch('routing').fetch('topic')
21
21
 
22
22
  required(:pause_timeout) { |val| val.is_a?(Integer) && val.positive? }
23
23
  required(:pause_max_timeout) { |val| val.is_a?(Integer) && val.positive? }
@@ -17,7 +17,7 @@ module Karafka
17
17
  File.read(
18
18
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
19
19
  )
20
- ).fetch('en').fetch('validations').fetch('topic')
20
+ ).fetch('en').fetch('validations').fetch('routing').fetch('topic')
21
21
  end
22
22
 
23
23
  nested(:periodic_job) do
@@ -10,6 +10,15 @@ module Karafka
10
10
  class PeriodicJob < Base
11
11
  # Periodic topic action flows extensions
12
12
  module Topic
13
+ # This method calls the parent class initializer and then sets up the
14
+ # extra instance variable to nil. The explicit initialization
15
+ # to nil is included as an optimization for Ruby's object shapes system,
16
+ # which improves memory layout and access performance.
17
+ def initialize(...)
18
+ super
19
+ @periodic_job = nil
20
+ end
21
+
13
22
  # Defines topic as periodic. Periodic topics consumers will invoke `#tick` with each
14
23
  # poll where messages were not received.
15
24
  # @param active [Boolean] should ticking happen for this topic assignments.
@@ -17,7 +17,7 @@ module Karafka
17
17
  File.read(
18
18
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
19
19
  )
20
- ).fetch('en').fetch('validations').fetch('topic')
20
+ ).fetch('en').fetch('validations').fetch('routing').fetch('topic')
21
21
  end
22
22
 
23
23
  nested(:recurring_tasks) do
@@ -11,6 +11,15 @@ module Karafka
11
11
  # Topic extensions to be able to check if given topic is a recurring tasks topic
12
12
  # Please note, that this applies to both the schedules topics and reports topics
13
13
  module Topic
14
+ # This method calls the parent class initializer and then sets up the
15
+ # extra instance variable to nil. The explicit initialization
16
+ # to nil is included as an optimization for Ruby's object shapes system,
17
+ # which improves memory layout and access performance.
18
+ def initialize(...)
19
+ super
20
+ @recurring_tasks = nil
21
+ end
22
+
14
23
  # @param active [Boolean] should this topic be considered related to recurring tasks
15
24
  def recurring_tasks(active = false)
16
25
  @recurring_tasks ||= Config.new(active: active)
@@ -17,7 +17,7 @@ module Karafka
17
17
  File.read(
18
18
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
19
19
  )
20
- ).fetch('en').fetch('validations').fetch('topic')
20
+ ).fetch('en').fetch('validations').fetch('routing').fetch('topic')
21
21
  end
22
22
 
23
23
  nested(:scheduled_messages) do
@@ -11,6 +11,15 @@ module Karafka
11
11
  # Topic extensions to be able to check if given topic is a scheduled messages topic
12
12
  # Please note, that this applies to both the schedules topic and logs topic
13
13
  module Topic
14
+ # This method calls the parent class initializer and then sets up the
15
+ # extra instance variable to nil. The explicit initialization
16
+ # to nil is included as an optimization for Ruby's object shapes system,
17
+ # which improves memory layout and access performance.
18
+ def initialize(...)
19
+ super
20
+ @scheduled_messages = nil
21
+ end
22
+
14
23
  # @param active [Boolean] should this topic be considered related to scheduled messages
15
24
  def scheduled_messages(active = false)
16
25
  @scheduled_messages ||= Config.new(active: active)
@@ -17,7 +17,7 @@ module Karafka
17
17
  File.read(
18
18
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
19
19
  )
20
- ).fetch('en').fetch('validations').fetch('topic')
20
+ ).fetch('en').fetch('validations').fetch('routing').fetch('topic')
21
21
  end
22
22
 
23
23
  nested(:swarm) do
@@ -10,6 +10,15 @@ module Karafka
10
10
  class Swarm < Base
11
11
  # Topic swarm API extensions
12
12
  module Topic
13
+ # This method calls the parent class initializer and then sets up the
14
+ # extra instance variable to nil. The explicit initialization
15
+ # to nil is included as an optimization for Ruby's object shapes system,
16
+ # which improves memory layout and access performance.
17
+ def initialize(...)
18
+ super
19
+ @swarm = nil
20
+ end
21
+
13
22
  # Allows defining swarm routing topic settings
14
23
  # @param nodes [Range, Array, Hash] range of nodes ids or array with nodes ids for
15
24
  # which we should run given topic or hash with nodes expected partition assignments
@@ -17,7 +17,7 @@ module Karafka
17
17
  File.read(
18
18
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
19
19
  )
20
- ).fetch('en').fetch('validations').fetch('topic')
20
+ ).fetch('en').fetch('validations').fetch('routing').fetch('topic')
21
21
  end
22
22
 
23
23
  nested(:throttling) do
@@ -10,6 +10,15 @@ module Karafka
10
10
  class Throttling < Base
11
11
  # Topic throttling API extensions
12
12
  module Topic
13
+ # This method calls the parent class initializer and then sets up the
14
+ # extra instance variable to nil. The explicit initialization
15
+ # to nil is included as an optimization for Ruby's object shapes system,
16
+ # which improves memory layout and access performance.
17
+ def initialize(...)
18
+ super
19
+ @throttling = nil
20
+ end
21
+
13
22
  # @param limit [Integer] max messages to process in an time interval
14
23
  # @param interval [Integer] time interval for processing
15
24
  def throttling(
@@ -17,7 +17,7 @@ module Karafka
17
17
  File.read(
18
18
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
19
19
  )
20
- ).fetch('en').fetch('validations').fetch('topic')
20
+ ).fetch('en').fetch('validations').fetch('routing').fetch('topic')
21
21
  end
22
22
 
23
23
  nested(:virtual_partitions) do
@@ -10,6 +10,15 @@ module Karafka
10
10
  class VirtualPartitions < Base
11
11
  # Topic extensions to be able to manage virtual partitions feature
12
12
  module Topic
13
+ # This method calls the parent class initializer and then sets up the
14
+ # extra instance variable to nil. The explicit initialization
15
+ # to nil is included as an optimization for Ruby's object shapes system,
16
+ # which improves memory layout and access performance.
17
+ def initialize(...)
18
+ super
19
+ @virtual_partitions = nil
20
+ end
21
+
13
22
  # @param max_partitions [Integer] max number of virtual partitions that can come out of
14
23
  # the single distribution flow. When set to more than the Karafka threading, will
15
24
  # create more work than workers. When less, can ensure we have spare resources to
@@ -15,7 +15,7 @@ module Karafka
15
15
  File.read(
16
16
  File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
17
17
  )
18
- ).fetch('en').fetch('validations').fetch('config')
18
+ ).fetch('en').fetch('validations').fetch('setup').fetch('config')
19
19
  end
20
20
 
21
21
  nested(:scheduled_messages) do
@@ -58,9 +58,15 @@ module Karafka
58
58
  selected << [epoch, message]
59
59
  end
60
60
 
61
- selected
62
- .sort_by!(&:first)
63
- .each { |_, message| yield(message) }
61
+ # When epoch is of the same value for multiple messages to be dispatched, we also sort
62
+ # on the offset to make sure that earlier messages are dispatched prior to newer
63
+ selected.sort! do |pck1, pck2|
64
+ cmp = pck1[0] <=> pck2[0]
65
+
66
+ cmp.zero? ? pck1[1].offset <=> pck2[1].offset : cmp
67
+ end
68
+
69
+ selected.each { |_, message| yield(message) }
64
70
  end
65
71
 
66
72
  # Removes given key from the accumulator
@@ -163,19 +163,34 @@ module Karafka
163
163
  end
164
164
 
165
165
  # @return [Integer] RSS in MB for the current process
166
- # @note Since swarm is linux only, we do not have to worry about getting RSS on other OSes
167
166
  def rss_mb
167
+ RUBY_PLATFORM.include?('linux') ? rss_mb_linux : rss_mb_macos
168
+ end
169
+
170
+ # @return [Integer] RSS in MB for the current process on Linux
171
+ def rss_mb_linux
168
172
  kb_rss = 0
169
173
 
170
174
  IO.readlines("/proc/#{node.pid}/status").each do |line|
171
175
  next unless line.start_with?('VmRSS:')
172
176
 
173
177
  kb_rss = line.split[1].to_i
174
-
175
178
  break
176
179
  end
177
180
 
178
181
  (kb_rss / 1_024.to_i).round
182
+ rescue Errno::ENOENT, Errno::EACCES
183
+ # /proc file doesn't exist or no permission to read it
184
+ 0
185
+ end
186
+
187
+ # @return [Integer] RSS in MB for the current process on macOS/BSD
188
+ def rss_mb_macos
189
+ output = `ps -o rss= -p #{node.pid}`.strip
190
+ output.empty? ? 0 : (output.to_i / 1_024.0).round
191
+ rescue Errno::ESRCH
192
+ # Process doesn't exist
193
+ 0
179
194
  end
180
195
  end
181
196
  end
@@ -168,7 +168,6 @@ module Karafka
168
168
  topic = @coordinator.topic
169
169
 
170
170
  strategy = strategy_selector.find(topic)
171
- expansions = expansions_selector.find(topic)
172
171
 
173
172
  consumer = topic.consumer_class.new
174
173
  # We use singleton class as the same consumer class may be used to process different
@@ -178,6 +177,7 @@ module Karafka
178
177
  # Specific features may expand consumer API beyond the injected strategy. The difference
179
178
  # here is that strategy impacts the flow of states while extra APIs just provide some
180
179
  # extra methods with informations, etc but do no deviate the flow behavior
180
+ expansions = expansions_selector.find(topic)
181
181
  expansions.each { |expansion| consumer.singleton_class.include(expansion) }
182
182
 
183
183
  consumer.client = @client
@@ -157,9 +157,6 @@ module Karafka
157
157
  end
158
158
  end
159
159
 
160
- # In case we use simple style of routing, all topics will be assigned to the same consumer
161
- # group that will be based on the client_id
162
- #
163
160
  # @param topic_name [String, Symbol] name of a topic from which we want to consumer
164
161
  # @param block [Proc] proc we want to evaluate in the topic context
165
162
  def topic(topic_name, &block)
@@ -22,10 +22,7 @@ module Karafka
22
22
  # groups
23
23
  attr_accessor :current_subscription_group_details
24
24
 
25
- # @param name [String, Symbol] raw name of this consumer group. Raw means, that it does not
26
- # yet have an application client_id namespace, this will be added here by default.
27
- # We add it to make a multi-system development easier for people that don't use
28
- # kafka and don't understand the concept of consumer groups.
25
+ # @param name [String, Symbol] name of this consumer group.
29
26
  def initialize(name)
30
27
  @name = name.to_s
31
28
  # This used to be different when consumer mappers existed but now it is the same
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Routing
5
+ # Namespace for routing related contracts
6
+ module Contracts
7
+ # Contract for single full route (consumer group + topics) validation.
8
+ class ConsumerGroup < Karafka::Contracts::Base
9
+ configure do |config|
10
+ config.error_messages = YAML.safe_load(
11
+ File.read(
12
+ File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
13
+ )
14
+ ).fetch('en').fetch('validations').fetch('routing').fetch('consumer_group')
15
+ end
16
+
17
+ required(:id) { |val| val.is_a?(String) && Karafka::Contracts::TOPIC_REGEXP.match?(val) }
18
+ required(:topics) { |val| val.is_a?(Array) && !val.empty? }
19
+
20
+ virtual do |data, errors|
21
+ next unless errors.empty?
22
+
23
+ names = data.fetch(:topics).map { |topic| topic_unique_key(topic) }
24
+
25
+ next if names.size == names.uniq.size
26
+
27
+ [[%i[topics], :names_not_unique]]
28
+ end
29
+
30
+ # Prevent same topics subscriptions in one CG with different consumer classes
31
+ # This should prevent users from accidentally creating multi-sg one CG setup with weird
32
+ # different consumer usage. If you need to consume same topic twice, use distinct CGs.
33
+ virtual do |data, errors|
34
+ next unless errors.empty?
35
+
36
+ topics_consumers = Hash.new { |h, k| h[k] = Set.new }
37
+
38
+ data.fetch(:topics).map do |topic|
39
+ topics_consumers[topic[:name]] << topic[:consumer]
40
+ end
41
+
42
+ next if topics_consumers.values.map(&:size).all? { |count| count == 1 }
43
+
44
+ [[%i[topics], :many_consumers_same_topic]]
45
+ end
46
+
47
+ virtual do |data, errors|
48
+ next unless errors.empty?
49
+ next unless ::Karafka::App.config.strict_topics_namespacing
50
+
51
+ names = data.fetch(:topics).map { |topic| topic[:name] }
52
+ names_hash = names.each_with_object({}) { |n, h| h[n] = true }
53
+ error_occured = false
54
+ names.each do |n|
55
+ # Skip topic names that are not namespaced
56
+ next unless n.chars.find { |c| ['.', '_'].include?(c) }
57
+
58
+ if n.chars.include?('.')
59
+ # Check underscore styled topic
60
+ underscored_topic = n.tr('.', '_')
61
+ error_occured = names_hash[underscored_topic] ? true : false
62
+ else
63
+ # Check dot styled topic
64
+ dot_topic = n.tr('_', '.')
65
+ error_occured = names_hash[dot_topic] ? true : false
66
+ end
67
+ end
68
+
69
+ next unless error_occured
70
+
71
+ [[%i[topics], :topics_namespaced_names_not_unique]]
72
+ end
73
+
74
+ class << self
75
+ # @param topic [Hash] topic config hash
76
+ # @return [String] topic unique key for validators
77
+ def topic_unique_key(topic)
78
+ topic[:name]
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Routing
5
+ module Contracts
6
+ # Ensures that routing wide rules are obeyed
7
+ class Routing < Karafka::Contracts::Base
8
+ configure do |config|
9
+ config.error_messages = YAML.safe_load(
10
+ File.read(
11
+ File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
12
+ )
13
+ ).fetch('en').fetch('validations').fetch('routing')
14
+ end
15
+
16
+ # Ensures, that when declarative topics strict requirement is on, all topics have
17
+ # declarative definition (including DLQ topics)
18
+ # @note It will ignore routing pattern topics because those topics are virtual
19
+ virtual do |data, errors|
20
+ next unless errors.empty?
21
+ # Do not validate declaratives unless required and explicitly enabled
22
+ next unless Karafka::App.config.strict_declarative_topics
23
+
24
+ # Collects declarative topics. Please note, that any topic that has a `#topic` reference,
25
+ # will be declarative by default unless explicitly disabled. This however does not apply
26
+ # to the DLQ definitions
27
+ dec_topics = Set.new
28
+ # All topics including the DLQ topics names that are marked as active
29
+ topics = Set.new
30
+
31
+ data.each do |consumer_group|
32
+ consumer_group[:topics].each do |topic|
33
+ pat = topic[:patterns]
34
+ # Ignore pattern topics because they won't exist and should not be declarative
35
+ # managed
36
+ topics << topic[:name] if !pat || !pat[:active]
37
+
38
+ dlq = topic[:dead_letter_queue]
39
+ topics << dlq[:topic] if dlq[:active]
40
+
41
+ dec = topic[:declaratives]
42
+
43
+ dec_topics << topic[:name] if dec[:active]
44
+ end
45
+ end
46
+
47
+ missing_dec = topics - dec_topics
48
+
49
+ next if missing_dec.empty?
50
+
51
+ missing_dec.map do |topic_name|
52
+ [
53
+ [:topics, topic_name],
54
+ :without_declarative_definition
55
+ ]
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end