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
@@ -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
  #
@@ -15,12 +17,30 @@ module Karafka
15
17
  # Pro coordinator that provides extra orchestration methods useful for parallel processing
16
18
  # within the same partition
17
19
  class Coordinator < ::Karafka::Processing::Coordinator
20
+ attr_reader :filter, :virtual_offset_manager
21
+
18
22
  # @param args [Object] anything the base coordinator accepts
19
23
  def initialize(*args)
20
24
  super
21
- @on_started_invoked = false
22
- @on_finished_invoked = false
25
+
26
+ @executed = []
23
27
  @flow_lock = Mutex.new
28
+ @collapser = Collapser.new
29
+ @filter = FiltersApplier.new(self)
30
+
31
+ return unless topic.virtual_partitions?
32
+
33
+ @virtual_offset_manager = VirtualOffsetManager.new(
34
+ topic.name,
35
+ partition
36
+ )
37
+
38
+ # We register our own "internal" filter to support filtering of messages that were marked
39
+ # as consumed virtually
40
+ @filter.filters << Filters::VirtualLimiter.new(
41
+ @virtual_offset_manager,
42
+ @collapser
43
+ )
24
44
  end
25
45
 
26
46
  # Starts the coordination process
@@ -29,12 +49,37 @@ module Karafka
29
49
  def start(messages)
30
50
  super
31
51
 
32
- @mutex.synchronize do
33
- @on_started_invoked = false
34
- @on_finished_invoked = false
35
- @first_message = messages.first
36
- @last_message = messages.last
37
- end
52
+ @collapser.refresh!(messages.first.offset)
53
+
54
+ @filter.apply!(messages)
55
+
56
+ @executed.clear
57
+
58
+ # We keep the old processed offsets until the collapsing is done and regular processing
59
+ # with virtualization is restored
60
+ @virtual_offset_manager.clear if topic.virtual_partitions? && !@collapser.collapsed?
61
+
62
+ @last_message = messages.last
63
+ end
64
+
65
+ # Sets the consumer failure status and additionally starts the collapse until
66
+ #
67
+ # @param consumer [Karafka::BaseConsumer] consumer that failed
68
+ # @param error [StandardError] error from the failure
69
+ def failure!(consumer, error)
70
+ super
71
+ @collapser.collapse_until!(@last_message.offset + 1)
72
+ end
73
+
74
+ # @return [Boolean] are we in a collapsed state at the moment
75
+ def collapsed?
76
+ @collapser.collapsed?
77
+ end
78
+
79
+ # @return [Boolean] did any of the filters apply any logic that would cause use to run
80
+ # the filtering flow
81
+ def filtered?
82
+ @filter.applied?
38
83
  end
39
84
 
40
85
  # @return [Boolean] is the coordinated work finished or not
@@ -42,14 +87,22 @@ module Karafka
42
87
  @running_jobs.zero?
43
88
  end
44
89
 
90
+ # Runs synchronized code once for a collective of virtual partitions prior to work being
91
+ # enqueued
92
+ def on_enqueued
93
+ @flow_lock.synchronize do
94
+ return unless executable?(:on_enqueued)
95
+
96
+ yield(@last_message)
97
+ end
98
+ end
99
+
45
100
  # Runs given code only once per all the coordinated jobs upon starting first of them
46
101
  def on_started
47
102
  @flow_lock.synchronize do
48
- return if @on_started_invoked
103
+ return unless executable?(:on_started)
49
104
 
50
- @on_started_invoked = true
51
-
52
- yield(@first_message, @last_message)
105
+ yield(@last_message)
53
106
  end
54
107
  end
55
108
 
@@ -59,13 +112,36 @@ module Karafka
59
112
  def on_finished
60
113
  @flow_lock.synchronize do
61
114
  return unless finished?
62
- return if @on_finished_invoked
115
+ return unless executable?(:on_finished)
116
+
117
+ yield(@last_message)
118
+ end
119
+ end
63
120
 
64
- @on_finished_invoked = true
121
+ # Runs once after a partition is revoked
122
+ def on_revoked
123
+ @flow_lock.synchronize do
124
+ return unless executable?(:on_revoked)
65
125
 
66
- yield(@first_message, @last_message)
126
+ yield(@last_message)
67
127
  end
68
128
  end
129
+
130
+ private
131
+
132
+ # Checks if given action is executable once. If it is and true is returned, this method
133
+ # will return false next time it is used.
134
+ #
135
+ # @param action [Symbol] what action we want to perform
136
+ # @return [Boolean] true if we can
137
+ # @note This method needs to run behind a mutex.
138
+ def executable?(action)
139
+ return false if @executed.include?(action)
140
+
141
+ @executed << action
142
+
143
+ true
144
+ end
69
145
  end
70
146
  end
71
147
  end
