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
@@ -0,0 +1,46 @@
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 messages components
18
+ module Messages
19
+ # Middleware for WaterDrop. It automatically encrypts messages payload.
20
+ # It is injected only if encryption is enabled.
21
+ class Middleware
22
+ # @param message [Hash] WaterDrop message hash
23
+ # @return [Hash] hash with encrypted payload and encryption version indicator
24
+ def call(message)
25
+ message[:headers] ||= {}
26
+ message[:headers]['encryption'] = version
27
+ message[:payload] = cipher.encrypt(message[:payload])
28
+ message
29
+ end
30
+
31
+ private
32
+
33
+ # @return [::Karafka::Pro::Encryption::Cipher]
34
+ def cipher
35
+ @cipher ||= ::Karafka::App.config.encryption.cipher
36
+ end
37
+
38
+ # @return [String] encryption version
39
+ def version
40
+ @version ||= ::Karafka::App.config.encryption.version
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,56 @@
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
+ module Messages
18
+ # Pro parser that takes into consideration encryption usage
19
+ # @note There may be a case where someone decides not to encrypt data and we start getting
20
+ # unencrypted payloads. That is why we always rely on message headers for encryption
21
+ # indication.
22
+ class Parser < ::Karafka::Messages::Parser
23
+ # @param message [::Karafka::Messages::Message]
24
+ # @return [Object] deserialized payload
25
+ def call(message)
26
+ if active? && message.headers.key?('encryption')
27
+ # Decrypt raw payload so it can be handled by the default parser logic
28
+ message.raw_payload = cipher.decrypt(
29
+ message.headers['encryption'],
30
+ message.raw_payload
31
+ )
32
+ end
33
+
34
+ super(message)
35
+ end
36
+
37
+ private
38
+
39
+ # @return [::Karafka::Pro::Encryption::Cipher]
40
+ def cipher
41
+ @cipher ||= ::Karafka::App.config.encryption.cipher
42
+ end
43
+
44
+ # @return [Boolean] is encryption active
45
+ def active?
46
+ return @active unless @active.nil?
47
+
48
+ @active = ::Karafka::App.config.encryption.active
49
+
50
+ @active
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,48 @@
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
+ # Setup and config related encryption components
18
+ module Setup
19
+ # Config for encryption
20
+ class Config
21
+ extend ::Karafka::Core::Configurable
22
+
23
+ # Should this feature be in use
24
+ setting(:active, default: false)
25
+
26
+ # Supporting versions allows us to be able to rotate private and public keys in case
27
+ # we would need this. We can increase the version, rotate and Karafka when decrypting
28
+ # will figure out proper private key based on the version
29
+ setting(:version, default: '1')
30
+
31
+ # We always support one public key for producing messages
32
+ # Public key needs to be always present even if we do not plan to produce messages from
33
+ # a Karafka process. This is because of the web-ui and potentially other cases like this
34
+ setting(:public_key, default: '')
35
+
36
+ # Private keys in pem format, where the key is the version and value is the key.
37
+ # This allows us to support key rotation
38
+ setting(:private_keys, default: {})
39
+
40
+ # Cipher used to encrypt and decrypt data
41
+ setting(:cipher, default: Encryption::Cipher.new)
42
+
43
+ configure
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,47 @@
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
+ # Out of the box encryption engine for both Karafka and WaterDrop
17
+ # It uses asymmetric encryption via RSA. We use asymmetric so we can have producers that won't
18
+ # have ability (when private key not added) to decrypt messages.
19
+ module Encryption
20
+ class << self
21
+ # Sets up additional config scope, validations and other things
22
+ #
23
+ # @param config [Karafka::Core::Configurable::Node] root node config
24
+ def pre_setup(config)
25
+ # Expand the config with this feature specific stuff
26
+ config.instance_eval do
27
+ setting(:encryption, default: Setup::Config.config)
28
+ end
29
+ end
30
+
31
+ # @param config [Karafka::Core::Configurable::Node] root node config
32
+ def post_setup(config)
33
+ Encryption::Contracts::Config.new.validate!(config.to_h)
34
+
35
+ # Don't inject extra components if encryption is not active
36
+ return unless config.encryption.active
37
+
38
+ # This parser is encryption aware
39
+ config.internal.messages.parser = Messages::Parser.new
40
+
41
+ # Encryption for WaterDrop
42
+ config.producer.middleware.append(Messages::Middleware.new)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,253 @@
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
+ # Topic iterator allows you to iterate over topic/partition data and perform lookups for
17
+ # information that you need.
18
+ #
19
+ # It supports early stops on finding the requested data and allows for seeking till
20
+ # the end. It also allows for signaling, when a given message should be last out of certain
21
+ # partition, but we still want to continue iterating in other messages.
22
+ #
23
+ # It does **not** create a consumer group and does not have any offset management.
24
+ class Iterator
25
+ # Local partition reference for librdkafka
26
+ Partition = Struct.new(:partition, :offset)
27
+
28
+ private_constant :Partition
29
+
30
+ # A simple API allowing to iterate over topic/partition data, without having to subscribe
31
+ # and deal with rebalances. This API allows for multi-partition streaming and is optimized
32
+ # for data lookups. It allows for explicit stopping iteration over any partition during
33
+ # the iteration process, allowing for optimized lookups.
34
+ #
35
+ # @param topics [Array<String>, Hash] list of strings if we want to subscribe to multiple
36
+ # topics and all of their partitions or a hash where keys are the topics and values are
37
+ # hashes with partitions and their initial offsets.
38
+ # @param settings [Hash] extra settings for the consumer. Please keep in mind, that if
39
+ # overwritten, you may want to include `auto.offset.reset` to match your case.
40
+ # @param yield_nil [Boolean] should we yield also `nil` values when poll returns nothing.
41
+ # Useful in particular for long-living iterators.
42
+ #
43
+ # @note It is worth keeping in mind, that this API also needs to operate within
44
+ # `max.poll.interval.ms` limitations on each iteration
45
+ #
46
+ # @note In case of a never-ending iterator, you need to set `enable.partition.eof` to `false`
47
+ # so we don't stop polling data even when reaching the end (end on a given moment)
48
+ def initialize(
49
+ topics,
50
+ settings: { 'auto.offset.reset': 'beginning' },
51
+ yield_nil: false
52
+ )
53
+ @topics_with_partitions = expand_topics_with_partitions(topics)
54
+
55
+ @routing_topics = @topics_with_partitions.map do |name, _|
56
+ [name, ::Karafka::Routing::Router.find_or_initialize_by_name(name)]
57
+ end.to_h
58
+
59
+ @total_partitions = @topics_with_partitions.map(&:last).sum(&:count)
60
+
61
+ @stopped_partitions = 0
62
+
63
+ @settings = settings
64
+ @yield_nil = yield_nil
65
+ end
66
+
67
+ # Iterates over requested topic partitions and yields the results with the iterator itself
68
+ # Iterator instance is yielded because one can run `stop_partition` to stop iterating over
69
+ # part of data. It is useful for scenarios where we are looking for some information in all
70
+ # the partitions but once we found it, given partition data is no longer needed and would
71
+ # only eat up resources.
72
+ def each
73
+ Admin.with_consumer(@settings) do |consumer|
74
+ tpl = tpl_with_expanded_offsets(consumer)
75
+ consumer.assign(tpl)
76
+
77
+ # We need this for self-referenced APIs like pausing
78
+ @current_consumer = consumer
79
+
80
+ # Stream data until we reach the end of all the partitions or until the end user
81
+ # indicates that they are done
82
+ until done?
83
+ message = poll(200)
84
+
85
+ # Skip nils if not explicitly required
86
+ next if message.nil? && !@yield_nil
87
+
88
+ if message
89
+ @current_message = build_message(message)
90
+
91
+ yield(@current_message, self)
92
+ else
93
+ yield(nil, self)
94
+ end
95
+ end
96
+
97
+ @current_message = nil
98
+ @current_consumer = nil
99
+ end
100
+
101
+ # Reset so we can use the same iterator again if needed
102
+ @stopped_partitions = 0
103
+ end
104
+
105
+ # Stops the partition we're currently yielded into
106
+ def stop_current_partition
107
+ stop_partition(
108
+ @current_message.topic,
109
+ @current_message.partition
110
+ )
111
+ end
112
+
113
+ # Stops processing of a given partition
114
+ # We expect the partition to be provided because of a scenario, where there is a
115
+ # multi-partition iteration and we want to stop a different partition that the one that
116
+ # is currently yielded.
117
+ #
118
+ # We pause it forever and no longer work with it.
119
+ #
120
+ # @param name [String] topic name of which partition we want to stop
121
+ # @param partition [Integer] partition we want to stop processing
122
+ def stop_partition(name, partition)
123
+ @stopped_partitions += 1
124
+
125
+ @current_consumer.pause(
126
+ Rdkafka::Consumer::TopicPartitionList.new(
127
+ name => [Partition.new(partition, 0)]
128
+ )
129
+ )
130
+ end
131
+
132
+ private
133
+
134
+ # Expands topics to which we want to subscribe with partitions information in case this
135
+ # info is not provided. For our convenience we want to support 5 formats of defining
136
+ # the subscribed topics:
137
+ #
138
+ # - 'topic1' - just a string with one topic name
139
+ # - ['topic1', 'topic2'] - just the names
140
+ # - { 'topic1' => -100 } - names with negative lookup offset
141
+ # - { 'topic1' => { 0 => 5 } } - names with exact partitions offsets
142
+ # - { 'topic1' => { 0 => -5 }, 'topic2' => { 1 => 5 } } - with per partition negative offsets
143
+ #
144
+ # @param topics [Array, Hash] topics definitions
145
+ # @return [Hash] hash with topics containing partitions definitions
146
+ def expand_topics_with_partitions(topics)
147
+ # Simplification for the single topic case
148
+ topics = [topics] if topics.is_a?(String)
149
+ # If we've got just array with topics, we need to convert that into a representation
150
+ # that we can expand with offsets
151
+ topics = topics.map { |name| [name, false] }.to_h if topics.is_a?(Array)
152
+
153
+ expanded = Hash.new { |h, k| h[k] = {} }
154
+
155
+ topics.map do |topic, details|
156
+ if details.is_a?(Hash)
157
+ details.each do |partition, offset|
158
+ expanded[topic][partition] = offset
159
+ end
160
+ else
161
+ partition_count(topic.to_s).times do |partition|
162
+ # If no offsets are provided, we just start from zero
163
+ expanded[topic][partition] = details || 0
164
+ end
165
+ end
166
+ end
167
+
168
+ expanded
169
+ end
170
+
171
+ # @param timeout [Integer] timeout in ms
172
+ # @return [Rdkafka::Consumer::Message, nil] message or nil if nothing to do
173
+ def poll(timeout)
174
+ @current_consumer.poll(timeout)
175
+ rescue Rdkafka::RdkafkaError => e
176
+ # End of partition
177
+ if e.code == :partition_eof
178
+ @stopped_partitions += 1
179
+
180
+ retry
181
+ end
182
+
183
+ raise e
184
+ end
185
+
186
+ # Converts raw rdkafka message into Karafka message
187
+ #
188
+ # @param message [Rdkafka::Consumer::Message] raw rdkafka message
189
+ # @return [::Karafka::Messages::Message]
190
+ def build_message(message)
191
+ Messages::Builders::Message.call(
192
+ message,
193
+ @routing_topics.fetch(message.topic),
194
+ Time.now
195
+ )
196
+ end
197
+
198
+ # Do we have all the data we wanted or did every topic partition has reached eof.
199
+ # @return [Boolean]
200
+ def done?
201
+ @stopped_partitions >= @total_partitions
202
+ end
203
+
204
+ # Builds the tpl representing all the subscriptions we want to run
205
+ #
206
+ # Additionally for negative offsets, does the watermark calculation where to start
207
+ #
208
+ # @param consumer [Rdkafka::Consumer] consumer we need in case of negative offsets as
209
+ # negative are going to be used to do "give me last X". We use the already initialized
210
+ # consumer instance, not to start another one again.
211
+ # @return [Rdkafka::Consumer::TopicPartitionList]
212
+ def tpl_with_expanded_offsets(consumer)
213
+ tpl = Rdkafka::Consumer::TopicPartitionList.new
214
+
215
+ @topics_with_partitions.each do |name, partitions|
216
+ partitions_with_offsets = {}
217
+
218
+ # When no offsets defined, we just start from zero
219
+ if partitions.is_a?(Array) || partitions.is_a?(Range)
220
+ partitions_with_offsets = partitions.map { |partition| [partition, 0] }.to_h
221
+ else
222
+ # When offsets defined, we can either use them if positive or expand and move back
223
+ # in case of negative (-1000 means last 1000 messages, etc)
224
+ partitions.each do |partition, offset|
225
+ if offset.negative?
226
+ _, high_watermark_offset = consumer.query_watermark_offsets(name, partition)
227
+ # We add because this offset is negative
228
+ partitions_with_offsets[partition] = high_watermark_offset + offset
229
+ else
230
+ partitions_with_offsets[partition] = offset
231
+ end
232
+ end
233
+ end
234
+
235
+ tpl.add_topic_and_partitions_with_offsets(name, partitions_with_offsets)
236
+ end
237
+
238
+ tpl
239
+ end
240
+
241
+ # @param name [String] topic name
242
+ # @return [Integer] number of partitions of the topic we want to iterate over
243
+ def partition_count(name)
244
+ Admin
245
+ .cluster_info
246
+ .topics
247
+ .find { |topic| topic.fetch(:topic_name) == name }
248
+ .fetch(:partitions)
249
+ .count
250
+ end
251
+ end
252
+ end
253
+ 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
  #
