karafka-enterprise 0.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of karafka-enterprise might be problematic. Click here for more details.

Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.coditsu/ci.yml +3 -0
  5. data/.console_irbrc +11 -0
  6. data/.diffend.yml +3 -0
  7. data/.github/FUNDING.yml +3 -0
  8. data/.github/ISSUE_TEMPLATE/bug_report.md +50 -0
  9. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  10. data/.github/workflows/ci.yml +74 -0
  11. data/.gitignore +69 -0
  12. data/.rspec +1 -0
  13. data/.ruby-gemset +1 -0
  14. data/.ruby-version +1 -0
  15. data/CHANGELOG.md +573 -0
  16. data/CODE_OF_CONDUCT.md +46 -0
  17. data/CONTRIBUTING.md +41 -0
  18. data/Gemfile +14 -0
  19. data/Gemfile.lock +137 -0
  20. data/MIT-LICENCE +18 -0
  21. data/README.md +99 -0
  22. data/bin/karafka +19 -0
  23. data/certs/mensfeld.pem +25 -0
  24. data/config/errors.yml +41 -0
  25. data/docker-compose.yml +17 -0
  26. data/karafka.gemspec +43 -0
  27. data/lib/karafka.rb +72 -0
  28. data/lib/karafka/app.rb +53 -0
  29. data/lib/karafka/assignment_strategies/round_robin.rb +13 -0
  30. data/lib/karafka/attributes_map.rb +63 -0
  31. data/lib/karafka/backends/inline.rb +16 -0
  32. data/lib/karafka/base_consumer.rb +57 -0
  33. data/lib/karafka/base_responder.rb +226 -0
  34. data/lib/karafka/cli.rb +62 -0
  35. data/lib/karafka/cli/base.rb +78 -0
  36. data/lib/karafka/cli/console.rb +31 -0
  37. data/lib/karafka/cli/flow.rb +48 -0
  38. data/lib/karafka/cli/info.rb +31 -0
  39. data/lib/karafka/cli/install.rb +66 -0
  40. data/lib/karafka/cli/missingno.rb +19 -0
  41. data/lib/karafka/cli/server.rb +71 -0
  42. data/lib/karafka/code_reloader.rb +67 -0
  43. data/lib/karafka/connection/api_adapter.rb +161 -0
  44. data/lib/karafka/connection/batch_delegator.rb +55 -0
  45. data/lib/karafka/connection/builder.rb +18 -0
  46. data/lib/karafka/connection/client.rb +117 -0
  47. data/lib/karafka/connection/listener.rb +71 -0
  48. data/lib/karafka/connection/message_delegator.rb +36 -0
  49. data/lib/karafka/consumers/batch_metadata.rb +10 -0
  50. data/lib/karafka/consumers/callbacks.rb +71 -0
  51. data/lib/karafka/consumers/includer.rb +64 -0
  52. data/lib/karafka/consumers/responders.rb +24 -0
  53. data/lib/karafka/consumers/single_params.rb +15 -0
  54. data/lib/karafka/contracts.rb +10 -0
  55. data/lib/karafka/contracts/config.rb +21 -0
  56. data/lib/karafka/contracts/consumer_group.rb +211 -0
  57. data/lib/karafka/contracts/consumer_group_topic.rb +19 -0
  58. data/lib/karafka/contracts/responder_usage.rb +54 -0
  59. data/lib/karafka/contracts/server_cli_options.rb +31 -0
  60. data/lib/karafka/errors.rb +51 -0
  61. data/lib/karafka/fetcher.rb +42 -0
  62. data/lib/karafka/helpers/class_matcher.rb +88 -0
  63. data/lib/karafka/helpers/config_retriever.rb +46 -0
  64. data/lib/karafka/helpers/inflector.rb +26 -0
  65. data/lib/karafka/helpers/multi_delegator.rb +32 -0
  66. data/lib/karafka/instrumentation/logger.rb +58 -0
  67. data/lib/karafka/instrumentation/monitor.rb +70 -0
  68. data/lib/karafka/instrumentation/proctitle_listener.rb +36 -0
  69. data/lib/karafka/instrumentation/stdout_listener.rb +140 -0
  70. data/lib/karafka/params/batch_metadata.rb +26 -0
  71. data/lib/karafka/params/builders/batch_metadata.rb +30 -0
  72. data/lib/karafka/params/builders/params.rb +38 -0
  73. data/lib/karafka/params/builders/params_batch.rb +25 -0
  74. data/lib/karafka/params/metadata.rb +20 -0
  75. data/lib/karafka/params/params.rb +54 -0
  76. data/lib/karafka/params/params_batch.rb +60 -0
  77. data/lib/karafka/patches/ruby_kafka.rb +47 -0
  78. data/lib/karafka/persistence/client.rb +29 -0
  79. data/lib/karafka/persistence/consumers.rb +45 -0
  80. data/lib/karafka/persistence/topics.rb +48 -0
  81. data/lib/karafka/process.rb +60 -0
  82. data/lib/karafka/responders/builder.rb +36 -0
  83. data/lib/karafka/responders/topic.rb +55 -0
  84. data/lib/karafka/routing/builder.rb +90 -0
  85. data/lib/karafka/routing/consumer_group.rb +63 -0
  86. data/lib/karafka/routing/consumer_mapper.rb +34 -0
  87. data/lib/karafka/routing/proxy.rb +46 -0
  88. data/lib/karafka/routing/router.rb +29 -0
  89. data/lib/karafka/routing/topic.rb +62 -0
  90. data/lib/karafka/routing/topic_mapper.rb +53 -0
  91. data/lib/karafka/serialization/json/deserializer.rb +27 -0
  92. data/lib/karafka/serialization/json/serializer.rb +31 -0
  93. data/lib/karafka/server.rb +86 -0
  94. data/lib/karafka/setup/config.rb +226 -0
  95. data/lib/karafka/setup/configurators/water_drop.rb +36 -0
  96. data/lib/karafka/setup/dsl.rb +21 -0
  97. data/lib/karafka/status.rb +29 -0
  98. data/lib/karafka/templates/application_consumer.rb.erb +7 -0
  99. data/lib/karafka/templates/application_responder.rb.erb +11 -0
  100. data/lib/karafka/templates/karafka.rb.erb +92 -0
  101. data/lib/karafka/version.rb +7 -0
  102. data/log/.gitkeep +0 -0
  103. metadata +325 -0
  104. metadata.gz.sig +0 -0
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Persistence
5
+ # Persistence layer to store current thread messages consumer client for further use
6
+ class Client
7
+ # Thread.current key under which we store current thread messages consumer client
8
+ PERSISTENCE_SCOPE = :client
9
+
10
+ private_constant :PERSISTENCE_SCOPE
11
+
12
+ class << self
13
+ # @param client [Karafka::Connection::Client] messages consumer client of
14
+ # a current thread
15
+ # @return [Karafka::Connection::Client] persisted messages consumer client
16
+ def write(client)
17
+ Thread.current[PERSISTENCE_SCOPE] = client
18
+ end
19
+
20
+ # @return [Karafka::Connection::Client] persisted messages consumer client
21
+ # @raise [Karafka::Errors::MissingClientError] raised when no thread messages consumer
22
+ # client but we try to use it anyway
23
+ def read
24
+ Thread.current[PERSISTENCE_SCOPE] || raise(Errors::MissingClientError)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ # Module used to provide a persistent cache layer for Karafka components that need to be
5
+ # shared inside of a same thread
6
+ module Persistence
7
+ # Module used to provide a persistent cache across batch requests for a given
8
+ # topic and partition to store some additional details when the persistent mode
9
+ # for a given topic is turned on
10
+ class Consumers
11
+ # Thread.current scope under which we store consumers data
12
+ PERSISTENCE_SCOPE = :consumers
13
+
14
+ private_constant :PERSISTENCE_SCOPE
15
+
16
+ class << self
17
+ # @return [Hash] current thread's persistence scope hash with all the consumers
18
+ def current
19
+ Thread.current[PERSISTENCE_SCOPE] ||= Concurrent::Hash.new do |hash, key|
20
+ hash[key] = Concurrent::Hash.new
21
+ end
22
+ end
23
+
24
+ # Used to build (if block given) and/or fetch a current consumer instance that will be
25
+ # used to process messages from a given topic and partition
26
+ # @param topic [Karafka::Routing::Topic] topic instance for which we might cache
27
+ # @param partition [Integer] number of partition for which we want to cache
28
+ # @return [Karafka::BaseConsumer] base consumer descendant
29
+ def fetch(topic, partition)
30
+ current[topic][partition] ||= topic.consumer.new(topic)
31
+ end
32
+
33
+ # Removes all persisted instances of consumers from the consumer cache
34
+ # @note This is used to reload consumers instances when code reloading in development mode
35
+ # is present. This should not be used in production.
36
+ def clear
37
+ Thread
38
+ .list
39
+ .select { |thread| thread[PERSISTENCE_SCOPE] }
40
+ .each { |thread| thread[PERSISTENCE_SCOPE].clear }
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Persistence
5
+ # Local cache for routing topics
6
+ # We use it in order not to build string instances and remap incoming topic upon each
7
+ # message / message batches received
8
+ class Topics
9
+ # Thread.current scope under which we store topics data
10
+ PERSISTENCE_SCOPE = :topics
11
+
12
+ private_constant :PERSISTENCE_SCOPE
13
+
14
+ class << self
15
+ # @return [Concurrent::Hash] hash with all the topics from given groups
16
+ def current
17
+ Thread.current[PERSISTENCE_SCOPE] ||= Concurrent::Hash.new do |hash, key|
18
+ hash[key] = Concurrent::Hash.new
19
+ end
20
+ end
21
+
22
+ # @param group_id [String] group id for which we fetch a topic representation
23
+ # @param raw_topic_name [String] raw topic name (before remapping) for which we fetch a
24
+ # topic representation
25
+ # @return [Karafka::Routing::Topics] remapped topic representation that can be used further
26
+ # on when working with given parameters
27
+ def fetch(group_id, raw_topic_name)
28
+ current[group_id][raw_topic_name] ||= begin
29
+ # We map from incoming topic name, as it might be namespaced, etc.
30
+ # @see topic_mapper internal docs
31
+ mapped_topic_name = Karafka::App.config.topic_mapper.incoming(raw_topic_name)
32
+ Routing::Router.find("#{group_id}_#{mapped_topic_name}")
33
+ end
34
+ end
35
+
36
+ # Clears the whole topics cache for all the threads
37
+ # This is used for in-development code reloading as we need to get rid of all the
38
+ # preloaded and cached instances of objects to make it work
39
+ def clear
40
+ Thread
41
+ .list
42
+ .select { |thread| thread[PERSISTENCE_SCOPE] }
43
+ .each { |thread| thread[PERSISTENCE_SCOPE].clear }
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ # Class used to catch signals from ruby Signal class in order to manage Karafka stop
5
+ # @note There might be only one process - this class is a singleton
6
+ class Process
7
+ # Signal types that we handle
8
+ HANDLED_SIGNALS = %i[
9
+ SIGINT
10
+ SIGQUIT
11
+ SIGTERM
12
+ ].freeze
13
+
14
+ HANDLED_SIGNALS.each do |signal|
15
+ # Assigns a callback that will happen when certain signal will be send
16
+ # to Karafka server instance
17
+ # @note It does not define the callback itself -it needs to be passed in a block
18
+ # @example Define an action that should be taken on_sigint
19
+ # process.on_sigint do
20
+ # Karafka.logger.info('Log something here')
21
+ # exit
22
+ # end
23
+ define_method :"on_#{signal.to_s.downcase}" do |&block|
24
+ @callbacks[signal] << block
25
+ end
26
+ end
27
+
28
+ # Creates an instance of process and creates empty hash for callbacks
29
+ def initialize
30
+ @callbacks = Hash.new { |hsh, key| hsh[key] = [] }
31
+ end
32
+
33
+ # Method catches all HANDLED_SIGNALS and performs appropriate callbacks (if defined)
34
+ # @note If there are no callbacks, this method will just ignore a given signal that was sent
35
+ def supervise
36
+ HANDLED_SIGNALS.each { |signal| trap_signal(signal) }
37
+ end
38
+
39
+ private
40
+
41
+ # Traps a single signal and performs callbacks (if any) or just ignores this signal
42
+ # @param [Symbol] signal type that we want to catch
43
+ def trap_signal(signal)
44
+ trap(signal) do
45
+ notice_signal(signal)
46
+ (@callbacks[signal] || []).each(&:call)
47
+ end
48
+ end
49
+
50
+ # Informs monitoring about trapped signal
51
+ # @param [Symbol] signal type that we received
52
+ # @note We cannot perform logging from trap context, that's why
53
+ # we have to spin up a new thread to do this
54
+ def notice_signal(signal)
55
+ Thread.new do
56
+ Karafka.monitor.instrument('process.notice_signal', caller: self, signal: signal)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ # Responders namespace encapsulates all the internal responder implementation parts
5
+ module Responders
6
+ # Responders builder is used for finding (based on the consumer class name) a responder
7
+ # that match the consumer. We use it when user does not provide a responder inside routing,
8
+ # but he still names responder with the same convention (and namespaces) as consumer
9
+ #
10
+ # @example Matching responder exists
11
+ # Karafka::Responder::Builder(NewEventsConsumer).build #=> NewEventsResponder
12
+ # @example Matching responder does not exist
13
+ # Karafka::Responder::Builder(NewBuildsConsumer).build #=> nil
14
+ class Builder
15
+ # @param consumer_class [Karafka::BaseConsumer, nil] descendant of
16
+ # Karafka::BaseConsumer
17
+ # @example Tries to find a responder that matches a given consumer. If nothing found,
18
+ # will return nil (nil is accepted, because it means that a given consumer don't
19
+ # pipe stuff further on)
20
+ def initialize(consumer_class)
21
+ @consumer_class = consumer_class
22
+ end
23
+
24
+ # Tries to figure out a responder based on a consumer class name
25
+ # @return [Class] Responder class (not an instance)
26
+ # @return [nil] or nil if there's no matching responding class
27
+ def build
28
+ Helpers::ClassMatcher.new(
29
+ @consumer_class,
30
+ from: 'Consumer',
31
+ to: 'Responder'
32
+ ).match
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Responders
5
+ # Topic describes a single topic on which we want to respond with responding requirements
6
+ # @example Define topic (required by default)
7
+ # Karafka::Responders::Topic.new(:topic_name, {}) #=> #<Karafka::Responders::Topic...
8
+ # @example Define optional topic
9
+ # Karafka::Responders::Topic.new(:topic_name, required: false)
10
+ class Topic
11
+ # Name of the topic on which we want to respond
12
+ attr_reader :name
13
+
14
+ # @param name [Symbol, String] name of a topic on which we want to respond
15
+ # @param options [Hash] non-default options for this topic
16
+ # @return [Karafka::Responders::Topic] topic description object
17
+ def initialize(name, options)
18
+ @name = name.to_s
19
+ @options = options
20
+ end
21
+
22
+ # @return [Boolean] is this a required topic (if not, it is optional)
23
+ def required?
24
+ @options.key?(:required) ? @options[:required] : true
25
+ end
26
+
27
+ # @return [Boolean] was usage of this topic registered or not
28
+ def registered?
29
+ @options[:registered] == true
30
+ end
31
+
32
+ # @return [Class] Class to use to serialize messages for this topic
33
+ def serializer
34
+ @options[:serializer]
35
+ end
36
+
37
+ # @return [Boolean] do we want to use async producer. Defaults to false as the sync producer
38
+ # is safer and introduces less problems
39
+ def async?
40
+ @options.key?(:async) ? @options[:async] : false
41
+ end
42
+
43
+ # @return [Hash] hash with this topic attributes and options
44
+ def to_h
45
+ {
46
+ name: name,
47
+ required: required?,
48
+ registered: registered?,
49
+ serializer: serializer,
50
+ async: async?
51
+ }
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Routing
5
+ # Builder used as a DSL layer for building consumers and telling them which topics to consume
6
+ # @example Build a simple (most common) route
7
+ # consumers do
8
+ # topic :new_videos do
9
+ # consumer NewVideosConsumer
10
+ # end
11
+ # end
12
+ class Builder < Concurrent::Array
13
+ # Consumer group consistency checking contract
14
+ CONTRACT = Karafka::Contracts::ConsumerGroup.new.freeze
15
+
16
+ private_constant :CONTRACT
17
+
18
+ def initialize
19
+ super
20
+ @draws = Concurrent::Array.new
21
+ end
22
+
23
+ # Used to draw routes for Karafka
24
+ # @param block [Proc] block we will evaluate within the builder context
25
+ # @yield Evaluates provided block in a builder context so we can describe routes
26
+ # @raise [Karafka::Errors::InvalidConfigurationError] raised when configuration
27
+ # doesn't match with the config contract
28
+ # @note After it is done drawing it will store and validate all the routes to make sure that
29
+ # they are correct and that there are no topic/group duplications (this is forbidden)
30
+ # @example
31
+ # draw do
32
+ # topic :xyz do
33
+ # end
34
+ # end
35
+ def draw(&block)
36
+ @draws << block
37
+
38
+ instance_eval(&block)
39
+
40
+ each do |consumer_group|
41
+ hashed_group = consumer_group.to_h
42
+ validation_result = CONTRACT.call(hashed_group)
43
+ next if validation_result.success?
44
+
45
+ raise Errors::InvalidConfigurationError, validation_result.errors.to_h
46
+ end
47
+ end
48
+
49
+ # @return [Array<Karafka::Routing::ConsumerGroup>] only active consumer groups that
50
+ # we want to use. Since Karafka supports multi-process setup, we need to be able
51
+ # to pick only those consumer groups that should be active in our given process context
52
+ def active
53
+ select(&:active?)
54
+ end
55
+
56
+ # Clears the builder and the draws memory
57
+ def clear
58
+ @draws.clear
59
+ super
60
+ end
61
+
62
+ # Redraws all the routes for the in-process code reloading.
63
+ # @note This won't allow registration of new topics without process restart but will trigger
64
+ # cache invalidation so all the classes, etc are re-fetched after code reload
65
+ def reload
66
+ draws = @draws.dup
67
+ clear
68
+ draws.each { |block| draw(&block) }
69
+ end
70
+
71
+ private
72
+
73
+ # Builds and saves given consumer group
74
+ # @param group_id [String, Symbol] name for consumer group
75
+ # @param block [Proc] proc that should be executed in the proxy context
76
+ def consumer_group(group_id, &block)
77
+ consumer_group = ConsumerGroup.new(group_id.to_s)
78
+ self << Proxy.new(consumer_group, &block).target
79
+ end
80
+
81
+ # @param topic_name [String, Symbol] name of a topic from which we want to consumer
82
+ # @param block [Proc] proc we want to evaluate in the topic context
83
+ def topic(topic_name, &block)
84
+ consumer_group(topic_name) do
85
+ topic(topic_name, &block).tap(&:build)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Routing
5
+ # Object used to describe a single consumer group that is going to subscribe to
6
+ # given topics
7
+ # It is a part of Karafka's DSL
8
+ class ConsumerGroup
9
+ extend Helpers::ConfigRetriever
10
+
11
+ attr_reader(
12
+ :topics,
13
+ :id,
14
+ :name
15
+ )
16
+
17
+ # @param name [String, Symbol] raw name of this consumer group. Raw means, that it does not
18
+ # yet have an application client_id namespace, this will be added here by default.
19
+ # We add it to make a multi-system development easier for people that don't use
20
+ # kafka and don't understand the concept of consumer groups.
21
+ def initialize(name)
22
+ @name = name
23
+ @id = Karafka::App.config.consumer_mapper.call(name)
24
+ @topics = []
25
+ end
26
+
27
+ # @return [Boolean] true if this consumer group should be active in our current process
28
+ def active?
29
+ Karafka::Server.consumer_groups.include?(name)
30
+ end
31
+
32
+ # Builds a topic representation inside of a current consumer group route
33
+ # @param name [String, Symbol] name of topic to which we want to subscribe
34
+ # @param block [Proc] block that we want to evaluate in the topic context
35
+ # @return [Karafka::Routing::Topic] newly built topic instance
36
+ def topic=(name, &block)
37
+ topic = Topic.new(name, self)
38
+ @topics << Proxy.new(topic, &block).target.tap(&:build)
39
+ @topics.last
40
+ end
41
+
42
+ Karafka::AttributesMap.consumer_group.each do |attribute|
43
+ config_retriever_for(attribute)
44
+ end
45
+
46
+ # Hashed version of consumer group that can be used for validation purposes
47
+ # @return [Hash] hash with consumer group attributes including serialized to hash
48
+ # topics inside of it.
49
+ def to_h
50
+ result = {
51
+ topics: topics.map(&:to_h),
52
+ id: id
53
+ }
54
+
55
+ Karafka::AttributesMap.consumer_group.each do |attribute|
56
+ result[attribute] = public_send(attribute)
57
+ end
58
+
59
+ result
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Routing
5
+ # Default consumer mapper that builds consumer ids based on app id and consumer group name
6
+ # Different mapper can be used in case of preexisting consumer names or for applying
7
+ # other naming conventions not compatible with Karafka client_id + consumer name concept
8
+ #
9
+ # @example Mapper for using consumer groups without a client_id prefix
10
+ # class MyMapper
11
+ # def call(raw_consumer_group_name)
12
+ # raw_consumer_group_name
13
+ # end
14
+ # end
15
+ #
16
+ # @example Mapper for replacing "_" with "." in topic names
17
+ # class MyMapper
18
+ # def call(raw_consumer_group_name)
19
+ # [
20
+ # Karafka::Helpers::Inflector.map(Karafka::App.config.client_id.to_s),
21
+ # raw_consumer_group_name
22
+ # ].join('_').gsub('_', '.')
23
+ # end
24
+ # end
25
+ class ConsumerMapper
26
+ # @param raw_consumer_group_name [String, Symbol] string or symbolized consumer group name
27
+ # @return [String] remapped final consumer group name
28
+ def call(raw_consumer_group_name)
29
+ client_name = Karafka::Helpers::Inflector.map(Karafka::App.config.client_id.to_s)
30
+ "#{client_name}_#{raw_consumer_group_name}"
31
+ end
32
+ end
33
+ end
34
+ end