karafka 1.4.12 → 2.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/FUNDING.yml +3 -0
  4. data/.github/workflows/ci.yml +74 -25
  5. data/CHANGELOG.md +38 -3
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +32 -34
  8. data/LICENSE +14 -0
  9. data/LICENSE-COMM +89 -0
  10. data/LICENSE-LGPL +165 -0
  11. data/README.md +16 -48
  12. data/bin/benchmarks +85 -0
  13. data/bin/create_token +28 -0
  14. data/bin/integrations +160 -0
  15. data/bin/stress +13 -0
  16. data/certs/karafka-pro.pem +11 -0
  17. data/config/errors.yml +4 -38
  18. data/docker-compose.yml +11 -3
  19. data/karafka.gemspec +13 -13
  20. data/lib/active_job/consumer.rb +22 -0
  21. data/lib/active_job/karafka.rb +18 -0
  22. data/lib/active_job/queue_adapters/karafka_adapter.rb +29 -0
  23. data/lib/active_job/routing_extensions.rb +15 -0
  24. data/lib/karafka/app.rb +13 -20
  25. data/lib/karafka/base_consumer.rb +103 -34
  26. data/lib/karafka/cli/base.rb +4 -4
  27. data/lib/karafka/cli/info.rb +43 -8
  28. data/lib/karafka/cli/install.rb +3 -8
  29. data/lib/karafka/cli/server.rb +17 -30
  30. data/lib/karafka/cli.rb +4 -11
  31. data/lib/karafka/connection/client.rb +279 -93
  32. data/lib/karafka/connection/listener.rb +137 -38
  33. data/lib/karafka/connection/messages_buffer.rb +57 -0
  34. data/lib/karafka/connection/pauses_manager.rb +46 -0
  35. data/lib/karafka/connection/rebalance_manager.rb +62 -0
  36. data/lib/karafka/contracts/config.rb +25 -7
  37. data/lib/karafka/contracts/consumer_group.rb +0 -173
  38. data/lib/karafka/contracts/consumer_group_topic.rb +17 -7
  39. data/lib/karafka/contracts/server_cli_options.rb +1 -9
  40. data/lib/karafka/contracts.rb +1 -1
  41. data/lib/karafka/env.rb +46 -0
  42. data/lib/karafka/errors.rb +14 -18
  43. data/lib/karafka/helpers/multi_delegator.rb +2 -2
  44. data/lib/karafka/instrumentation/callbacks/error.rb +40 -0
  45. data/lib/karafka/instrumentation/callbacks/statistics.rb +42 -0
  46. data/lib/karafka/instrumentation/monitor.rb +14 -21
  47. data/lib/karafka/instrumentation/stdout_listener.rb +64 -91
  48. data/lib/karafka/instrumentation.rb +21 -0
  49. data/lib/karafka/licenser.rb +65 -0
  50. data/lib/karafka/{params → messages}/batch_metadata.rb +7 -13
  51. data/lib/karafka/messages/builders/batch_metadata.rb +30 -0
  52. data/lib/karafka/messages/builders/message.rb +38 -0
  53. data/lib/karafka/messages/builders/messages.rb +40 -0
  54. data/lib/karafka/{params/params.rb → messages/message.rb} +7 -12
  55. data/lib/karafka/messages/messages.rb +64 -0
  56. data/lib/karafka/{params → messages}/metadata.rb +4 -6
  57. data/lib/karafka/messages/seek.rb +9 -0
  58. data/lib/karafka/patches/rdkafka/consumer.rb +22 -0
  59. data/lib/karafka/processing/executor.rb +96 -0
  60. data/lib/karafka/processing/executors_buffer.rb +49 -0
  61. data/lib/karafka/processing/jobs/base.rb +18 -0
  62. data/lib/karafka/processing/jobs/consume.rb +28 -0
  63. data/lib/karafka/processing/jobs/revoked.rb +22 -0
  64. data/lib/karafka/processing/jobs/shutdown.rb +23 -0
  65. data/lib/karafka/processing/jobs_queue.rb +121 -0
  66. data/lib/karafka/processing/worker.rb +57 -0
  67. data/lib/karafka/processing/workers_batch.rb +22 -0
  68. data/lib/karafka/railtie.rb +65 -0
  69. data/lib/karafka/routing/builder.rb +15 -14
  70. data/lib/karafka/routing/consumer_group.rb +10 -18
  71. data/lib/karafka/routing/consumer_mapper.rb +1 -2
  72. data/lib/karafka/routing/router.rb +1 -1
  73. data/lib/karafka/routing/subscription_group.rb +53 -0
  74. data/lib/karafka/routing/subscription_groups_builder.rb +51 -0
  75. data/lib/karafka/routing/topic.rb +47 -25
  76. data/lib/karafka/runner.rb +59 -0
  77. data/lib/karafka/serialization/json/deserializer.rb +6 -15
  78. data/lib/karafka/server.rb +62 -25
  79. data/lib/karafka/setup/config.rb +86 -159
  80. data/lib/karafka/status.rb +13 -3
  81. data/lib/karafka/templates/example_consumer.rb.erb +16 -0
  82. data/lib/karafka/templates/karafka.rb.erb +14 -50
  83. data/lib/karafka/time_trackers/base.rb +19 -0
  84. data/lib/karafka/time_trackers/pause.rb +84 -0
  85. data/lib/karafka/time_trackers/poll.rb +65 -0
  86. data/lib/karafka/version.rb +1 -1
  87. data/lib/karafka.rb +30 -13
  88. data.tar.gz.sig +0 -0
  89. metadata +70 -87
  90. metadata.gz.sig +0 -0
  91. data/MIT-LICENCE +0 -18
  92. data/lib/karafka/assignment_strategies/round_robin.rb +0 -13
  93. data/lib/karafka/attributes_map.rb +0 -63
  94. data/lib/karafka/backends/inline.rb +0 -16
  95. data/lib/karafka/base_responder.rb +0 -226
  96. data/lib/karafka/cli/flow.rb +0 -48
  97. data/lib/karafka/cli/missingno.rb +0 -19
  98. data/lib/karafka/code_reloader.rb +0 -67
  99. data/lib/karafka/connection/api_adapter.rb +0 -158
  100. data/lib/karafka/connection/batch_delegator.rb +0 -55
  101. data/lib/karafka/connection/builder.rb +0 -23
  102. data/lib/karafka/connection/message_delegator.rb +0 -36
  103. data/lib/karafka/consumers/batch_metadata.rb +0 -10
  104. data/lib/karafka/consumers/callbacks.rb +0 -71
  105. data/lib/karafka/consumers/includer.rb +0 -64
  106. data/lib/karafka/consumers/responders.rb +0 -24
  107. data/lib/karafka/consumers/single_params.rb +0 -15
  108. data/lib/karafka/contracts/responder_usage.rb +0 -54
  109. data/lib/karafka/fetcher.rb +0 -42
  110. data/lib/karafka/helpers/class_matcher.rb +0 -88
  111. data/lib/karafka/helpers/config_retriever.rb +0 -46
  112. data/lib/karafka/helpers/inflector.rb +0 -26
  113. data/lib/karafka/params/builders/batch_metadata.rb +0 -30
  114. data/lib/karafka/params/builders/params.rb +0 -38
  115. data/lib/karafka/params/builders/params_batch.rb +0 -25
  116. data/lib/karafka/params/params_batch.rb +0 -60
  117. data/lib/karafka/patches/ruby_kafka.rb +0 -47
  118. data/lib/karafka/persistence/client.rb +0 -29
  119. data/lib/karafka/persistence/consumers.rb +0 -45
  120. data/lib/karafka/persistence/topics.rb +0 -48
  121. data/lib/karafka/responders/builder.rb +0 -36
  122. data/lib/karafka/responders/topic.rb +0 -55
  123. data/lib/karafka/routing/topic_mapper.rb +0 -53
  124. data/lib/karafka/serialization/json/serializer.rb +0 -31
  125. data/lib/karafka/setup/configurators/water_drop.rb +0 -36
  126. data/lib/karafka/templates/application_responder.rb.erb +0 -11