@@ -0,0 +1,61 @@
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
+ module Filters
18
+ # Base for all the filters.
19
+ # All filters (including custom) need to use this API.
20
+ #
21
+ # Due to the fact, that filters can limit data in such a way, that we need to pause or
22
+ # seek (throttling for example), the api is not just "remove some things from batch" but
23
+ # also provides ways to control the post-filtering operations that may be needed.
24
+ class Base
25
+ # @return [Karafka::Messages::Message, nil] the message that we want to use as a cursor
26
+ # one to pause or seek or nil if not applicable.
27
+ attr_reader :cursor
28
+
29
+ include Karafka::Core::Helpers::Time
30
+
31
+ def initialize
32
+ @applied = false
33
+ @cursor = nil
34
+ end
35
+
36
+ # @param messages [Array<Karafka::Messages::Message>] array with messages. Please keep
37
+ # in mind, this may already be partial due to execution of previous filters.
38
+ def apply!(messages)
39
+ raise NotImplementedError, 'Implement in a subclass'
40
+ end
41
+
42
+ # @return [Symbol] filter post-execution action on consumer. Either `:skip`, `:pause` or
43
+ # `:seek`.
44
+ def action
45
+ :skip
46
+ end
47
+
48
+ # @return [Boolean] did this filter change messages in any way
49
+ def applied?
50
+ @applied
51
+ end
52
+
53
+ # @return [Integer] default timeout for pausing (if applicable)
54
+ def timeout
55
+ 0
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,70 @@
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
+ module Filters
18
+ # A filter that allows us to delay processing by pausing until time is right.
19
+ class Delayer < Base
20
+ # @param delay [Integer] ms delay / minimum age of each message we want to process
21
+ def initialize(delay)
22
+ super()
23
+
24
+ @delay = delay
25
+ end
26
+
27
+ # Removes too old messages
28
+ #
29
+ # @param messages [Array<Karafka::Messages::Message>]
30
+ def apply!(messages)
31
+ @applied = false
32
+ @cursor = nil
33
+
34
+ # Time on message is in seconds with ms precision, so we need to convert the ttl that
35
+ # is in ms to this format
36
+ border = ::Time.now.utc - @delay / 1_000.to_f
37
+
38
+ messages.delete_if do |message|
39
+ too_young = message.timestamp > border
40
+
41
+ if too_young
42
+ @applied = true
43
+
44
+ @cursor ||= message
45
+ end
46
+
47
+ @applied
48
+ end
49
+ end
50
+
51
+ # @return [Integer] timeout delay in ms
52
+ def timeout
53
+ return 0 unless @cursor
54
+
55
+ timeout = (@delay / 1_000.to_f) - (::Time.now.utc - @cursor.timestamp)
56
+
57
+ timeout <= 0 ? 0 : timeout * 1_000
58
+ end
59
+
60
+ # @return [Symbol] action to take on post-filtering
61
+ def action
62
+ return :skip unless applied?
63
+
64
+ timeout <= 0 ? :seek : :pause
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,51 @@
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
+ module Filters
18
+ # Expirer for removing too old messages.
19
+ # It never moves offsets in any way and does not impact the processing flow. It always
20
+ # runs `:skip` action.
21
+ class Expirer < Base
22
+ # @param ttl [Integer] maximum age of a message (in ms)
23
+ def initialize(ttl)
24
+ super()
25
+
26
+ @ttl = ttl
27
+ end
28
+
29
+ # Removes too old messages
30
+ #
31
+ # @param messages [Array<Karafka::Messages::Message>]
32
+ def apply!(messages)
33
+ @applied = false
34
+
35
+ # Time on message is in seconds with ms precision, so we need to convert the ttl that
36
+ # is in ms to this format
37
+ border = ::Time.now.utc - @ttl / 1_000.to_f
38
+
39
+ messages.delete_if do |message|
40
+ too_old = message.timestamp < border
41
+
42
+ @applied = true if too_old
43
+
44
+ too_old
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,84 @@
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
+ # Namespace containing Pro out of the box filters used by various strategies
18
+ module Filters
19
+ # Throttler used to limit number of messages we can process in a given time interval
20
+ # The tricky thing is, that even if we throttle on 100 messages, if we've reached 100, we
21
+ # still need to indicate, that we throttle despite not receiving 101. Otherwise we will
22
+ # not pause the partition and will fetch more data that we should not process.
23
+ #
24
+ # This is a special type of a filter that always throttles and makes us wait / seek if
25
+ # anything is applied out.
26
+ class Throttler < Base
27
+ # @param limit [Integer] how many messages we can process in a given time
28
+ # @param interval [Integer] interval in milliseconds for which we want to process
29
+ def initialize(limit, interval)
30
+ super()
31
+
32
+ @limit = limit
33
+ @interval = interval
34
+ @requests = Hash.new { |h, k| h[k] = 0 }
35
+ end
36
+
37
+ # Limits number of messages to a range that we can process (if needed) and keeps track
38
+ # of how many messages we've processed in a given time
39
+ # @param messages [Array<Karafka::Messages::Message>] limits the number of messages to
40
+ # number we can accept in the context of throttling constraints
41
+ def apply!(messages)
42
+ @applied = false
43
+ @cursor = nil
44
+ @time = monotonic_now
45
+ @requests.delete_if { |timestamp, _| timestamp < (@time - @interval) }
46
+ values = @requests.values.sum
47
+ accepted = 0
48
+
49
+ messages.delete_if do |message|
50
+ # +1 because of current
51
+ @applied = (values + accepted + 1) > @limit
52
+
53
+ @cursor = message if @applied && @cursor.nil?
54
+
55
+ next true if @applied
56
+
57
+ accepted += 1
58
+
59
+ false
60
+ end
61
+
62
+ @requests[@time] += accepted
63
+ end
64
+
65
+ # @return [Symbol] action to take upon throttler reaching certain state
66
+ def action
67
+ if applied?
68
+ timeout.zero? ? :seek : :pause
69
+ else
70
+ :skip
71
+ end
72
+ end
73
+
74
+ # @return [Integer] minimum number of milliseconds to wait before getting more messages
75
+ # so we are no longer throttled and so we can process at least one message
76
+ def timeout
77
+ timeout = @interval - (monotonic_now - @time)
78
+ timeout <= 0 ? 0 : timeout
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,52 @@
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
+ module Filters
18
+ # Removes messages that are already marked as consumed in the virtual offset manager
19
+ # This should operate only when using virtual partitions.
20
+ #
21
+ # This cleaner prevents us from duplicated processing of messages that were virtually
22
+ # marked as consumed even if we could not mark them as consumed in Kafka. This allows us
23
+ # to limit reprocessing when errors occur drastically when operating with virtual
24
+ # partitions
25
+ #
26
+ # @note It should be registered only when VPs are used
27
+ class VirtualLimiter < Base
28
+ # @param manager [Processing::VirtualOffsetManager]
29
+ # @param collapser [Processing::Collapser]
30
+ def initialize(manager, collapser)
31
+ @manager = manager
32
+ @collapser = collapser
33
+
34
+ super()
35
+ end
36
+
37
+ # Remove messages that we already marked as virtually consumed. Does nothing if not in
38
+ # the collapsed mode.
39
+ #
40
+ # @param messages [Array<Karafka::Messages::Message>]
41
+ def apply!(messages)
42
+ return unless @collapser.collapsed?
43
+
44
+ marked = @manager.marked
45
+
46
+ messages.delete_if { |message| marked.include?(message.offset) }
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,104 @@
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
+ # Applier for all filters we want to have. Whether related to limiting messages based
18
+ # on the payload or any other things.
19
+ #
20
+ # From the outside world perspective, this encapsulates all the filters.
21
+ # This means that this is the API we expose as a single filter, allowing us to control
22
+ # the filtering via many filters easily.
23
+ class FiltersApplier
24
+ # @return [Array] registered filters array. Useful if we want to inject internal context
25
+ # aware filters.
26
+ attr_reader :filters
27
+
28
+ # @param coordinator [Pro::Coordinator] pro coordinator
29
+ def initialize(coordinator)
30
+ # Builds filters out of their factories
31
+ # We build it that way (providing topic and partition) because there may be a case where
32
+ # someone wants to have a specific logic that is per topic or partition. Like for example
33
+ # a case where there is a cache bypassing revocations for topic partition.
34
+ #
35
+ # We provide full Karafka routing topic here and not the name only, in case the filter
36
+ # would be customized based on other topic settings (like VPs, etc)
37
+ #
38
+ # This setup allows for biggest flexibility also because topic object holds the reference
39
+ # to the subscription group and consumer group
40
+ @filters = coordinator.topic.filtering.factories.map do |factory|
41
+ factory.call(coordinator.topic, coordinator.partition)
42
+ end
43
+ end
44
+
45
+ # @param messages [Array<Karafka::Messages::Message>] array with messages from the
46
+ # partition
47
+ def apply!(messages)
48
+ return unless active?
49
+
50
+ @filters.each { |filter| filter.apply!(messages) }
51
+ end
52
+
53
+ # @return [Boolean] did we filter out any messages during filtering run
54
+ def applied?
55
+ return false unless active?
56
+
57
+ !applied.empty?
58
+ end
59
+
60
+ # @return [Symbol] consumer post-filtering action that should be taken
61
+ def action
62
+ return :skip unless applied?
63
+
64
+ # The highest priority is on a potential backoff from any of the filters because it is
65
+ # the less risky (delay and continue later)
66
+ return :pause if applied.any? { |filter| filter.action == :pause }
67
+
68
+ # If none of the filters wanted to pause, we can check for any that would want to seek
69
+ # and if there is any, we can go with this strategy
70
+ return :seek if applied.any? { |filter| filter.action == :seek }
71
+
72
+ :skip
73
+ end
74
+
75
+ # @return [Integer] minimum timeout we need to pause. This is the minimum for all the
76
+ # filters to satisfy all of them.
77
+ def timeout
78
+ applied.map(&:timeout).compact.min || 0
79
+ end
80
+
81
+ # The first message we do need to get next time we poll. We use the minimum not to jump
82
+ # accidentally by over any.
83
+ # @return [Karafka::Messages::Message, nil] cursor message or nil if none
84
+ def cursor
85
+ return nil unless active?
86
+
87
+ applied.map(&:cursor).compact.min_by(&:offset)
88
+ end
89
+
90
+ private
91
+
92
+ # @return [Boolean] is filtering active
93
+ def active?
94
+ !@filters.empty?
95
+ end
96
+
97
+ # @return [Array<Object>] filters that applied any sort of messages limiting
98
+ def applied
99
+ @filters.select(&:applied?)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ 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
  #
