karafka 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/FUNDING.yml +1 -0
  4. data/.github/workflows/ci.yml +40 -16
  5. data/.rspec +4 -0
  6. data/.ruby-version +1 -1
  7. data/CHANGELOG.md +459 -796
  8. data/Gemfile +3 -2
  9. data/Gemfile.lock +44 -32
  10. data/LICENSE +1 -1
  11. data/README.md +22 -17
  12. data/bin/integrations +86 -26
  13. data/bin/karafka +1 -13
  14. data/bin/rspecs +6 -0
  15. data/bin/verify_license_integrity +37 -0
  16. data/certs/cert_chain.pem +26 -0
  17. data/config/{errors.yml → locales/errors.yml} +34 -6
  18. data/config/locales/pro_errors.yml +34 -0
  19. data/docker-compose.yml +4 -30
  20. data/karafka.gemspec +17 -10
  21. data/lib/active_job/karafka.rb +3 -7
  22. data/lib/active_job/queue_adapters/karafka_adapter.rb +7 -1
  23. data/lib/karafka/active_job/consumer.rb +29 -6
  24. data/lib/karafka/active_job/current_attributes/loading.rb +36 -0
  25. data/lib/karafka/active_job/current_attributes/persistence.rb +28 -0
  26. data/lib/karafka/active_job/current_attributes.rb +42 -0
  27. data/lib/karafka/active_job/dispatcher.rb +34 -3
  28. data/lib/karafka/active_job/job_options_contract.rb +13 -2
  29. data/lib/karafka/admin.rb +246 -0
  30. data/lib/karafka/app.rb +30 -7
  31. data/lib/karafka/base_consumer.rb +124 -82
  32. data/lib/karafka/cli/base.rb +20 -0
  33. data/lib/karafka/cli/console.rb +13 -8
  34. data/lib/karafka/cli/info.rb +3 -2
  35. data/lib/karafka/cli/server.rb +55 -8
  36. data/lib/karafka/cli/topics.rb +146 -0
  37. data/lib/karafka/connection/client.rb +118 -61
  38. data/lib/karafka/connection/consumer_group_coordinator.rb +48 -0
  39. data/lib/karafka/connection/listener.rb +78 -30
  40. data/lib/karafka/connection/listeners_batch.rb +18 -2
  41. data/lib/karafka/connection/rebalance_manager.rb +3 -0
  42. data/lib/karafka/contracts/config.rb +1 -1
  43. data/lib/karafka/contracts/consumer_group.rb +22 -9
  44. data/lib/karafka/contracts/server_cli_options.rb +71 -11
  45. data/lib/karafka/contracts/topic.rb +65 -0
  46. data/lib/karafka/contracts.rb +1 -1
  47. data/lib/karafka/embedded.rb +36 -0
  48. data/lib/karafka/errors.rb +6 -4
  49. data/lib/karafka/helpers/colorize.rb +6 -0
  50. data/lib/karafka/instrumentation/callbacks/error.rb +3 -4
  51. data/lib/karafka/instrumentation/callbacks/statistics.rb +14 -4
  52. data/lib/karafka/instrumentation/logger_listener.rb +151 -22
  53. data/lib/karafka/instrumentation/notifications.rb +26 -7
  54. data/lib/karafka/instrumentation/proctitle_listener.rb +7 -16
  55. data/lib/karafka/instrumentation/vendors/datadog/dashboard.json +1 -1
  56. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +153 -0
  57. data/lib/karafka/instrumentation/vendors/datadog/{listener.rb → metrics_listener.rb} +62 -35
  58. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +166 -0
  59. data/lib/karafka/licenser.rb +53 -50
  60. data/lib/karafka/messages/builders/batch_metadata.rb +8 -8
  61. data/lib/karafka/messages/builders/messages.rb +6 -2
  62. data/lib/karafka/messages/message.rb +14 -2
  63. data/lib/karafka/messages/messages.rb +5 -0
  64. data/lib/karafka/messages/parser.rb +14 -0
  65. data/lib/karafka/patches/rdkafka/bindings.rb +132 -0
  66. data/lib/karafka/pro/active_job/consumer.rb +10 -9
  67. data/lib/karafka/pro/active_job/dispatcher.rb +28 -3
  68. data/lib/karafka/pro/active_job/job_options_contract.rb +16 -3
  69. data/lib/karafka/pro/encryption/cipher.rb +58 -0
  70. data/lib/karafka/pro/encryption/contracts/config.rb +79 -0
  71. data/lib/karafka/pro/encryption/errors.rb +24 -0
  72. data/lib/karafka/pro/encryption/messages/middleware.rb +46 -0
  73. data/lib/karafka/pro/encryption/messages/parser.rb +56 -0
  74. data/lib/karafka/pro/encryption/setup/config.rb +48 -0
  75. data/lib/karafka/pro/encryption.rb +47 -0
  76. data/lib/karafka/pro/iterator.rb +253 -0
  77. data/lib/karafka/pro/loader.rb +52 -26
  78. data/lib/karafka/pro/performance_tracker.rb +10 -6
  79. data/lib/karafka/pro/processing/collapser.rb +62 -0
  80. data/lib/karafka/pro/processing/coordinator.rb +92 -16
  81. data/lib/karafka/pro/processing/filters/base.rb +61 -0
  82. data/lib/karafka/pro/processing/filters/delayer.rb +70 -0
  83. data/lib/karafka/pro/processing/filters/expirer.rb +51 -0
  84. data/lib/karafka/pro/processing/filters/throttler.rb +84 -0
  85. data/lib/karafka/pro/processing/filters/virtual_limiter.rb +52 -0
  86. data/lib/karafka/pro/processing/filters_applier.rb +104 -0
  87. data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +7 -5
  88. data/lib/karafka/pro/processing/jobs/revoked_non_blocking.rb +37 -0
  89. data/lib/karafka/pro/processing/jobs_builder.rb +22 -4
  90. data/lib/karafka/pro/processing/partitioner.rb +33 -24
  91. data/lib/karafka/pro/processing/scheduler.rb +27 -8
  92. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +68 -0
  93. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +74 -0
  94. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom.rb +72 -0
  95. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +76 -0
  96. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +64 -0
  97. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +68 -0
  98. data/lib/karafka/pro/processing/strategies/aj/dlq_mom.rb +64 -0
  99. data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +69 -0
  100. data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom.rb +38 -0
  101. data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom_vp.rb +64 -0
  102. data/lib/karafka/pro/processing/strategies/aj/ftr_mom.rb +38 -0
  103. data/lib/karafka/pro/processing/strategies/aj/ftr_mom_vp.rb +58 -0
  104. data/lib/karafka/pro/processing/strategies/aj/lrj_mom.rb +37 -0
  105. data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +79 -0
  106. data/lib/karafka/pro/processing/strategies/aj/mom.rb +36 -0
  107. data/lib/karafka/pro/processing/strategies/aj/mom_vp.rb +52 -0
  108. data/lib/karafka/pro/processing/strategies/base.rb +26 -0
  109. data/lib/karafka/pro/processing/strategies/default.rb +105 -0
  110. data/lib/karafka/pro/processing/strategies/dlq/default.rb +131 -0
  111. data/lib/karafka/pro/processing/strategies/dlq/ftr.rb +61 -0
  112. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj.rb +75 -0
  113. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +71 -0
  114. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom_vp.rb +43 -0
  115. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_vp.rb +41 -0
  116. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +69 -0
  117. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom_vp.rb +41 -0
  118. data/lib/karafka/pro/processing/strategies/dlq/ftr_vp.rb +40 -0
  119. data/lib/karafka/pro/processing/strategies/dlq/lrj.rb +63 -0
  120. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +63 -0
  121. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom_vp.rb +36 -0
  122. data/lib/karafka/pro/processing/strategies/dlq/lrj_vp.rb +39 -0
  123. data/lib/karafka/pro/processing/strategies/dlq/mom.rb +68 -0
  124. data/lib/karafka/pro/processing/strategies/dlq/mom_vp.rb +37 -0
  125. data/lib/karafka/pro/processing/strategies/dlq/vp.rb +40 -0
  126. data/lib/karafka/pro/processing/strategies/ftr/default.rb +104 -0
  127. data/lib/karafka/pro/processing/strategies/ftr/vp.rb +40 -0
  128. data/lib/karafka/pro/processing/strategies/lrj/default.rb +87 -0
  129. data/lib/karafka/pro/processing/strategies/lrj/ftr.rb +69 -0
  130. data/lib/karafka/pro/processing/strategies/lrj/ftr_mom.rb +67 -0
  131. data/lib/karafka/pro/processing/strategies/lrj/ftr_mom_vp.rb +40 -0
  132. data/lib/karafka/pro/processing/strategies/lrj/ftr_vp.rb +39 -0
  133. data/lib/karafka/pro/processing/strategies/lrj/mom.rb +80 -0
  134. data/lib/karafka/pro/processing/strategies/lrj/mom_vp.rb +38 -0
  135. data/lib/karafka/pro/processing/strategies/lrj/vp.rb +36 -0
  136. data/lib/karafka/pro/processing/strategies/mom/default.rb +46 -0
  137. data/lib/karafka/pro/processing/strategies/mom/ftr.rb +53 -0
  138. data/lib/karafka/pro/processing/strategies/mom/ftr_vp.rb +37 -0
  139. data/lib/karafka/pro/processing/strategies/mom/vp.rb +35 -0
  140. data/lib/karafka/pro/processing/strategies/vp/default.rb +104 -0
  141. data/lib/karafka/pro/{contracts/base.rb → processing/strategies.rb} +6 -5
  142. data/lib/karafka/pro/processing/strategy_selector.rb +84 -0
  143. data/lib/karafka/pro/processing/virtual_offset_manager.rb +147 -0
  144. data/lib/karafka/pro/routing/features/base.rb +24 -0
  145. data/lib/karafka/pro/routing/features/dead_letter_queue/contract.rb +50 -0
  146. data/lib/karafka/pro/routing/features/dead_letter_queue.rb +27 -0
  147. data/lib/karafka/pro/routing/{builder_extensions.rb → features/delaying/config.rb} +8 -11
  148. data/lib/karafka/pro/routing/features/delaying/contract.rb +38 -0
  149. data/lib/karafka/pro/routing/features/delaying/topic.rb +59 -0
  150. data/lib/karafka/pro/routing/features/delaying.rb +29 -0
  151. data/lib/karafka/pro/routing/features/expiring/config.rb +27 -0
  152. data/lib/karafka/pro/routing/features/expiring/contract.rb +38 -0
  153. data/lib/karafka/pro/routing/features/expiring/topic.rb +59 -0
  154. data/lib/karafka/pro/routing/features/expiring.rb +27 -0
  155. data/lib/karafka/pro/routing/features/filtering/config.rb +40 -0
  156. data/lib/karafka/pro/routing/features/filtering/contract.rb +41 -0
  157. data/lib/karafka/pro/routing/features/filtering/topic.rb +51 -0
  158. data/lib/karafka/pro/routing/features/filtering.rb +27 -0
  159. data/lib/karafka/pro/routing/features/long_running_job/config.rb +28 -0
  160. data/lib/karafka/pro/routing/features/long_running_job/contract.rb +37 -0
  161. data/lib/karafka/pro/routing/features/long_running_job/topic.rb +42 -0
  162. data/lib/karafka/pro/routing/features/long_running_job.rb +28 -0
  163. data/lib/karafka/pro/routing/features/throttling/config.rb +32 -0
  164. data/lib/karafka/pro/routing/features/throttling/contract.rb +41 -0
  165. data/lib/karafka/pro/routing/features/throttling/topic.rb +69 -0
  166. data/lib/karafka/pro/routing/features/throttling.rb +30 -0
  167. data/lib/karafka/pro/routing/features/virtual_partitions/config.rb +30 -0
  168. data/lib/karafka/pro/routing/features/virtual_partitions/contract.rb +52 -0
  169. data/lib/karafka/pro/routing/features/virtual_partitions/topic.rb +56 -0
  170. data/lib/karafka/pro/routing/features/virtual_partitions.rb +27 -0
  171. data/lib/karafka/process.rb +23 -8
  172. data/lib/karafka/processing/coordinator.rb +102 -23
  173. data/lib/karafka/processing/coordinators_buffer.rb +5 -1
  174. data/lib/karafka/processing/executor.rb +51 -21
  175. data/lib/karafka/processing/executors_buffer.rb +10 -26
  176. data/lib/karafka/processing/jobs/base.rb +6 -2
  177. data/lib/karafka/processing/jobs/consume.rb +8 -5
  178. data/lib/karafka/processing/jobs/idle.rb +24 -0
  179. data/lib/karafka/processing/jobs_builder.rb +2 -3
  180. data/lib/karafka/processing/jobs_queue.rb +14 -8
  181. data/lib/karafka/processing/partitioner.rb +3 -1
  182. data/lib/karafka/processing/result.rb +14 -1
  183. data/lib/karafka/processing/strategies/aj_dlq_mom.rb +44 -0
  184. data/lib/karafka/processing/strategies/aj_mom.rb +21 -0
  185. data/lib/karafka/processing/strategies/base.rb +52 -0
  186. data/lib/karafka/processing/strategies/default.rb +142 -0
  187. data/lib/karafka/processing/strategies/dlq.rb +88 -0
  188. data/lib/karafka/processing/strategies/dlq_mom.rb +49 -0
  189. data/lib/karafka/processing/strategies/mom.rb +29 -0
  190. data/lib/karafka/processing/strategy_selector.rb +47 -0
  191. data/lib/karafka/processing/worker.rb +8 -3
  192. data/lib/karafka/railtie.rb +24 -12
  193. data/lib/karafka/routing/activity_manager.rb +84 -0
  194. data/lib/karafka/routing/builder.rb +20 -0
  195. data/lib/karafka/routing/consumer_group.rb +38 -4
  196. data/lib/karafka/routing/consumer_mapper.rb +0 -10
  197. data/lib/karafka/routing/features/active_job/builder.rb +33 -0
  198. data/lib/karafka/routing/features/active_job/config.rb +15 -0
  199. data/lib/karafka/routing/features/active_job/contract.rb +41 -0
  200. data/lib/karafka/routing/features/active_job/topic.rb +33 -0
  201. data/lib/karafka/routing/features/active_job.rb +13 -0
  202. data/lib/karafka/routing/features/base/expander.rb +53 -0
  203. data/lib/karafka/routing/features/base.rb +34 -0
  204. data/lib/karafka/routing/features/dead_letter_queue/config.rb +19 -0
  205. data/lib/karafka/routing/features/dead_letter_queue/contract.rb +42 -0
  206. data/lib/karafka/routing/features/dead_letter_queue/topic.rb +41 -0
  207. data/lib/karafka/routing/features/dead_letter_queue.rb +16 -0
  208. data/lib/karafka/routing/features/declaratives/config.rb +18 -0
  209. data/lib/karafka/routing/features/declaratives/contract.rb +30 -0
  210. data/lib/karafka/routing/features/declaratives/topic.rb +44 -0
  211. data/lib/karafka/routing/features/declaratives.rb +14 -0
  212. data/lib/karafka/routing/features/manual_offset_management/config.rb +15 -0
  213. data/lib/karafka/routing/features/manual_offset_management/contract.rb +24 -0
  214. data/lib/karafka/routing/features/manual_offset_management/topic.rb +35 -0
  215. data/lib/karafka/routing/features/manual_offset_management.rb +18 -0
  216. data/lib/karafka/routing/proxy.rb +18 -20
  217. data/lib/karafka/routing/router.rb +27 -2
  218. data/lib/karafka/routing/subscription_group.rb +43 -5
  219. data/lib/karafka/routing/subscription_groups_builder.rb +6 -1
  220. data/lib/karafka/routing/topic.rb +17 -5
  221. data/lib/karafka/routing/topics.rb +8 -0
  222. data/lib/karafka/runner.rb +1 -0
  223. data/lib/karafka/serialization/json/deserializer.rb +1 -0
  224. data/lib/karafka/server.rb +58 -28
  225. data/lib/karafka/setup/attributes_map.rb +344 -0
  226. data/lib/karafka/setup/config.rb +48 -19
  227. data/lib/karafka/status.rb +35 -7
  228. data/lib/karafka/templates/karafka.rb.erb +14 -6
  229. data/lib/karafka/time_trackers/base.rb +1 -6
  230. data/lib/karafka/time_trackers/pause.rb +49 -19
  231. data/lib/karafka/time_trackers/poll.rb +7 -3
  232. data/lib/karafka/version.rb +1 -1
  233. data/lib/karafka.rb +56 -3
  234. data/renovate.json +6 -0
  235. data.tar.gz.sig +0 -0
  236. metadata +189 -61
  237. metadata.gz.sig +0 -0
  238. data/bin/wait_for_kafka +0 -20
  239. data/certs/mensfeld.pem +0 -25
  240. data/lib/karafka/active_job/routing/extensions.rb +0 -31
  241. data/lib/karafka/contracts/consumer_group_topic.rb +0 -42
  242. data/lib/karafka/instrumentation.rb +0 -21
  243. data/lib/karafka/pro/base_consumer.rb +0 -82
  244. data/lib/karafka/pro/contracts/consumer_group.rb +0 -34
  245. data/lib/karafka/pro/contracts/consumer_group_topic.rb +0 -33
  246. data/lib/karafka/pro/routing/topic_extensions.rb +0 -38