@@ -4,48 +4,80 @@
4
4
  module Karafka
5
5
  # Base consumer from which all Karafka consumers should inherit
6
6
  class BaseConsumer
7
- extend Forwardable
8
-
9
- # Allows us to mark messages as consumed for non-automatic mode without having
10
- # to use consumer client directly. We do this that way, because most of the people should not
11
- # mess with the client instance directly (just in case)
12
- %i[
13
- mark_as_consumed
14
- mark_as_consumed!
15
- trigger_heartbeat
16
- trigger_heartbeat!
17
- ].each do |delegated_method_name|
18
- def_delegator :client, delegated_method_name
19
-
20
- private delegated_method_name
21
- end
22
-
23
7
  # @return [Karafka::Routing::Topic] topic to which a given consumer is subscribed
24
- attr_reader :topic
25
- # @return [Karafka::Params:ParamsBatch] current params batch
26
- attr_accessor :params_batch
27
-
28
- # Assigns a topic to a consumer and builds up proper consumer functionalities
29
- # so that it can cooperate with the topic settings
30
- # @param topic [Karafka::Routing::Topic]
31
- def initialize(topic)
32
- @topic = topic
33
- Consumers::Includer.call(self)
34
- end
8
+ attr_accessor :topic
9
+ # @return [Karafka::Messages::Messages] current messages batch
10
+ attr_accessor :messages
11
+ # @return [Karafka::Connection::Client] kafka connection client
12
+ attr_accessor :client
13
+ # @return [Karafka::TimeTrackers::Pause] current topic partition pause
14
+ attr_accessor :pause
15
+ # @return [Waterdrop::Producer] producer instance
16
+ attr_accessor :producer
35
17
 