@@ -13,43 +15,66 @@ module Karafka
13
15
  module Pro
14
16
  # Loader requires and loads all the pro components only when they are needed
15
17
  class Loader
16
- # All the pro components that need to be loaded
17
- COMPONENTS = %w[
18
- base_consumer
19
- performance_tracker
20
- processing/scheduler
21
- processing/jobs/consume_non_blocking
22
- processing/jobs_builder
23
- processing/coordinator
24
- processing/partitioner
25
- contracts/base
26
- contracts/consumer_group
27
- contracts/consumer_group_topic
28
- routing/topic_extensions
29
- routing/builder_extensions
30
- active_job/consumer
18
+ # There seems to be a conflict in between using two Zeitwerk instances and it makes lookups
19
+ # for nested namespaces instead of creating them.
20
+ # We require those not to deal with this and then all works as expected
21
+ FORCE_LOADED = %w[
31
22
  active_job/dispatcher
32
- active_job/job_options_contract
23
+ processing/jobs/consume_non_blocking
24
+ processing/strategies/base
25
+ routing/features/base
26
+ encryption
27
+ encryption/cipher
28
+ encryption/setup/config
29
+ encryption/contracts/config
30
+ encryption/messages/parser
33
31
  ].freeze
34
32
 
35
- private_constant :COMPONENTS
33
+ # Zeitwerk pro loader
34
+ # We need to have one per process, that's why it's set as a constant
35
+ PRO_LOADER = Zeitwerk::Loader.new
36
+
37
+ private_constant :PRO_LOADER
36
38
 
37
39
  class << self
40
+ # Requires all the components without using them anywhere
41
+ def require_all
42
+ FORCE_LOADED.each { |file| require_relative(file) }
43
+
44
+ PRO_LOADER.push_dir(Karafka.core_root.join('pro'), namespace: Karafka::Pro)
45
+ PRO_LOADER.setup
46
+ PRO_LOADER.eager_load
47
+ end
48
+
38
49
  # Loads all the pro components and configures them wherever it is expected
39
50
  # @param config [Karafka::Core::Configurable::Node] app config that we can alter with pro
40
51
  # components
41
- def setup(config)
42
- COMPONENTS.each { |component| require_relative(component) }
52
+ def pre_setup(config)
53
+ features.each { |feature| feature.pre_setup(config) }
43
54
 
44
55
  reconfigure(config)
45
56
 
46
- load_routing_extensions
57
+ load_topic_features
58
+ end
59
+
60
+ # Runs post setup features configuration operations
61
+ #
62
+ # @param config [Karafka::Core::Configurable::Node]
63
+ def post_setup(config)
64
+ features.each { |feature| feature.post_setup(config) }
47
65
  end
48
66
 
49
67
  private
50
68
 
69
+ # @return [Array<Module>] extra non-routing related pro features
70
+ def features
71
+ [
72
+ Encryption
73
+ ]
74
+ end
75
+
51
76
  # Sets proper config options to use pro components
52
- # @param config [WaterDrop::Configurable::Node] root config node
77
+ # @param config [::Karafka::Core::Configurable::Node] root config node
53
78
  def reconfigure(config)
54
79
  icfg = config.internal
55
80
 
@@ -57,6 +82,7 @@ module Karafka
57
82
  icfg.processing.partitioner_class = Processing::Partitioner
58
83
  icfg.processing.scheduler = Processing::Scheduler.new
59
84
  icfg.processing.jobs_builder = Processing::JobsBuilder.new
85
+ icfg.processing.strategy_selector = Processing::StrategySelector.new
60
86
 
61
87
  icfg.active_job.consumer_class = ActiveJob::Consumer
62
88
  icfg.active_job.dispatcher = ActiveJob::Dispatcher.new
@@ -65,10 +91,10 @@ module Karafka
65
91
  config.monitor.subscribe(PerformanceTracker.instance)
66
92
  end
67
93
 
68
- # Loads routing extensions
69
- def load_routing_extensions
70
- ::Karafka::Routing::Topic.include(Routing::TopicExtensions)
71
- ::Karafka::Routing::Builder.prepend(Routing::BuilderExtensions)
94
+ # Loads the Pro features of Karafka
95
+ # @note Object space lookup is not the fastest but we do it once during boot, so it's ok
96
+ def load_topic_features
97
+ ::Karafka::Pro::Routing::Features::Base.load_all
72
98
  end
73
99
  end
74
100
  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
  #
@@ -23,11 +25,13 @@ module Karafka
23
25
 
24
26
  # Builds up nested concurrent hash for data tracking
25
27
  def initialize
26
- @processing_times = Concurrent::Hash.new do |topics_hash, topic|
27
- topics_hash[topic] = Concurrent::Hash.new do |partitions_hash, partition|
28
- # This array does not have to be concurrent because we always access single partition
29
- # data via instrumentation that operates in a single thread via consumer
30
- partitions_hash[partition] = []
28
+ @processing_times = Concurrent::Map.new do |topics_hash, topic|
29
+ topics_hash.compute_if_absent(topic) do
30
+ Concurrent::Map.new do |partitions_hash, partition|
31
+ # This array does not have to be concurrent because we always access single
32
+ # partition data via instrumentation that operates in a single thread via consumer
33
+ partitions_hash.compute_if_absent(partition) { [] }
34
+ end
31
35
  end
32
36
  end
33
37
  end
@@ -0,0 +1,62 @@
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 Processing
17
+ # Manages the collapse of virtual partitions
18
+ # Since any non-virtual partition is actually a virtual partition of size one, we can use
19
+ # it in a generic manner without having to distinguish between those cases.
20
+ #
21
+ # We need to have notion of the offset until we want to collapse because upon pause and retry
22
+ # rdkafka may purge the buffer. This means, that we may end up with smaller or bigger
23
+ # (different) dataset and without tracking the end of collapse, there would be a chance for
24
+ # things to flicker. Tracking allows us to ensure, that collapse is happening until all the
25
+ # messages from the corrupted batch are processed.
26
+ class Collapser
27
+ # When initialized, nothing is collapsed
28
+ def initialize
29
+ @collapsed = false
30
+ @until_offset = -1
31
+ @mutex = Mutex.new
32
+ end
33
+
34
+ # @return [Boolean] Should we collapse into a single consumer
35
+ def collapsed?
36
+ @collapsed
37
+ end
38
+
39
+ # Collapse until given offset. Until given offset is encountered or offset bigger than that
40
+ # we keep collapsing.
41
+ # @param offset [Integer] offset until which we keep the collapse
42
+ def collapse_until!(offset)
43
+ @mutex.synchronize do
44
+ # We check it here in case after a pause and re-fetch we would get less messages and
45
+ # one of them would cause an error. We do not want to overwrite the offset here unless
46
+ # it is bigger.
47
+ @until_offset = offset if offset > @until_offset
48
+ end
49
+ end
50
+
51
+ # Sets the collapse state based on the first collective offset that we are going to process
52
+ # and makes the decision whether or not we need to still keep the collapse.
53
+ # @param first_offset [Integer] first offset from a collective batch
54
+ def refresh!(first_offset)
55
+ @mutex.synchronize do
56
+ @collapsed = first_offset < @until_offset
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end