@@ -8,68 +8,71 @@ module Karafka
8
8
 
9
9
  private_constant :PUBLIC_KEY_LOCATION
10
10
 
11
- # Tries to prepare license and verifies it
12
- #
13
- # @param license_config [Karafka::Core::Configurable::Node] config related to the licensing
14
- def prepare_and_verify(license_config)
15
- prepare(license_config)
16
- verify(license_config)
17
- end
18
-
19
- private
11
+ class << self
12
+ # Tries to load the license and yields if successful
13
+ def detect
14
+ # If required, do not require again
15
+ require('karafka-license') unless const_defined?('::Karafka::License')
20
16
 
21
- # @param license_config [Karafka::Core::Configurable::Node] config related to the licensing
22
- def prepare(license_config)
23
- # If there is token, no action needed
24
- # We support a case where someone would put the token in instead of using one from the
25
- # license. That's in case there are limitations to using external package sources, etc
26
- return if license_config.token
17
+ yield
27
18
 
28
- begin
29
- license_config.token || require('karafka-license')
19
+ true
30
20
  rescue LoadError
31
- return
21
+ false
32
22
  end
33
23
 
34
- license_config.token = Karafka::License.token
35
- end
36
-
37
- # Check license and setup license details (if needed)
38
- # @param license_config [Karafka::Core::Configurable::Node] config related to the licensing
39
- def verify(license_config)
40
- # If no license, it will just run LGPL components without anything extra
41
- return unless license_config.token
24
+ # Tries to prepare license and verifies it
25
+ #
26
+ # @param license_config [Karafka::Core::Configurable::Node] config related to the licensing
27
+ def prepare_and_verify(license_config)
28
+ # If license is not loaded, nothing to do
29
+ return unless const_defined?('::Karafka::License')
42
30
 