36
18
  # Executes the default consumer flow.
37
- def call
38
- process
19
+ #
20
+ # @note We keep the seek offset tracking, and use it to compensate for async offset flushing
21
+ # that may not yet kick in when error occurs. That way we pause always on the last processed
22
+ # message.
23
+ def on_consume
24
+ Karafka.monitor.instrument('consumer.consume', caller: self) do
25
+ consume
26
+ end
27
+
28
+ pause.reset
29
+
30
+ # Mark as consumed only if manual offset management is not on
31
+ return if topic.manual_offset_management
32
+
33
+ # We use the non-blocking one here. If someone needs the blocking one, can implement it with
34
+ # manual offset management
35
+ mark_as_consumed(messages.last)
36
+ rescue StandardError => e
37
+ Karafka.monitor.instrument(
38
+ 'error.occurred',
39
+ error: e,
40
+ caller: self,
41
+ type: 'consumer.consume.error'
42
+ )
43
+ client.pause(topic.name, messages.first.partition, @seek_offset || messages.first.offset)
44
+ pause.pause
39
45
  end
40
46
 
41
- private
47
+ # Trigger method for running on shutdown.
48
+ #
49
+ # @private
50
+ def on_revoked
51
+ Karafka.monitor.instrument('consumer.revoked', caller: self) do
52
+ revoked
53
+ end
54
+ rescue StandardError => e
55
+ Karafka.monitor.instrument(
56
+ 'error.occurred',
57
+ error: e,
58
+ caller: self,
59
+ type: 'consumer.revoked.error'
60
+ )
61
+ end
42
62
 
43
- # @return [Karafka::Connection::Client] messages consuming client that can be used to
44
- # commit manually offset or pause / stop consumer based on the business logic
45
- def client
46
- Persistence::Client.read
63
+ # Trigger method for running on shutdown.
64
+ #
65
+ # @private
66
+ def on_shutdown
67
+ Karafka.monitor.instrument('consumer.shutdown', caller: self) do
68
+ shutdown
69
+ end
70
+ rescue StandardError => e
71
+ Karafka.monitor.instrument(
72
+ 'error.occurred',
73
+ error: e,
74
+ caller: self,
75
+ type: 'consumer.shutdown.error'
76
+ )
47
77
  end