@@ -16,8 +18,7 @@ module Karafka
16
18
  # Pro jobs
17
19
  module Jobs
18
20
  # The main job type in a non-blocking variant.
19
- # This variant works "like" the regular consumption but pauses the partition for as long
20
- # as it is needed until a job is done.
21
+ # This variant works "like" the regular consumption but does not block the queue.
21
22
  #
22
23
  # It can be useful when having long lasting jobs that would exceed `max.poll.interval`
23
24
  # if would block.
@@ -25,8 +26,9 @@ module Karafka
25
26
  # @note It needs to be working with a proper consumer that will handle the partition
26
27
  # management. This layer of the framework knows nothing about Kafka messages consumption.
27
28
  class ConsumeNonBlocking < ::Karafka::Processing::Jobs::Consume
28
- # Releases the blocking lock after it is done with the preparation phase for this job
29
- def before_call
29
+ # Makes this job non-blocking from the start
30
+ # @param args [Array] any arguments accepted by `::Karafka::Processing::Jobs::Consume`
31
+ def initialize(*args)
30
32
  super
31
33
  @non_blocking = true
32
34
  end
@@ -0,0 +1,37 @@
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
+ # Pro components related to processing part of Karafka
17
+ module Processing
18
+ # Pro jobs
19
+ module Jobs
20
+ # The revoked job type in a non-blocking variant.
21
+ # This variant works "like" the regular revoked but does not block the queue.
22
+ #
23
+ # It can be useful when having long lasting jobs that would exceed `max.poll.interval`
24
+ # in scenarios where there are more jobs than threads, without this being async we
25
+ # would potentially stop polling
26
+ class RevokedNonBlocking < ::Karafka::Processing::Jobs::Revoked
27
+ # Makes this job non-blocking from the start
28
+ # @param args [Array] any arguments accepted by `::Karafka::Processing::Jobs::Revoked`
29
+ def initialize(*args)
30
+ super
31
+ @non_blocking = true
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ 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
  #