43
- public_key = OpenSSL::PKey::RSA.new(File.read(PUBLIC_KEY_LOCATION))
31
+ prepare(license_config)
32
+ verify(license_config)
33
+ end
44
34
 
45
- # We gsub and strip in case someone copy-pasted it as a multi line string
46
- formatted_token = license_config.token.strip.delete("\n").delete(' ')
47
- decoded_token = Base64.decode64(formatted_token)
35
+ private
48
36
 
49
- begin
50
- data = public_key.public_decrypt(decoded_token)
51
- rescue OpenSSL::OpenSSLError
52
- data = nil
37
+ # @param license_config [Karafka::Core::Configurable::Node] config related to the licensing
38
+ def prepare(license_config)
39
+ license_config.token = Karafka::License.token
53
40
  end
54
41
 
55
- details = data ? JSON.parse(data) : raise_invalid_license_token(license_config)
42
+ # Check license and setup license details (if needed)
43
+ # @param license_config [Karafka::Core::Configurable::Node] config related to the licensing
44
+ def verify(license_config)
45
+ public_key = OpenSSL::PKey::RSA.new(File.read(PUBLIC_KEY_LOCATION))
56
46
 
57
- license_config.entity = details.fetch('entity')
58
- end
47
+ # We gsub and strip in case someone copy-pasted it as a multi line string
48
+ formatted_token = license_config.token.strip.delete("\n").delete(' ')
49
+ decoded_token = Base64.decode64(formatted_token)
59
50
 