48
78
 
79
+ private
80
+
49
81
  # Method that will perform business logic and on data received from Kafka (it will consume
50
82
  # the data)
51
83
  # @note This method needs bo be implemented in a subclass. We stub it here as a failover if
@@ -53,5 +85,42 @@ module Karafka
53
85
  def consume
54
86
  raise NotImplementedError, 'Implement this in a subclass'
55
87
  end
88
+
89
+ # Method that will be executed when a given topic partition is revoked. You can use it for
90
+ # some teardown procedures (closing file handler, etc).
91
+ def revoked; end
92
+
93
+ # Method that will be executed when the process is shutting down. You can use it for
94
+ # some teardown procedures (closing file handler, etc).
95
+ def shutdown; end
96
+
97
+ # Marks message as consumed in an async way.
98
+ #
99
+ # @param message [Messages::Message] last successfully processed message.
100
+ def mark_as_consumed(message)
101
+ client.mark_as_consumed(message)
102
+ @seek_offset = message.offset + 1
103
+ end
104
+
105
+ # Marks message as consumed in a sync way.
106
+ #
107
+ # @param message [Messages::Message] last successfully processed message.
108
+ def mark_as_consumed!(message)
109
+ client.mark_as_consumed!(message)
110
+ @seek_offset = message.offset + 1
111
+ end
112
+
113
+ # Seeks in the context of current topic and partition
114
+ #
115
+ # @param offset [Integer] offset where we want to seek
116
+ def seek(offset)
117
+ client.seek(
118
+ Karafka::Messages::Seek.new(
119
+ messages.metadata.topic,
120
+ messages.metadata.partition,
121
+ offset
122
+ )
123
+ )
124
+ end
56
125
  end
57
126
  end
@@ -43,16 +43,16 @@ module Karafka
43
43
  end
44
44
 
45
45
  # Allows to set description of a given cli command
46
- # @param args [Array] All the arguments that Thor desc method accepts
47
- def desc(*args)
48
- @desc ||= args
46
+ # @param desc [String] Description of a given cli command
47
+ def desc(desc)
48
+ @desc ||= desc
49
49
  end
50
50
 
51
51
  # This method will bind a given Cli command into Karafka Cli
52
52
  # This method is a wrapper to way Thor defines its commands
53
53
  # @param cli_class [Karafka::Cli] Karafka cli_class
54
54
  def bind_to(cli_class)
55
- cli_class.desc name, *@desc
55
+ cli_class.desc name, @desc
56
56
 
57
57
  (@options || []).each { |option| cli_class.option(*option) }
58
58
 
@@ -7,24 +7,59 @@ module Karafka
7
7
  class Info < Base
8
8
  desc 'Print configuration details and other options of your application'
9
9
 
10
+ # Nice karafka banner
11
+ BANNER = <<~BANNER
12
+
13
+ @@@ @@@@@ @@@
14
+ @@@ @@@ @@@
15
+ @@@ @@@ @@@@@@@@@ @@@ @@@ @@@@@@@@@ @@@@@@@@ @@@ @@@@ @@@@@@@@@
16
+ @@@@@@ @@@ @@@ @@@@@ @@@ @@@ @@@ @@@@@@@ @@@ @@@
17
+ @@@@@@@ @@@ @@@ @@@ @@@@ @@@ @@@ @@@@@@@ @@@ @@@
18
+ @@@ @@@@ @@@@@@@@@@ @@@ @@@@@@@@@@ @@@ @@@ @@@@ @@@@@@@@@@
19
+
20
+ BANNER
21
+
10
22
  # Print configuration details and other options of your application
11
23
  def call
24
+ Karafka.logger.info(BANNER)
25
+ Karafka.logger.info((core_info + license_info).join("\n"))
26
+ end
27
+
28
+ private
29
+
30
+ # @return [Array<String>] core framework related info
31
+ def core_info
12
32
  config = Karafka::App.config