@@ -14,14 +16,30 @@ module Karafka
14
16
  module Processing
15
17
  # Pro jobs builder that supports lrj
16
18
  class JobsBuilder < ::Karafka::Processing::JobsBuilder
19
+ # @param executor [Karafka::Processing::Executor]
20
+ def idle(executor)
21
+ Karafka::Processing::Jobs::Idle.new(executor)
22
+ end
23
+
17
24
  # @param executor [Karafka::Processing::Executor]
18
25
  # @param messages [Karafka::Messages::Messages] messages batch to be consumed
19
- # @param coordinator [Karafka::Processing::Coordinator]
20
26
  # @return [Karafka::Processing::Jobs::Consume] blocking job
21
27
  # @return [Karafka::Pro::Processing::Jobs::ConsumeNonBlocking] non blocking for lrj
22
- def consume(executor, messages, coordinator)
28
+ def consume(executor, messages)
29
+ if executor.topic.long_running_job?
30
+ Jobs::ConsumeNonBlocking.new(executor, messages)
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ # @param executor [Karafka::Processing::Executor]
37
+ # @return [Karafka::Processing::Jobs::Revoked] revocation job for non LRJ
38
+ # @return [Karafka::Processing::Jobs::RevokedNonBlocking] revocation job that is
39
+ # non-blocking, so when revocation job is scheduled for LRJ it also will not block
40
+ def revoked(executor)
23
41
  if executor.topic.long_running_job?
24
- Jobs::ConsumeNonBlocking.new(executor, messages, coordinator)
42
+ Jobs::RevokedNonBlocking.new(executor)
25
43
  else
26
44
  super
27
45
  end