60
- # Raises an error with info, that used token is invalid
61
- # @param license_config [Karafka::Core::Configurable::Node]
62
- def raise_invalid_license_token(license_config)
63
- # We set it to false so `Karafka.pro?` method behaves as expected
64
- license_config.token = false
65
-
66
- raise(
67
- Errors::InvalidLicenseTokenError,
68
- <<~MSG.tr("\n", ' ')
69
- License key you provided is invalid.
70
- Please reach us at contact@karafka.io or visit https://karafka.io to obtain a valid one.
71
- MSG
72
- )
51
+ begin
52
+ data = public_key.public_decrypt(decoded_token)
53
+ rescue OpenSSL::OpenSSLError
54
+ data = nil
55
+ end
56
+
57
+ details = data ? JSON.parse(data) : raise_invalid_license_token(license_config)
58
+
59
+ license_config.entity = details.fetch('entity')
60
+ end
61
+
62
+ # Raises an error with info, that used token is invalid
63
+ # @param license_config [Karafka::Core::Configurable::Node]
64
+ def raise_invalid_license_token(license_config)
65
+ # We set it to false so `Karafka.pro?` method behaves as expected
66
+ license_config.token = false
67
+
68
+ raise(
69
+ Errors::InvalidLicenseTokenError,
70
+ <<~MSG.tr("\n", ' ')
71
+ License key you provided is invalid.
72
+ Please reach us at contact@karafka.io or visit https://karafka.io to obtain a valid one.
73
+ MSG
74
+ )
75
+ end
73
76
  end