13
33
 
34
+ postfix = config.license.token ? ' + Pro' : ''
35
+
14
36
  info = [
15
- "Karafka version: #{Karafka::VERSION}",
37
+ "Karafka version: #{Karafka::VERSION}#{postfix}",
16
38
  "Ruby version: #{RUBY_VERSION}",
17
- "Ruby-kafka version: #{::Kafka::VERSION}",
39
+ "Rdkafka version: #{::Rdkafka::VERSION}",
40
+ "Subscription groups count: #{Karafka::App.subscription_groups.size}",
41
+ "Workers count: #{Karafka::App.config.concurrency}",
18
42
  "Application client id: #{config.client_id}",
19
- "Backend: #{config.backend}",
20
- "Batch fetching: #{config.batch_fetching}",
21
- "Batch consuming: #{config.batch_consuming}",
22
43
  "Boot file: #{Karafka.boot_file}",
23
- "Environment: #{Karafka.env}",
24
- "Kafka seed brokers: #{config.kafka.seed_brokers}"
44
+ "Environment: #{Karafka.env}"
25
45
  ]
46
+ end
47
+
48
+ # @return [Array<String>] license related info
49
+ def license_info
50
+ config = Karafka::App.config
26
51
 
27
- Karafka.logger.info(info.join("\n"))
52
+ if config.license.token
53
+ [
54
+ 'License: Commercial',
55
+ "License entity: #{config.license.entity}",
56
+ "License expires on: #{config.license.expires_on}"
57
+ ]
58
+ else
59
+ [
60
+ 'License: LGPL-3.0'
61
+ ]
62
+ end
28
63
  end
29
64
  end
30
65
  end
@@ -12,19 +12,15 @@ module Karafka
12
12
  # Directories created by default
13
13
  INSTALL_DIRS = %w[
14
14
  app/consumers
15
- app/responders
16
- app/workers
17
15
  config
18
- lib
19
16
  log
20
- tmp/pids
21
17
  ].freeze
22
18
 
23
19
  # Where should we map proper files from templates
24
20
  INSTALL_FILES_MAP = {
25
21
  'karafka.rb.erb' => Karafka.boot_file.basename,
26
22
  'application_consumer.rb.erb' => 'app/consumers/application_consumer.rb',
27
- 'application_responder.rb.erb' => 'app/responders/application_responder.rb'
23
+ 'example_consumer.rb.erb' => 'app/consumers/example_consumer.rb'
28
24
  }.freeze
29
25
 
30
26
  # @param args [Array] all the things that Thor CLI accepts
@@ -35,6 +31,7 @@ module Karafka
35
31
  Bundler.default_lockfile
36
32
  )
37
33
  ).dependencies
34
+
38
35
  @rails = dependencies.key?('railties') || dependencies.key?('rails')
39
36
  end
40
37
 
@@ -48,9 +45,7 @@ module Karafka
48
45
  target = Karafka.root.join(target)
49
46
 
50
47
  template = File.read(Karafka.core_root.join("templates/#{source}"))
51
- # @todo Replace with the keyword argument version once we don't have to support
52
- # Ruby < 2.6
53
- render = ::ERB.new(template, nil, '-').result(binding)
48
+ render = ::ERB.new(template, trim_mode: '-').result(binding)
54
49
 
55
50
  File.open(target, 'w') { |file| file.write(render) }
56
51
  end
@@ -12,21 +12,15 @@ module Karafka
12
12
 
13
13
  desc 'Start the Karafka server (short-cut alias: "s")'
14
14
  option aliases: 's'
15
- option :daemon, default: false, type: :boolean, aliases: :d
16
- option :pid, default: 'tmp/pids/karafka', type: :string, aliases: :p
17
15
  option :consumer_groups, type: :array, default: nil, aliases: :g
18
16
 
19
17
  # Start the Karafka server
20
18
  def call
21
- cli.info
19
+ # Print our banner and info in the dev mode
20
+ print_marketing_info if Karafka::App.env.development?
22
21
 