74
77
  end
75
78
  end
@@ -10,27 +10,27 @@ module Karafka
10
10
  #
11
11
  # @param messages [Array<Karafka::Messages::Message>] messages array
12
12
  # @param topic [Karafka::Routing::Topic] topic for which we've fetched the batch
13
+ # @param partition [Integer] partition of this metadata
13
14
  # @param scheduled_at [Time] moment when the batch was scheduled for processing
14
15
  # @return [Karafka::Messages::BatchMetadata] batch metadata object
15
16
  #
16
17
  # @note We do not set `processed_at` as this needs to be assigned when the batch is
17
18
  # picked up for processing.
18
- def call(messages, topic, scheduled_at)
19
+ def call(messages, topic, partition, scheduled_at)
19
20
  Karafka::Messages::BatchMetadata.new(
20
21
  size: messages.count,
21
- first_offset: messages.first.offset,
22
- last_offset: messages.last.offset,
22
+ first_offset: messages.first&.offset || -1001,
23
+ last_offset: messages.last&.offset || -1001,
23
24
  deserializer: topic.deserializer,
24
- partition: messages.first.partition,
25
+ partition: partition,
25
26
  topic: topic.name,
26
27
  # We go with the assumption that the creation of the whole batch is the last message
27
28
  # creation time
28
- created_at: messages.last.timestamp,
29
+ created_at: messages.last&.timestamp || nil,
29
30
  # When this batch was built and scheduled for execution
30
31
  scheduled_at: scheduled_at,
31
- # We build the batch metadata when we pick up the job in the worker, thus we can use
32
- # current time here
33
- processed_at: Time.now
32
+ # This needs to be set to a correct value prior to processing starting
33
+ processed_at: nil
34
34
  )
35
35
  end
36
36
  end
@@ -11,14 +11,18 @@ module Karafka
11
11
  #
12
12
  # @param messages [Array<Karafka::Messages::Message>] karafka messages array
13
13
  # @param topic [Karafka::Routing::Topic] topic for which we're received messages
14
+ # @param partition [Integer] partition of those messages
14
15
  # @param received_at [Time] moment in time when the messages were received
15
16
  # @return [Karafka::Messages::Messages] messages batch object
16
- def call(messages, topic, received_at)
17
+ def call(messages, topic, partition, received_at)
18
+ # We cannot freeze the batch metadata because it is altered with the processed_at time
19
+ # prior to the consumption. It is being frozen there
17
20
  metadata = BatchMetadata.call(
18
21
  messages,
19
22
  topic,
23
+ partition,
20
24
  received_at
21
- ).freeze
25
+ )
22
26
 
23
27
  Karafka::Messages::Messages.new(
24
28
  messages,
@@ -9,7 +9,19 @@ module Karafka
9
9
  class Message
10
10
  extend Forwardable
11
11
 
12
- attr_reader :raw_payload, :metadata
12
+ class << self
13
+ # @return [Object] general parser
14
+ # @note We cache it here for performance reasons. It is 2.5x times faster than getting it
15
+ # via the config chain.
16
+ def parser
17
+ @parser ||= App.config.internal.messages.parser
18
+ end
19
+ end
20
+
21
+ attr_reader :metadata
22
+ # raw payload needs to be mutable as we want to have option to change it in the parser
23
+ # prior to the final deserialization
24
+ attr_accessor :raw_payload
13
25
 
14
26
  def_delegators :metadata, *Metadata.members
15
27
 
@@ -42,7 +54,7 @@ module Karafka
42
54
 
43
55
  # @return [Object] deserialized data
44
56
  def deserialize
45
- metadata.deserializer.call(self)
57
+ self.class.parser.call(self)
46
58
  end
47
59
  end
48
60
  end
@@ -40,6 +40,11 @@ module Karafka
40
40
  map(&:raw_payload)
41
41
  end
42
42
 
43
+ # @return [Boolean] is the messages batch empty
44
+ def empty?
45
+ @messages_array.empty?
46
+ end
47
+
43
48
  # @return [Karafka::Messages::Message] first message
44
49
  def first
45
50
  @messages_array.first
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Messages
5
+ # Default message parser. The only thing it does, is calling the deserializer
6
+ class Parser
7
+ # @param message [::Karafka::Messages::Message]
8
+ # @return [Object] deserialized payload
9
+ def call(message)
10
+ message.metadata.deserializer.call(message)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Patches
5
+ module Rdkafka
6
+ # Binding patches that slightly change how rdkafka operates in certain places
7
+ module Bindings
8
+ include ::Rdkafka::Bindings
9
+
10
+ # Alias internally
11
+ RB = ::Rdkafka::Bindings
12
+
13
+ class << self
14
+ # Handle assignments on cooperative rebalance
15
+ #
16
+ # @param client_ptr [FFI::Pointer]
17
+ # @param code [Integer]
18
+ # @param partitions_ptr [FFI::Pointer]
19
+ def on_cooperative_rebalance(client_ptr, code, partitions_ptr)
20
+ case code
21
+ when RB::RD_KAFKA_RESP_ERR__ASSIGN_PARTITIONS
22
+ RB.rd_kafka_incremental_assign(client_ptr, partitions_ptr)
23
+ when RB::RD_KAFKA_RESP_ERR__REVOKE_PARTITIONS
24
+ RB.rd_kafka_commit(client_ptr, nil, false)
25
+ RB.rd_kafka_incremental_unassign(client_ptr, partitions_ptr)
26
+ else
27
+ RB.rd_kafka_assign(client_ptr, FFI::Pointer::NULL)
28
+ end
29
+ end
30
+
31
+ # Handle assignments on a eager rebalance
32
+ #
33
+ # @param client_ptr [FFI::Pointer]
34
+ # @param code [Integer]
35
+ # @param partitions_ptr [FFI::Pointer]
36
+ def on_eager_rebalance(client_ptr, code, partitions_ptr)
37
+ case code
38
+ when RB::RD_KAFKA_RESP_ERR__ASSIGN_PARTITIONS
39
+ RB.rd_kafka_assign(client_ptr, partitions_ptr)
40
+ when RB::RD_KAFKA_RESP_ERR__REVOKE_PARTITIONS
41
+ RB.rd_kafka_commit(client_ptr, nil, false)
42
+ RB.rd_kafka_assign(client_ptr, FFI::Pointer::NULL)
43
+ else
44
+ RB.rd_kafka_assign(client_ptr, FFI::Pointer::NULL)
45
+ end
46
+ end
47
+
48
+ # Trigger Karafka callbacks
49
+ #
50
+ # @param code [Integer]
51
+ # @param opaque [Rdkafka::Opaque]
52
+ # @param consumer [Rdkafka::Consumer]
53
+ # @param tpl [Rdkafka::Consumer::TopicPartitionList]
54
+ def trigger_callbacks(code, opaque, consumer, tpl)
55
+ case code
56
+ when RB::RD_KAFKA_RESP_ERR__ASSIGN_PARTITIONS
57
+ opaque.call_on_partitions_assigned(consumer, tpl)
58
+ when RB::RD_KAFKA_RESP_ERR__REVOKE_PARTITIONS
59
+ opaque.call_on_partitions_revoked(consumer, tpl)
60
+ end
61
+ rescue StandardError => e
62
+ Karafka.monitor.instrument(
63
+ 'error.occurred',
64
+ caller: self,
65
+ error: e,
66
+ type: 'connection.client.rebalance_callback.error'
67
+ )
68
+ end
69
+ end
70
+
71
+ # This patch changes few things:
72
+ # - it commits offsets (if any) upon partition revocation, so less jobs need to be
73
+ # reprocessed if they are assigned to a different process
74
+ # - reports callback errors into the errors instrumentation instead of the logger
75
+ # - catches only StandardError instead of Exception as we fully control the directly
76
+ # executed callbacks
77
+ #
78
+ # @see https://docs.confluent.io/2.0.0/clients/librdkafka/classRdKafka_1_1RebalanceCb.html
79
+ RebalanceCallback = FFI::Function.new(
80
+ :void, %i[pointer int pointer pointer]
81
+ ) do |client_ptr, code, partitions_ptr, opaque_ptr|
82
+ # Patch reference
83
+ pr = ::Karafka::Patches::Rdkafka::Bindings
84
+
85
+ if RB.rd_kafka_rebalance_protocol(client_ptr) == 'COOPERATIVE'
86
+ pr.on_cooperative_rebalance(client_ptr, code, partitions_ptr)
87
+ else
88
+ pr.on_eager_rebalance(client_ptr, code, partitions_ptr)
89
+ end
90
+
91
+ opaque = ::Rdkafka::Config.opaques[opaque_ptr.to_i]
92
+ return unless opaque
93
+
94
+ tpl = ::Rdkafka::Consumer::TopicPartitionList.from_native_tpl(partitions_ptr).freeze
95
+ consumer = ::Rdkafka::Consumer.new(client_ptr)
96
+
97
+ pr.trigger_callbacks(code, opaque, consumer, tpl)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ # We need to replace the original callback with ours.
105
+ # At the moment there is no API in rdkafka-ruby to do so
106
+ ::Rdkafka::Bindings.send(
107
+ :remove_const,
108
+ 'RebalanceCallback'
109
+ )
110
+
111
+ ::Rdkafka::Bindings.const_set(
112
+ 'RebalanceCallback',
113
+ Karafka::Patches::Rdkafka::Bindings::RebalanceCallback
114
+ )
115
+
116
+ ::Rdkafka::Bindings.attach_function(
117
+ :rd_kafka_rebalance_protocol,
118
+ %i[pointer],
119
+ :string
120
+ )
121
+
122
+ ::Rdkafka::Bindings.attach_function(
123
+ :rd_kafka_incremental_assign,
124
+ %i[pointer pointer],
125
+ :string
126
+ )
127
+
128
+ ::Rdkafka::Bindings.attach_function(
129
+ :rd_kafka_incremental_unassign,
130
+ %i[pointer pointer],
131
+ :string
132
+ )
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # This Karafka component is a Pro component.
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
4
6
  # All of the commercial components are present in the lib/karafka/pro directory of this
5
7
  # repository and their usage requires commercial license agreement.
6
8
  #
@@ -20,23 +22,22 @@ module Karafka
20
22
  #
21
23
  # It contains slightly better revocation warranties than the regular blocking consumer as
22
24
  # it can stop processing batch of jobs in the middle after the revocation.
23
- class Consumer < Karafka::Pro::BaseConsumer
25
+ class Consumer < ::Karafka::ActiveJob::Consumer
24
26
  # Runs ActiveJob jobs processing and handles lrj if needed
25
27
  def consume
26
28
  messages.each do |message|
27
29
  # If for any reason we've lost this partition, not worth iterating over new messages
28
30
  # as they are no longer ours
29
31
  break if revoked?
30
- break if Karafka::App.stopping?
31
32
 
32
- ::ActiveJob::Base.execute(
33
- ::ActiveSupport::JSON.decode(message.raw_payload)
34
- )
33
+ # We cannot early stop when running virtual partitions because the intermediate state
34
+ # would force us not to commit the offsets. This would cause extensive
35
+ # double-processing
36
+ break if Karafka::App.stopping? && !topic.virtual_partitions?
35
37
 
36
- # We cannot mark jobs as done after each if there are virtual partitions. Otherwise
37
- # this could create random markings
38
- next if topic.virtual_partitioner?
38
+ consume_job(message)
39
39
 
40
+ # We can always mark because of the virtual offset management that we have in VPs
40
41
  mark_as_consumed(message)
41
42
  end
42
43
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # This Karafka component is a Pro component.
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
4
6
  # All of the commercial components are present in the lib/karafka/pro directory of this
5
7
  # repository and their usage requires commercial license agreement.
6
8
  #
@@ -21,6 +23,7 @@ module Karafka
21
23
  # They can be updated by using `#karafka_options` on the job
22
24
  DEFAULTS = {
23
25
  dispatch_method: :produce_async,
26
+ dispatch_many_method: :produce_many_async,
24
27
  # We don't create a dummy proc based partitioner as we would have to evaluate it with
25
28
  # each job.
26
29
  partitioner: nil,
@@ -31,16 +34,38 @@ module Karafka
31
34
  private_constant :DEFAULTS
32
35
 
33
36
  # @param job [ActiveJob::Base] job
34
- def call(job)
37
+ def dispatch(job)
35
38
  ::Karafka.producer.public_send(
36
39
  fetch_option(job, :dispatch_method, DEFAULTS),
37
40
  dispatch_details(job).merge!(
38
41
  topic: job.queue_name,
39
- payload: ::ActiveSupport::JSON.encode(job.serialize)
42
+ payload: ::ActiveSupport::JSON.encode(serialize_job(job))
40
43
  )
41
44
  )
42
45
  end
43
46
 
47
+ # Bulk dispatches multiple jobs using the Rails 7.1+ API
48
+ # @param jobs [Array<ActiveJob::Base>] jobs we want to dispatch
49
+ def dispatch_many(jobs)
50
+ dispatches = Hash.new { |hash, key| hash[key] = [] }
51
+
52
+ jobs.each do |job|
53
+ d_method = fetch_option(job, :dispatch_many_method, DEFAULTS)
54
+
55
+ dispatches[d_method] << dispatch_details(job).merge!(
56
+ topic: job.queue_name,
57
+ payload: ::ActiveSupport::JSON.encode(serialize_job(job))
58
+ )
59
+ end
60
+
61
+ dispatches.each do |type, messages|
62
+ ::Karafka.producer.public_send(
63
+ type,
64
+ messages
65
+ )
66
+ end
67
+ end
68
+
44
69
  private
45
70
 
46
71
  # @param job [ActiveJob::Base] job instance
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # This Karafka component is a Pro component.
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
4
6
  # All of the commercial components are present in the lib/karafka/pro directory of this
5
7
  # repository and their usage requires commercial license agreement.
6
8
  #
@@ -18,14 +20,25 @@ module Karafka
18
20
  configure do |config|
19
21
  config.error_messages = YAML.safe_load(
20
22
  File.read(
21
- File.join(Karafka.gem_root, 'config', 'errors.yml')
23
+ File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
22
24
  )
23
25
  ).fetch('en').fetch('validations').fetch('job_options')
24
26
  end
25
27
 
26
- optional(:dispatch_method) { |val| %i[produce_async produce_sync].include?(val) }
27
28
  optional(:partitioner) { |val| val.respond_to?(:call) }
28
29
  optional(:partition_key_type) { |val| %i[key partition_key].include?(val) }
30
+ optional(:dispatch_method) do |val|
31
+ %i[
32
+ produce_async
33
+ produce_sync
34
+ ].include?(val)
35
+ end
36
+ optional(:dispatch_many_method) do |val|
37
+ %i[
38
+ produce_many_async
39
+ produce_many_sync
40
+ ].include?(val)
41
+ end
29
42
  end
30
43
  end
31
44
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Encryption
17
+ # Cipher for encrypting and decrypting data
18
+ class Cipher
19
+ def initialize
20
+ @private_pems = {}
21
+ end
22
+
23
+ # Encrypts given string content with the public key
24
+ # @param content [String]
25
+ # @return [String]
26
+ def encrypt(content)
27
+ public_pem.public_encrypt(content)
28
+ end
29
+
30
+ # Decrypts provided content using `version` key
31
+ # @param version [String] encryption version
32
+ # @param content [String] encrypted content
33
+ # @return [String] decrypted content
34
+ def decrypt(version, content)
35
+ private_pem(version).private_decrypt(content)
36
+ end
37
+
38
+ private
39
+
40
+ # @return [::OpenSSL::PKey::RSA] rsa public key
41
+ def public_pem
42
+ @public_pem ||= ::OpenSSL::PKey::RSA.new(::Karafka::App.config.encryption.public_key)
43
+ end
44
+
45
+ # @param version [String] version for which we want to get the rsa key
46
+ # @return [::OpenSSL::PKey::RSA] rsa private key
47
+ def private_pem(version)
48
+ return @private_pems[version] if @private_pems.key?(version)
49
+
50
+ key_string = ::Karafka::App.config.encryption.private_keys[version]
51
+ key_string || raise(Errors::PrivateKeyNotFound, version)
52
+
53
+ @private_pems[version] = ::OpenSSL::PKey::RSA.new(key_string)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Encryption
17
+ # Encryption related contracts
18
+ module Contracts
19
+ # Makes sure, all the expected config is defined as it should be
20
+ class Config < ::Karafka::Contracts::Base
21
+ configure do |config|
22
+ config.error_messages = YAML.safe_load(
23
+ File.read(
24
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
25
+ )
26
+ ).fetch('en').fetch('validations').fetch('config')
27
+ end
28
+
29
+ nested(:encryption) do
30
+ required(:active) { |val| [true, false].include?(val) }
31
+ required(:version) { |val| val.is_a?(String) && !val.empty? }
32
+ required(:public_key) { |val| val.is_a?(String) }
33
+
34
+ required(:private_keys) do |val|
35
+ val.is_a?(Hash) &&
36
+ val.keys.all? { |key| key.is_a?(String) } &&
37
+ val.values.all? { |key| key.is_a?(String) }
38
+ end
39
+ end
40
+
41
+ # Public key validation
42
+ virtual do |data, errors|
43
+ next unless errors.empty?
44
+ next unless data.fetch(:encryption).fetch(:active)
45
+
46
+ key = OpenSSL::PKey::RSA.new(data.fetch(:encryption).fetch(:public_key))
47
+
48
+ next unless key.private?
49
+
50
+ [[%i[encryption public_key], :needs_to_be_public]]
51
+ rescue OpenSSL::PKey::RSAError
52
+ [[%i[encryption public_key], :invalid]]
53
+ end
54
+
55
+ # Private keys validation
56
+ virtual do |data, errors|
57
+ next unless errors.empty?
58
+ next unless data.fetch(:encryption).fetch(:active)
59
+
60
+ private_keys = data.fetch(:encryption).fetch(:private_keys)
61
+
62
+ # Keys may be empty for production only envs
63
+ next if private_keys.empty?
64
+
65
+ keys = private_keys.each_value.map do |key|
66
+ OpenSSL::PKey::RSA.new(key)
67
+ end
68
+
69
+ next if keys.all?(&:private?)
70
+
71
+ [[%i[encryption private_keys], :need_to_be_private]]
72
+ rescue OpenSSL::PKey::RSAError
73
+ [[%i[encryption private_keys], :invalid]]
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Encryption
17
+ # Encryption related errors
18
+ module Errors
19
+ # Raised when we have encountered encryption key with version we do not have
20
+ PrivateKeyNotFound = Class.new(::Karafka::Errors::BaseError)
21
+ end
22
+ end
23
+ end
24
+ end