23
22
  validate!
24
23
 
25
- if cli.options[:daemon]
26
- FileUtils.mkdir_p File.dirname(cli.options[:pid])
27
- daemonize
28
- end
29
-
30
24
  # We assign active topics on a server level, as only server is expected to listen on
31
25
  # part of the topics
32
26
  Karafka::Server.consumer_groups = cli.options[:consumer_groups]
@@ -36,6 +30,21 @@ module Karafka
36
30
 
37
31
  private
38
32
 
33
+ # Prints marketing info
34
+ def print_marketing_info
35
+ Karafka.logger.info Info::BANNER
36
+
37
+ if Karafka::App.config.license.token
38
+ Karafka.logger.info(
39
+ "\033[0;32mThank you for investing in the Karafka Pro subscription!\033[0m\n"
40
+ )
41
+ else
42
+ Karafka.logger.info(
43
+ "\033[0;31mYou like Karafka? Please consider getting a Pro subscription!\033[0m\n"
44
+ )
45
+ end
46
+ end
47
+
39
48
  # Checks the server cli configuration
40
49
  # options validations in terms of app setup (topics, pid existence, etc)
41
50
  def validate!
@@ -44,28 +53,6 @@ module Karafka
44
53
 
45
54
  raise Errors::InvalidConfigurationError, result.errors.to_h
46
55
  end
47
-
48
- # Detaches current process into background and writes its pidfile
49
- def daemonize
50
- ::Process.daemon(true)
51
- File.open(
52
- cli.options[:pid],
53
- 'w'
54
- ) { |file| file.write(::Process.pid) }
55
-
56
- # Remove pidfile on stop, just before the server instance is going to be GCed
57
- # We want to delay the moment in which the pidfile is removed as much as we can,
58
- # so instead of removing it after the server stops running, we rely on the gc moment
59
- # when this object gets removed (it is a bit later), so it is closer to the actual
60
- # system process end. We do that, so monitoring and deployment tools that rely on a pid
61
- # won't alarm or start new system process up until the current one is finished
62
- ObjectSpace.define_finalizer(self, proc { send(:clean) })
63
- end
64
-
65
- # Removes a pidfile (if exist)
66
- def clean
67
- FileUtils.rm_f(cli.options[:pid]) if cli.options[:pid]
68
- end
69
56
  end
70
57
  end
71
58
  end
data/lib/karafka/cli.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Karafka
4
4
  # Karafka framework Cli
5
+ #
5
6
  # If you want to add/modify command that belongs to CLI, please review all commands
6
7
  # available in cli/ directory inside Karafka source code.
7
8
  #
@@ -10,24 +11,16 @@ module Karafka
10
11
  class Cli < Thor
11
12
  package_name 'Karafka'
12
13
 
13
- default_task :missingno
14
-
15
14
  class << self
16
- # Loads all Cli commands into Thor framework
15
+ # Loads all Cli commands into Thor framework.
17
16
  # This method should be executed before we run Karafka::Cli.start, otherwise we won't
18
- # have any Cli commands available
17
+ # have any Cli commands available.
19
18
  def prepare
20
19
  cli_commands.each do |action|
21
20
  action.bind_to(self)
22
21
  end
23
22
  end
24
23
 
25
- # When there is a CLI crash, exit
26
- # @return [true]
27
- def exit_on_failure?
28
- true
29
- end
30
-
31
24
  private
32
25
 
33
26
  # @return [Array<Class>] Array with Cli action classes that can be used as commands
@@ -42,7 +35,7 @@ module Karafka
42
35
  end
43
36
  end
44
37
 
45
- # This is kinda trick - since we don't have a autoload and other magic stuff
38
+ # This is kinda tricky - since we don't have an autoload and other magic stuff
46
39
  # like Rails does, so instead this method allows us to replace currently running
47
40
  # console with a new one via Kernel.exec. It will start console with new code loaded
48
41
  # Yes, we know that it is not turbo fast, however it is turbo convenient and small