karafka 1.4.12 → 2.2.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (359) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/FUNDING.yml +1 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +10 -9
  5. data/.github/workflows/ci.yml +169 -31
  6. data/.rspec +4 -0
  7. data/.ruby-version +1 -1
  8. data/CHANGELOG.md +716 -607
  9. data/CONTRIBUTING.md +10 -19
  10. data/Gemfile +7 -0
  11. data/Gemfile.lock +69 -92
  12. data/LICENSE +17 -0
  13. data/LICENSE-COMM +89 -0
  14. data/LICENSE-LGPL +165 -0
  15. data/README.md +48 -47
  16. data/bin/benchmarks +99 -0
  17. data/bin/create_token +22 -0
  18. data/bin/integrations +310 -0
  19. data/bin/karafka +5 -14
  20. data/bin/record_rss +50 -0
  21. data/bin/rspecs +6 -0
  22. data/bin/scenario +29 -0
  23. data/bin/stress_many +13 -0
  24. data/bin/stress_one +13 -0
  25. data/bin/verify_license_integrity +37 -0
  26. data/bin/wait_for_kafka +24 -0
  27. data/certs/cert_chain.pem +26 -0
  28. data/certs/karafka-pro.pem +11 -0
  29. data/config/locales/errors.yml +97 -0
  30. data/config/locales/pro_errors.yml +59 -0
  31. data/docker-compose.yml +19 -11
  32. data/karafka.gemspec +26 -22
  33. data/lib/active_job/karafka.rb +17 -0
  34. data/lib/active_job/queue_adapters/karafka_adapter.rb +32 -0
  35. data/lib/karafka/active_job/consumer.rb +49 -0
  36. data/lib/karafka/active_job/current_attributes/loading.rb +36 -0
  37. data/lib/karafka/active_job/current_attributes/persistence.rb +28 -0
  38. data/lib/karafka/active_job/current_attributes.rb +42 -0
  39. data/lib/karafka/active_job/dispatcher.rb +69 -0
  40. data/lib/karafka/active_job/job_extensions.rb +34 -0
  41. data/lib/karafka/active_job/job_options_contract.rb +32 -0
  42. data/lib/karafka/admin.rb +313 -0
  43. data/lib/karafka/app.rb +47 -23
  44. data/lib/karafka/base_consumer.rb +260 -29
  45. data/lib/karafka/cli/base.rb +67 -36
  46. data/lib/karafka/cli/console.rb +18 -12
  47. data/lib/karafka/cli/help.rb +24 -0
  48. data/lib/karafka/cli/info.rb +47 -12
  49. data/lib/karafka/cli/install.rb +23 -14
  50. data/lib/karafka/cli/server.rb +101 -44
  51. data/lib/karafka/cli/topics.rb +146 -0
  52. data/lib/karafka/cli.rb +24 -27
  53. data/lib/karafka/connection/client.rb +553 -90
  54. data/lib/karafka/connection/consumer_group_coordinator.rb +48 -0
  55. data/lib/karafka/connection/listener.rb +294 -38
  56. data/lib/karafka/connection/listeners_batch.rb +40 -0
  57. data/lib/karafka/connection/messages_buffer.rb +84 -0
  58. data/lib/karafka/connection/pauses_manager.rb +46 -0
  59. data/lib/karafka/connection/proxy.rb +98 -0
  60. data/lib/karafka/connection/raw_messages_buffer.rb +101 -0
  61. data/lib/karafka/connection/rebalance_manager.rb +105 -0
  62. data/lib/karafka/contracts/base.rb +17 -0
  63. data/lib/karafka/contracts/config.rb +130 -11
  64. data/lib/karafka/contracts/consumer_group.rb +32 -187
  65. data/lib/karafka/contracts/server_cli_options.rb +80 -19
  66. data/lib/karafka/contracts/topic.rb +65 -0
  67. data/lib/karafka/contracts.rb +1 -1
  68. data/lib/karafka/embedded.rb +36 -0
  69. data/lib/karafka/env.rb +46 -0
  70. data/lib/karafka/errors.rb +37 -21
  71. data/lib/karafka/helpers/async.rb +33 -0
  72. data/lib/karafka/helpers/colorize.rb +26 -0
  73. data/lib/karafka/helpers/multi_delegator.rb +2 -2
  74. data/lib/karafka/instrumentation/callbacks/error.rb +39 -0
  75. data/lib/karafka/instrumentation/callbacks/rebalance.rb +64 -0
  76. data/lib/karafka/instrumentation/callbacks/statistics.rb +51 -0
  77. data/lib/karafka/instrumentation/logger_listener.rb +303 -0
  78. data/lib/karafka/instrumentation/monitor.rb +13 -61
  79. data/lib/karafka/instrumentation/notifications.rb +79 -0
  80. data/lib/karafka/instrumentation/proctitle_listener.rb +7 -16
  81. data/lib/karafka/instrumentation/vendors/appsignal/base.rb +30 -0
  82. data/lib/karafka/instrumentation/vendors/appsignal/client.rb +122 -0
  83. data/lib/karafka/instrumentation/vendors/appsignal/dashboard.json +222 -0
  84. data/lib/karafka/instrumentation/vendors/appsignal/errors_listener.rb +30 -0
  85. data/lib/karafka/instrumentation/vendors/appsignal/metrics_listener.rb +331 -0
  86. data/lib/karafka/instrumentation/vendors/datadog/dashboard.json +1 -0
  87. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +155 -0
  88. data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +264 -0
  89. data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +176 -0
  90. data/lib/karafka/licenser.rb +78 -0
  91. data/lib/karafka/messages/batch_metadata.rb +52 -0
  92. data/lib/karafka/messages/builders/batch_metadata.rb +60 -0
  93. data/lib/karafka/messages/builders/message.rb +40 -0
  94. data/lib/karafka/messages/builders/messages.rb +36 -0
  95. data/lib/karafka/{params/params.rb → messages/message.rb} +20 -13
  96. data/lib/karafka/messages/messages.rb +71 -0
  97. data/lib/karafka/{params → messages}/metadata.rb +4 -6
  98. data/lib/karafka/messages/parser.rb +14 -0
  99. data/lib/karafka/messages/seek.rb +12 -0
  100. data/lib/karafka/patches/rdkafka/bindings.rb +122 -0
  101. data/lib/karafka/patches/rdkafka/opaque.rb +36 -0
  102. data/lib/karafka/pro/active_job/consumer.rb +47 -0
  103. data/lib/karafka/pro/active_job/dispatcher.rb +86 -0
  104. data/lib/karafka/pro/active_job/job_options_contract.rb +45 -0
  105. data/lib/karafka/pro/cleaner/errors.rb +27 -0
  106. data/lib/karafka/pro/cleaner/messages/message.rb +46 -0
  107. data/lib/karafka/pro/cleaner/messages/messages.rb +42 -0
  108. data/lib/karafka/pro/cleaner.rb +41 -0
  109. data/lib/karafka/pro/contracts/base.rb +23 -0
  110. data/lib/karafka/pro/contracts/server_cli_options.rb +111 -0
  111. data/lib/karafka/pro/encryption/cipher.rb +58 -0
  112. data/lib/karafka/pro/encryption/contracts/config.rb +79 -0
  113. data/lib/karafka/pro/encryption/errors.rb +27 -0
  114. data/lib/karafka/pro/encryption/messages/middleware.rb +46 -0
  115. data/lib/karafka/pro/encryption/messages/parser.rb +56 -0
  116. data/lib/karafka/pro/encryption/setup/config.rb +48 -0
  117. data/lib/karafka/pro/encryption.rb +47 -0
  118. data/lib/karafka/pro/iterator/expander.rb +95 -0
  119. data/lib/karafka/pro/iterator/tpl_builder.rb +155 -0
  120. data/lib/karafka/pro/iterator.rb +170 -0
  121. data/lib/karafka/pro/loader.rb +106 -0
  122. data/lib/karafka/pro/performance_tracker.rb +84 -0
  123. data/lib/karafka/pro/processing/collapser.rb +62 -0
  124. data/lib/karafka/pro/processing/coordinator.rb +147 -0
  125. data/lib/karafka/pro/processing/filters/base.rb +61 -0
  126. data/lib/karafka/pro/processing/filters/delayer.rb +70 -0
  127. data/lib/karafka/pro/processing/filters/expirer.rb +51 -0
  128. data/lib/karafka/pro/processing/filters/inline_insights_delayer.rb +78 -0
  129. data/lib/karafka/pro/processing/filters/throttler.rb +84 -0
  130. data/lib/karafka/pro/processing/filters/virtual_limiter.rb +52 -0
  131. data/lib/karafka/pro/processing/filters_applier.rb +105 -0
  132. data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +39 -0
  133. data/lib/karafka/pro/processing/jobs/revoked_non_blocking.rb +37 -0
  134. data/lib/karafka/pro/processing/jobs_builder.rb +50 -0
  135. data/lib/karafka/pro/processing/partitioner.rb +69 -0
  136. data/lib/karafka/pro/processing/scheduler.rb +75 -0
  137. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +70 -0
  138. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +76 -0
  139. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom.rb +72 -0
  140. data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +76 -0
  141. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +66 -0
  142. data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +70 -0
  143. data/lib/karafka/pro/processing/strategies/aj/dlq_mom.rb +64 -0
  144. data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +69 -0
  145. data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom.rb +38 -0
  146. data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom_vp.rb +66 -0
  147. data/lib/karafka/pro/processing/strategies/aj/ftr_mom.rb +38 -0
  148. data/lib/karafka/pro/processing/strategies/aj/ftr_mom_vp.rb +58 -0
  149. data/lib/karafka/pro/processing/strategies/aj/lrj_mom.rb +37 -0
  150. data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +82 -0
  151. data/lib/karafka/pro/processing/strategies/aj/mom.rb +36 -0
  152. data/lib/karafka/pro/processing/strategies/aj/mom_vp.rb +52 -0
  153. data/lib/karafka/pro/processing/strategies/base.rb +26 -0
  154. data/lib/karafka/pro/processing/strategies/default.rb +105 -0
  155. data/lib/karafka/pro/processing/strategies/dlq/default.rb +137 -0
  156. data/lib/karafka/pro/processing/strategies/dlq/ftr.rb +61 -0
  157. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj.rb +75 -0
  158. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +71 -0
  159. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom_vp.rb +43 -0
  160. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_vp.rb +41 -0
  161. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +69 -0
  162. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom_vp.rb +41 -0
  163. data/lib/karafka/pro/processing/strategies/dlq/ftr_vp.rb +40 -0
  164. data/lib/karafka/pro/processing/strategies/dlq/lrj.rb +64 -0
  165. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +65 -0
  166. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom_vp.rb +36 -0
  167. data/lib/karafka/pro/processing/strategies/dlq/lrj_vp.rb +39 -0
  168. data/lib/karafka/pro/processing/strategies/dlq/mom.rb +68 -0
  169. data/lib/karafka/pro/processing/strategies/dlq/mom_vp.rb +37 -0
  170. data/lib/karafka/pro/processing/strategies/dlq/vp.rb +40 -0
  171. data/lib/karafka/pro/processing/strategies/ftr/default.rb +111 -0
  172. data/lib/karafka/pro/processing/strategies/ftr/vp.rb +40 -0
  173. data/lib/karafka/pro/processing/strategies/lrj/default.rb +85 -0
  174. data/lib/karafka/pro/processing/strategies/lrj/ftr.rb +69 -0
  175. data/lib/karafka/pro/processing/strategies/lrj/ftr_mom.rb +67 -0
  176. data/lib/karafka/pro/processing/strategies/lrj/ftr_mom_vp.rb +40 -0
  177. data/lib/karafka/pro/processing/strategies/lrj/ftr_vp.rb +39 -0
  178. data/lib/karafka/pro/processing/strategies/lrj/mom.rb +77 -0
  179. data/lib/karafka/pro/processing/strategies/lrj/mom_vp.rb +38 -0
  180. data/lib/karafka/pro/processing/strategies/lrj/vp.rb +36 -0
  181. data/lib/karafka/pro/processing/strategies/mom/default.rb +46 -0
  182. data/lib/karafka/pro/processing/strategies/mom/ftr.rb +53 -0
  183. data/lib/karafka/pro/processing/strategies/mom/ftr_vp.rb +37 -0
  184. data/lib/karafka/pro/processing/strategies/mom/vp.rb +35 -0
  185. data/lib/karafka/pro/processing/strategies/vp/default.rb +124 -0
  186. data/lib/karafka/pro/processing/strategies.rb +22 -0
  187. data/lib/karafka/pro/processing/strategy_selector.rb +84 -0
  188. data/lib/karafka/pro/processing/virtual_offset_manager.rb +147 -0
  189. data/lib/karafka/pro/routing/features/active_job/builder.rb +45 -0
  190. data/lib/karafka/pro/routing/features/active_job.rb +26 -0
  191. data/lib/karafka/pro/routing/features/base.rb +24 -0
  192. data/lib/karafka/pro/routing/features/dead_letter_queue/contracts/topic.rb +53 -0
  193. data/lib/karafka/pro/routing/features/dead_letter_queue.rb +27 -0
  194. data/lib/karafka/pro/routing/features/delaying/config.rb +27 -0
  195. data/lib/karafka/pro/routing/features/delaying/contracts/topic.rb +41 -0
  196. data/lib/karafka/pro/routing/features/delaying/topic.rb +59 -0
  197. data/lib/karafka/pro/routing/features/delaying.rb +29 -0
  198. data/lib/karafka/pro/routing/features/expiring/config.rb +27 -0
  199. data/lib/karafka/pro/routing/features/expiring/contracts/topic.rb +41 -0
  200. data/lib/karafka/pro/routing/features/expiring/topic.rb +59 -0
  201. data/lib/karafka/pro/routing/features/expiring.rb +27 -0
  202. data/lib/karafka/pro/routing/features/filtering/config.rb +40 -0
  203. data/lib/karafka/pro/routing/features/filtering/contracts/topic.rb +44 -0
  204. data/lib/karafka/pro/routing/features/filtering/topic.rb +51 -0
  205. data/lib/karafka/pro/routing/features/filtering.rb +27 -0
  206. data/lib/karafka/pro/routing/features/inline_insights/config.rb +32 -0
  207. data/lib/karafka/pro/routing/features/inline_insights/contracts/topic.rb +41 -0
  208. data/lib/karafka/pro/routing/features/inline_insights/topic.rb +52 -0
  209. data/lib/karafka/pro/routing/features/inline_insights.rb +26 -0
  210. data/lib/karafka/pro/routing/features/long_running_job/config.rb +28 -0
  211. data/lib/karafka/pro/routing/features/long_running_job/contracts/topic.rb +40 -0
  212. data/lib/karafka/pro/routing/features/long_running_job/topic.rb +42 -0
  213. data/lib/karafka/pro/routing/features/long_running_job.rb +28 -0
  214. data/lib/karafka/pro/routing/features/patterns/builder.rb +38 -0
  215. data/lib/karafka/pro/routing/features/patterns/config.rb +54 -0
  216. data/lib/karafka/pro/routing/features/patterns/consumer_group.rb +72 -0
  217. data/lib/karafka/pro/routing/features/patterns/contracts/consumer_group.rb +62 -0
  218. data/lib/karafka/pro/routing/features/patterns/contracts/pattern.rb +46 -0
  219. data/lib/karafka/pro/routing/features/patterns/contracts/topic.rb +41 -0
  220. data/lib/karafka/pro/routing/features/patterns/detector.rb +71 -0
  221. data/lib/karafka/pro/routing/features/patterns/pattern.rb +95 -0
  222. data/lib/karafka/pro/routing/features/patterns/patterns.rb +35 -0
  223. data/lib/karafka/pro/routing/features/patterns/topic.rb +50 -0
  224. data/lib/karafka/pro/routing/features/patterns/topics.rb +53 -0
  225. data/lib/karafka/pro/routing/features/patterns.rb +33 -0
  226. data/lib/karafka/pro/routing/features/pausing/contracts/topic.rb +51 -0
  227. data/lib/karafka/pro/routing/features/pausing/topic.rb +44 -0
  228. data/lib/karafka/pro/routing/features/pausing.rb +25 -0
  229. data/lib/karafka/pro/routing/features/throttling/config.rb +32 -0
  230. data/lib/karafka/pro/routing/features/throttling/contracts/topic.rb +44 -0
  231. data/lib/karafka/pro/routing/features/throttling/topic.rb +69 -0
  232. data/lib/karafka/pro/routing/features/throttling.rb +30 -0
  233. data/lib/karafka/pro/routing/features/virtual_partitions/config.rb +30 -0
  234. data/lib/karafka/pro/routing/features/virtual_partitions/contracts/topic.rb +55 -0
  235. data/lib/karafka/pro/routing/features/virtual_partitions/topic.rb +56 -0
  236. data/lib/karafka/pro/routing/features/virtual_partitions.rb +27 -0
  237. data/lib/karafka/pro.rb +13 -0
  238. data/lib/karafka/process.rb +24 -8
  239. data/lib/karafka/processing/coordinator.rb +181 -0
  240. data/lib/karafka/processing/coordinators_buffer.rb +62 -0
  241. data/lib/karafka/processing/executor.rb +155 -0
  242. data/lib/karafka/processing/executors_buffer.rb +72 -0
  243. data/lib/karafka/processing/expansions_selector.rb +22 -0
  244. data/lib/karafka/processing/inline_insights/consumer.rb +41 -0
  245. data/lib/karafka/processing/inline_insights/listener.rb +19 -0
  246. data/lib/karafka/processing/inline_insights/tracker.rb +128 -0
  247. data/lib/karafka/processing/jobs/base.rb +55 -0
  248. data/lib/karafka/processing/jobs/consume.rb +45 -0
  249. data/lib/karafka/processing/jobs/idle.rb +24 -0
  250. data/lib/karafka/processing/jobs/revoked.rb +22 -0
  251. data/lib/karafka/processing/jobs/shutdown.rb +23 -0
  252. data/lib/karafka/processing/jobs_builder.rb +28 -0
  253. data/lib/karafka/processing/jobs_queue.rb +150 -0
  254. data/lib/karafka/processing/partitioner.rb +24 -0
  255. data/lib/karafka/processing/result.rb +42 -0
  256. data/lib/karafka/processing/scheduler.rb +22 -0
  257. data/lib/karafka/processing/strategies/aj_dlq_mom.rb +44 -0
  258. data/lib/karafka/processing/strategies/aj_mom.rb +21 -0
  259. data/lib/karafka/processing/strategies/base.rb +52 -0
  260. data/lib/karafka/processing/strategies/default.rb +158 -0
  261. data/lib/karafka/processing/strategies/dlq.rb +88 -0
  262. data/lib/karafka/processing/strategies/dlq_mom.rb +49 -0
  263. data/lib/karafka/processing/strategies/mom.rb +29 -0
  264. data/lib/karafka/processing/strategy_selector.rb +47 -0
  265. data/lib/karafka/processing/worker.rb +93 -0
  266. data/lib/karafka/processing/workers_batch.rb +27 -0
  267. data/lib/karafka/railtie.rb +141 -0
  268. data/lib/karafka/routing/activity_manager.rb +84 -0
  269. data/lib/karafka/routing/builder.rb +45 -19
  270. data/lib/karafka/routing/consumer_group.rb +56 -20
  271. data/lib/karafka/routing/consumer_mapper.rb +1 -12
  272. data/lib/karafka/routing/features/active_job/builder.rb +33 -0
  273. data/lib/karafka/routing/features/active_job/config.rb +15 -0
  274. data/lib/karafka/routing/features/active_job/contracts/topic.rb +44 -0
  275. data/lib/karafka/routing/features/active_job/proxy.rb +14 -0
  276. data/lib/karafka/routing/features/active_job/topic.rb +33 -0
  277. data/lib/karafka/routing/features/active_job.rb +13 -0
  278. data/lib/karafka/routing/features/base/expander.rb +59 -0
  279. data/lib/karafka/routing/features/base.rb +71 -0
  280. data/lib/karafka/routing/features/dead_letter_queue/config.rb +19 -0
  281. data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +46 -0
  282. data/lib/karafka/routing/features/dead_letter_queue/topic.rb +41 -0
  283. data/lib/karafka/routing/features/dead_letter_queue.rb +16 -0
  284. data/lib/karafka/routing/features/declaratives/config.rb +18 -0
  285. data/lib/karafka/routing/features/declaratives/contracts/topic.rb +33 -0
  286. data/lib/karafka/routing/features/declaratives/topic.rb +44 -0
  287. data/lib/karafka/routing/features/declaratives.rb +14 -0
  288. data/lib/karafka/routing/features/inline_insights/config.rb +15 -0
  289. data/lib/karafka/routing/features/inline_insights/contracts/topic.rb +27 -0
  290. data/lib/karafka/routing/features/inline_insights/topic.rb +31 -0
  291. data/lib/karafka/routing/features/inline_insights.rb +40 -0
  292. data/lib/karafka/routing/features/manual_offset_management/config.rb +15 -0
  293. data/lib/karafka/routing/features/manual_offset_management/contracts/topic.rb +27 -0
  294. data/lib/karafka/routing/features/manual_offset_management/topic.rb +35 -0
  295. data/lib/karafka/routing/features/manual_offset_management.rb +18 -0
  296. data/lib/karafka/routing/proxy.rb +22 -21
  297. data/lib/karafka/routing/router.rb +24 -10
  298. data/lib/karafka/routing/subscription_group.rb +110 -0
  299. data/lib/karafka/routing/subscription_groups_builder.rb +65 -0
  300. data/lib/karafka/routing/topic.rb +87 -24
  301. data/lib/karafka/routing/topics.rb +46 -0
  302. data/lib/karafka/runner.rb +52 -0
  303. data/lib/karafka/serialization/json/deserializer.rb +7 -15
  304. data/lib/karafka/server.rb +113 -37
  305. data/lib/karafka/setup/attributes_map.rb +348 -0
  306. data/lib/karafka/setup/config.rb +256 -175
  307. data/lib/karafka/status.rb +54 -7
  308. data/lib/karafka/templates/example_consumer.rb.erb +16 -0
  309. data/lib/karafka/templates/karafka.rb.erb +33 -55
  310. data/lib/karafka/time_trackers/base.rb +14 -0
  311. data/lib/karafka/time_trackers/pause.rb +122 -0
  312. data/lib/karafka/time_trackers/poll.rb +69 -0
  313. data/lib/karafka/version.rb +1 -1
  314. data/lib/karafka.rb +91 -17
  315. data/renovate.json +9 -0
  316. data.tar.gz.sig +0 -0
  317. metadata +330 -168
  318. metadata.gz.sig +0 -0
  319. data/MIT-LICENCE +0 -18
  320. data/certs/mensfeld.pem +0 -25
  321. data/config/errors.yml +0 -41
  322. data/lib/karafka/assignment_strategies/round_robin.rb +0 -13
  323. data/lib/karafka/attributes_map.rb +0 -63
  324. data/lib/karafka/backends/inline.rb +0 -16
  325. data/lib/karafka/base_responder.rb +0 -226
  326. data/lib/karafka/cli/flow.rb +0 -48
  327. data/lib/karafka/cli/missingno.rb +0 -19
  328. data/lib/karafka/code_reloader.rb +0 -67
  329. data/lib/karafka/connection/api_adapter.rb +0 -158
  330. data/lib/karafka/connection/batch_delegator.rb +0 -55
  331. data/lib/karafka/connection/builder.rb +0 -23
  332. data/lib/karafka/connection/message_delegator.rb +0 -36
  333. data/lib/karafka/consumers/batch_metadata.rb +0 -10
  334. data/lib/karafka/consumers/callbacks.rb +0 -71
  335. data/lib/karafka/consumers/includer.rb +0 -64
  336. data/lib/karafka/consumers/responders.rb +0 -24
  337. data/lib/karafka/consumers/single_params.rb +0 -15
  338. data/lib/karafka/contracts/consumer_group_topic.rb +0 -19
  339. data/lib/karafka/contracts/responder_usage.rb +0 -54
  340. data/lib/karafka/fetcher.rb +0 -42
  341. data/lib/karafka/helpers/class_matcher.rb +0 -88
  342. data/lib/karafka/helpers/config_retriever.rb +0 -46
  343. data/lib/karafka/helpers/inflector.rb +0 -26
  344. data/lib/karafka/instrumentation/stdout_listener.rb +0 -140
  345. data/lib/karafka/params/batch_metadata.rb +0 -26
  346. data/lib/karafka/params/builders/batch_metadata.rb +0 -30
  347. data/lib/karafka/params/builders/params.rb +0 -38
  348. data/lib/karafka/params/builders/params_batch.rb +0 -25
  349. data/lib/karafka/params/params_batch.rb +0 -60
  350. data/lib/karafka/patches/ruby_kafka.rb +0 -47
  351. data/lib/karafka/persistence/client.rb +0 -29
  352. data/lib/karafka/persistence/consumers.rb +0 -45
  353. data/lib/karafka/persistence/topics.rb +0 -48
  354. data/lib/karafka/responders/builder.rb +0 -36
  355. data/lib/karafka/responders/topic.rb +0 -55
  356. data/lib/karafka/routing/topic_mapper.rb +0 -53
  357. data/lib/karafka/serialization/json/serializer.rb +0 -31
  358. data/lib/karafka/setup/configurators/water_drop.rb +0 -36
  359. data/lib/karafka/templates/application_responder.rb.erb +0 -11
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Connection
5
+ # This object represents a collective status of execution of group of listeners running inside
6
+ # of one consumer group but in separate subscription groups.
7
+ #
8
+ # There are cases when we do not want to close a given client when others from the same
9
+ # consumer group are running because it can cause instabilities due to early shutdown of some
10
+ # of the clients out of same consumer group.
11
+ #
12
+ # We also want to make sure, we close one consumer at a time while others can continue polling.
13
+ #
14
+ # This prevents a scenario, where a rebalance is not acknowledged and we loose assignment
15
+ # without having a chance to commit changes.
16
+ class ConsumerGroupCoordinator
17
+ # @param group_size [Integer] number of separate subscription groups in a consumer group
18
+ def initialize(group_size)
19
+ @shutdown_lock = Mutex.new
20
+ @group_size = group_size
21
+ @finished = Set.new
22
+ end
23
+
24
+ # @return [Boolean] true if all the subscription groups from a given consumer group are
25
+ # finished
26
+ def finished?
27
+ @finished.size == @group_size
28
+ end
29
+
30
+ # @return [Boolean] can we start shutdown on a given listener
31
+ # @note If true, will also obtain a lock so no-one else will be closing the same time we do
32
+ def shutdown?
33
+ finished? && @shutdown_lock.try_lock
34
+ end
35
+
36
+ # Unlocks the shutdown lock
37
+ def unlock
38
+ @shutdown_lock.unlock if @shutdown_lock.owned?
39
+ end
40
+
41
+ # Marks given listener as finished
42
+ # @param listener_id [String]
43
+ def finish_work(listener_id)
44
+ @finished << listener_id
45
+ end
46
+ end
47
+ end
48
+ end
@@ -2,69 +2,325 @@
2
2
 
3
3
  module Karafka
4
4
  module Connection
5
- # A single listener that listens to incoming messages from a single route
6
- # @note It does not loop on itself - it needs to be executed in a loop
7
- # @note Listener itself does nothing with the message - it will return to the block
8
- # a raw Kafka::FetchedMessage
5
+ # A single listener that listens to incoming messages from a single subscription group.
6
+ # It polls the messages and then enqueues jobs. It also takes care of potential recovery from
7
+ # critical errors by restarting everything in a safe manner.
8
+ #
9
+ # This is the heart of the consumption process.
9
10
  class Listener
10
- # @param consumer_group [Karafka::Routing::ConsumerGroup] consumer group that holds details
11
- # on what topics and with what settings should we listen
11
+ include Helpers::Async
12
+
13
+ # Can be useful for logging
14
+ # @return [String] id of this listener
15
+ attr_reader :id
16
+
17
+ # @param consumer_group_coordinator [Karafka::Connection::ConsumerGroupCoordinator]
18
+ # @param subscription_group [Karafka::Routing::SubscriptionGroup]
19
+ # @param jobs_queue [Karafka::Processing::JobsQueue] queue where we should push work
12
20
  # @return [Karafka::Connection::Listener] listener instance
13
- def initialize(consumer_group)
14
- @consumer_group = consumer_group
21
+ def initialize(consumer_group_coordinator, subscription_group, jobs_queue)
22
+ proc_config = ::Karafka::App.config.internal.processing
23
+
24
+ @id = SecureRandom.hex(6)
25
+ @consumer_group_coordinator = consumer_group_coordinator
26
+ @subscription_group = subscription_group
27
+ @jobs_queue = jobs_queue
28
+ @coordinators = Processing::CoordinatorsBuffer.new(subscription_group.topics)
29
+ @client = Client.new(@subscription_group)
30
+ @executors = Processing::ExecutorsBuffer.new(@client, subscription_group)
31
+ @jobs_builder = proc_config.jobs_builder
32
+ @partitioner = proc_config.partitioner_class.new(subscription_group)
33
+ # We reference scheduler here as it is much faster than fetching this each time
34
+ @scheduler = proc_config.scheduler
35
+ # We keep one buffer for messages to preserve memory and not allocate extra objects
36
+ # We can do this that way because we always first schedule jobs using messages before we
37
+ # fetch another batch.
38
+ @messages_buffer = MessagesBuffer.new(subscription_group)
39
+ @mutex = Mutex.new
40
+ @stopped = false
15
41
  end
16
42
 
17
- # Runs prefetch callbacks and executes the main listener fetch loop
43
+ # Runs the main listener fetch loop.
44
+ #
45
+ # @note Prefetch callbacks can be used to seek offset or do other things before we actually
46
+ # start consuming data
18
47
  def call
19
48
  Karafka.monitor.instrument(
20
49
  'connection.listener.before_fetch_loop',
21
- consumer_group: @consumer_group,
22
- client: client
50
+ caller: self,
51
+ client: @client,
52
+ subscription_group: @subscription_group
23
53
  )
54
+
24
55
  fetch_loop
25
56
  end
26
57
 
58
+ # Stops the jobs queue, triggers shutdown on all the executors (sync), commits offsets and
59
+ # stops kafka client.
60
+ #
61
+ # @note This method is not private despite being part of the fetch loop because in case of
62
+ # a forceful shutdown, it may be invoked from a separate thread
63
+ #
64
+ # @note We wrap it with a mutex exactly because of the above case of forceful shutdown
65
+ def shutdown
66
+ return if @stopped
67
+
68
+ @mutex.synchronize do
69
+ @stopped = true
70
+ @executors.clear
71
+ @coordinators.reset
72
+ @client.stop
73
+ end
74
+ end
75
+
27
76
  private
28
77
 
29
- # Opens connection, gets messages and calls a block for each of the incoming messages
78
+ # Fetches the data and adds it to the jobs queue.
79
+ #
30
80
  # @note We catch all the errors here, so they don't affect other listeners (or this one)
31
81
  # so we will be able to listen and consume other incoming messages.
32
- # Since it is run inside Karafka::Connection::ActorCluster - catching all the exceptions
33
- # won't crash the whole cluster. Here we mostly focus on catching the exceptions related to
82
+ # Since it is run inside Karafka::Connection::Runner thread - catching all the exceptions
83
+ # won't crash the whole process. Here we mostly focus on catching the exceptions related to
34
84
  # Kafka connections / Internet connection issues / Etc. Business logic problems should not
35
- # propagate this far
85
+ # propagate this far.
36
86
  def fetch_loop
37
- # @note What happens here is a delegation of processing to a proper processor based
38
- # on the incoming messages characteristics
39
- client.fetch_loop do |raw_data, type|
40
- Karafka.monitor.instrument('connection.listener.fetch_loop')
41
-
42
- case type
43
- when :message
44
- MessageDelegator.call(@consumer_group.id, raw_data)
45
- when :batch
46
- BatchDelegator.call(@consumer_group.id, raw_data)
87
+ # Run the main loop as long as we are not stopping or moving into quiet mode
88
+ until Karafka::App.done?
89
+ Karafka.monitor.instrument(
90
+ 'connection.listener.fetch_loop',
91
+ caller: self,
92
+ client: @client,
93
+ subscription_group: @subscription_group
94
+ )
95
+
96
+ resume_paused_partitions
97
+
98
+ Karafka.monitor.instrument(
99
+ 'connection.listener.fetch_loop.received',
100
+ caller: self,
101
+ client: @client,
102
+ subscription_group: @subscription_group,
103
+ messages_buffer: @messages_buffer
104
+ ) do
105
+ # We need to fetch data before we revoke lost partitions details as during the polling
106
+ # the callbacks for tracking lost partitions are triggered. Otherwise we would be
107
+ # always one batch behind.
108
+ poll_and_remap_messages
47
109
  end
110
+
111
+ # If there were revoked partitions, we need to wait on their jobs to finish before
112
+ # distributing consuming jobs as upon revoking, we might get assigned to the same
113
+ # partitions, thus getting their jobs. The revoking jobs need to finish before
114
+ # appropriate consumers are taken down and re-created
115
+ build_and_schedule_revoked_jobs_for_revoked_partitions
116
+
117
+ # We wait only on jobs from our subscription group. Other groups are independent.
118
+ # This will block on revoked jobs until they are finished. Those are not meant to last
119
+ # long and should not have any bigger impact on the system. Doing this in a blocking way
120
+ # simplifies the overall design and prevents from race conditions
121
+ wait
122
+
123
+ build_and_schedule_consumption_jobs
124
+
125
+ wait
48
126
  end
127
+
128
+ # If we are stopping we will no longer schedule any regular jobs despite polling.
129
+ # We need to keep polling not to exceed the `max.poll.interval` for long-running
130
+ # non-blocking jobs and we need to allow them to finish. We however do not want to
131
+ # enqueue any new jobs. It's worth keeping in mind that it is the end user responsibility
132
+ # to detect shutdown in their long-running logic or else Karafka will force shutdown
133
+ # after a while.
134
+ #
135
+ # We do not care about resuming any partitions or lost jobs as we do not plan to do
136
+ # anything with them as we're in the shutdown phase.
137
+ #
138
+ # What we do care however is the ability to still run revocation jobs in case anything
139
+ # would change in the cluster. We still want to notify the long-running jobs about changes
140
+ # that occurred in the cluster.
141
+ wait_pinging(
142
+ wait_until: -> { @jobs_queue.empty?(@subscription_group.id) },
143
+ after_ping: -> { build_and_schedule_revoked_jobs_for_revoked_partitions }
144
+ )
145
+
146
+ # We do not want to schedule the shutdown jobs prior to finishing all the jobs
147
+ # (including non-blocking) as there might be a long-running job with a shutdown and then
148
+ # we would run two jobs in parallel for the same executor and consumer. We do not want that
149
+ # as it could create a race-condition.
150
+ build_and_schedule_shutdown_jobs
151
+
152
+ # Wait until all the shutdown jobs are done
153
+ wait_pinging(wait_until: -> { @jobs_queue.empty?(@subscription_group.id) })
154
+
155
+ # Once all the work is done, we need to decrement counter of active subscription groups
156
+ # within this consumer group
157
+ @consumer_group_coordinator.finish_work(id)
158
+
159
+ # Wait if we're in the process of finishing started work or finished all the work and
160
+ # just sitting and being quiet
161
+ wait_pinging(wait_until: -> { !(Karafka::App.quieting? || Karafka::App.quiet?) })
162
+
163
+ # We need to wait until all the work in the whole consumer group (local to the process)
164
+ # is done. Otherwise we may end up with locks and `Timed out LeaveGroupRequest in flight`
165
+ # warning notifications.
166
+ wait_pinging(wait_until: -> { @consumer_group_coordinator.shutdown? })
167
+
168
+ # This extra ping will make sure we've refreshed the rebalance state after other instances
169
+ # potentially shutdown. This will prevent us from closing with a dangling callback
170
+ @client.ping
171
+
172
+ shutdown
173
+
49
174
  # This is on purpose - see the notes for this method
50
175
  # rubocop:disable Lint/RescueException
51
176
  rescue Exception => e
52
- Karafka.monitor.instrument('connection.listener.fetch_loop.error', caller: self, error: e)
53
177
  # rubocop:enable Lint/RescueException
54
- # We can stop client without a problem, as it will reinitialize itself when running the
55
- # `fetch_loop` again
56
- @client.stop
57
- # We need to clear the consumers cache for current connection when fatal error happens and
58
- # we reset the connection. Otherwise for consumers with manual offset management, the
59
- # persistence might have stored some data that would be reprocessed
60
- Karafka::Persistence::Consumers.clear
61
- sleep(@consumer_group.reconnect_timeout) && retry
178
+ Karafka.monitor.instrument(
179
+ 'error.occurred',
180
+ caller: self,
181
+ error: e,
182
+ type: 'connection.listener.fetch_loop.error'
183
+ )
184
+
185
+ restart
186
+
187
+ sleep(1) && retry
188
+ ensure
189
+ @consumer_group_coordinator.unlock
190
+ end
191
+
192
+ # Resumes processing of partitions that were paused due to an error.
193
+ def resume_paused_partitions
194
+ @coordinators.resume do |topic, partition|
195
+ @client.resume(topic.name, partition)
196
+ end
197
+ end
198
+
199
+ # Enqueues revoking jobs for partitions that were taken away from the running process.
200
+ def build_and_schedule_revoked_jobs_for_revoked_partitions
201
+ revoked_partitions = @client.rebalance_manager.revoked_partitions
202
+
203
+ # Stop early to save on some execution and array allocation
204
+ return if revoked_partitions.empty?
205
+
206
+ jobs = []
207
+
208
+ revoked_partitions.each do |topic, partitions|
209
+ partitions.each do |partition|
210
+ @coordinators.revoke(topic, partition)
211
+
212
+ # There may be a case where we have lost partition of which data we have never
213
+ # processed (if it was assigned and revoked really fast), thus we may not have it
214
+ # here. In cases like this, we do not run a revocation job
215
+ @executors.find_all(topic, partition).each do |executor|
216
+ job = @jobs_builder.revoked(executor)
217
+ job.before_enqueue
218
+ jobs << job
219
+ end
220
+
221
+ # We need to remove all the executors of a given topic partition that we have lost, so
222
+ # next time we pick up it's work, new executors kick in. This may be needed especially
223
+ # for LRJ where we could end up with a race condition
224
+ # This revocation needs to happen after the jobs are scheduled, otherwise they would
225
+ # be scheduled with new executors instead of old
226
+ @executors.revoke(topic, partition)
227
+ end
228
+ end
229
+
230
+ @scheduler.schedule_revocation(@jobs_queue, jobs)
231
+ end
232
+
233
+ # Enqueues the shutdown jobs for all the executors that exist in our subscription group
234
+ def build_and_schedule_shutdown_jobs
235
+ jobs = []
236
+
237
+ @executors.each do |executor|
238
+ job = @jobs_builder.shutdown(executor)
239
+ job.before_enqueue
240
+ jobs << job
241
+ end
242
+
243
+ @scheduler.schedule_shutdown(@jobs_queue, jobs)
244
+ end
245
+
246
+ # Polls messages within the time and amount boundaries defined in the settings and then
247
+ # builds karafka messages based on the raw rdkafka messages buffer returned by the
248
+ # `#batch_poll` method.
249
+ #
250
+ # @note There are two buffers, one for raw messages and one for "built" karafka messages
251
+ def poll_and_remap_messages
252
+ @messages_buffer.remap(
253
+ @client.batch_poll
254
+ )
255
+ end
256
+
257
+ # Takes the messages per topic partition and enqueues processing jobs in threads using
258
+ # given scheduler.
259
+ def build_and_schedule_consumption_jobs
260
+ return if @messages_buffer.empty?
261
+
262
+ jobs = []
263
+
264
+ @messages_buffer.each do |topic, partition, messages|
265
+ coordinator = @coordinators.find_or_create(topic, partition)
266
+ # Start work coordination for this topic partition
267
+ coordinator.start(messages)
268
+
269
+ # We do not increment coordinator for idle job because it's not a user related one
270
+ # and it will not go through a standard lifecycle. Same applies to revoked and shutdown
271
+ if messages.empty?
272
+ executor = @executors.find_or_create(topic, partition, 0, coordinator)
273
+ jobs << @jobs_builder.idle(executor)
274
+ else
275
+ @partitioner.call(topic, messages, coordinator) do |group_id, partition_messages|
276
+ executor = @executors.find_or_create(topic, partition, group_id, coordinator)
277
+ coordinator.increment
278
+ jobs << @jobs_builder.consume(executor, partition_messages)
279
+ end
280
+ end
281
+ end
282
+
283
+ jobs.each(&:before_enqueue)
284
+
285
+ @scheduler.schedule_consumption(@jobs_queue, jobs)
286
+ end
287
+
288
+ # Waits for all the jobs from a given subscription group to finish before moving forward
289
+ def wait
290
+ @jobs_queue.wait(@subscription_group.id)
291
+ end
292
+
293
+ # Waits without blocking the polling
294
+ #
295
+ # This should be used only when we no longer plan to use any incoming messages data and we
296
+ # can safely discard it. We can however use the rebalance information if needed.
297
+ #
298
+ # @param wait_until [Proc] until this evaluates to true, we will poll data
299
+ # @param after_ping [Proc] code that we want to run after each ping (if any)
300
+ #
301
+ # @note Performance of this is not relevant (in regards to blocks) because it is used only
302
+ # on shutdown and quiet, hence not in the running mode
303
+ def wait_pinging(wait_until:, after_ping: -> {})
304
+ until wait_until.call
305
+ @client.ping
306
+ after_ping.call
307
+ sleep(0.2)
308
+ end
62
309
  end
63
310
 
64
- # @return [Karafka::Connection::Client] wrapped kafka consuming client for a given topic
65
- # consumption
66
- def client
67
- @client ||= Client.new(@consumer_group)
311
+ # We can stop client without a problem, as it will reinitialize itself when running the
312
+ # `#fetch_loop` again. We just need to remember to also reset the runner as it is a long
313
+ # running one, so with a new connection to Kafka, we need to initialize the state of the
314
+ # runner and underlying consumers once again.
315
+ def restart
316
+ # If there was any problem with processing, before we reset things we need to make sure,
317
+ # there are no jobs in the queue. Otherwise it could lead to leakage in between client
318
+ # resetting.
319
+ @jobs_queue.wait(@subscription_group.id)
320
+ @jobs_queue.clear(@subscription_group.id)
321
+ @client.reset
322
+ @coordinators.reset
323
+ @executors = Processing::ExecutorsBuffer.new(@client, @subscription_group)
68
324
  end
69
325
  end
70
326
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Connection
5
+ # Abstraction layer around listeners batch.
6
+ class ListenersBatch
7
+ include Enumerable
8
+
9
+ attr_reader :coordinators
10
+
11
+ # @param jobs_queue [JobsQueue]
12
+ # @return [ListenersBatch]
13
+ def initialize(jobs_queue)
14
+ @coordinators = []
15
+
16
+ @batch = App.subscription_groups.flat_map do |_consumer_group, subscription_groups|
17
+ consumer_group_coordinator = Connection::ConsumerGroupCoordinator.new(
18
+ subscription_groups.size
19
+ )
20
+
21
+ @coordinators << consumer_group_coordinator
22
+
23
+ subscription_groups.map do |subscription_group|
24
+ Connection::Listener.new(
25
+ consumer_group_coordinator,
26
+ subscription_group,
27
+ jobs_queue
28
+ )
29
+ end
30
+ end
31
+ end
32
+
33
+ # Iterates over available listeners and yields each listener
34
+ # @param block [Proc] block we want to run
35
+ def each(&block)
36
+ @batch.each(&block)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Connection
5
+ # Buffer used to build and store karafka messages built based on raw librdkafka messages.
6
+ #
7
+ # Why do we have two buffers? `RawMessagesBuffer` is used to store raw messages and to handle
8
+ # cases related to partition revocation and reconnections. It is "internal" to the listening
9
+ # process. `MessagesBuffer` on the other hand is used to "translate" those raw messages that
10
+ # we know that are ok into Karafka messages and to simplify further work with them.
11
+ #
12
+ # While it adds a bit of overhead, it makes conceptual things much easier and it adds only two
13
+ # simple hash iterations over messages batch.
14
+ #
15
+ # @note This buffer is NOT thread safe. We do not worry about it as we do not use it outside
16
+ # of the main listener loop. It can be cleared after the jobs are scheduled with messages
17
+ # it stores, because messages arrays are not "cleared" in any way directly and their
18
+ # reference stays.
19
+ class MessagesBuffer
20
+ attr_reader :size
21
+
22
+ # @param subscription_group [Karafka::Routing::SubscriptionGroup]
23
+ def initialize(subscription_group)
24
+ @subscription_group = subscription_group
25
+ @size = 0
26
+ @groups = Hash.new do |topic_groups, topic|
27
+ topic_groups[topic] = Hash.new do |partition_groups, partition|
28
+ partition_groups[partition] = []
29
+ end
30
+ end
31
+ end
32
+
33
+ # Remaps raw messages from the raw messages buffer to Karafka messages
34
+ # @param raw_messages_buffer [RawMessagesBuffer] buffer with raw messages
35
+ def remap(raw_messages_buffer)
36
+ clear unless @size.zero?
37
+
38
+ # Since it happens "right after" we've received the messages, it is close enough it time
39
+ # to be used as the moment we received messages.
40
+ received_at = Time.now
41
+
42
+ raw_messages_buffer.each do |topic, partition, messages|
43
+ @size += messages.count
44
+
45
+ ktopic = @subscription_group.topics.find(topic)
46
+
47
+ @groups[topic][partition] = messages.map do |message|
48
+ Messages::Builders::Message.call(
49
+ message,
50
+ ktopic,
51
+ received_at
52
+ )
53
+ end
54
+ end
55
+ end
56
+
57
+ # Allows to iterate over all the topics and partitions messages
58
+ #
59
+ # @yieldparam [String] topic name
60
+ # @yieldparam [Integer] partition number
61
+ # @yieldparam [Array<Karafka::Messages::Message>] messages from a given topic partition
62
+ def each
63
+ @groups.each do |topic, partitions|
64
+ partitions.each do |partition, messages|
65
+ yield(topic, partition, messages)
66
+ end
67
+ end
68
+ end
69
+
70
+ # @return [Boolean] is the buffer empty or does it contain any messages
71
+ def empty?
72
+ @size.zero?
73
+ end
74
+
75
+ private
76
+
77
+ # Clears the buffer completely
78
+ def clear
79
+ @size = 0
80
+ @groups.clear
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Connection
5
+ # Partitions pauses management abstraction layer.
6
+ # It aggregates all the pauses for all the partitions that we're working with.
7
+ class PausesManager
8
+ # @return [Karafka::Connection::PausesManager] pauses manager
9
+ def initialize
10
+ @pauses = Hash.new do |h, k|
11
+ h[k] = {}
12
+ end
13
+ end
14
+
15
+ # Creates or fetches pause tracker of a given topic partition.
16
+ #
17
+ # @param topic [::Karafka::Routing::Topic] topic
18
+ # @param partition [Integer] partition number
19
+ # @return [Karafka::TimeTrackers::Pause] pause tracker instance
20
+ def fetch(topic, partition)
21
+ @pauses[topic][partition] ||= TimeTrackers::Pause.new(
22
+ timeout: topic.pause_timeout,
23
+ max_timeout: topic.pause_max_timeout,
24
+ exponential_backoff: topic.pause_with_exponential_backoff
25
+ )
26
+ end
27
+
28
+ # Resumes processing of partitions for which pause time has ended.
29
+ #
30
+ # @yieldparam [Karafka::Routing::Topic] topic
31
+ # @yieldparam [Integer] partition number
32
+ def resume
33
+ @pauses.each do |topic, partitions|
34
+ partitions.each do |partition, pause|
35
+ next unless pause.paused?
36
+ next unless pause.expired?
37
+
38
+ pause.resume
39
+
40
+ yield(topic, partition)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Connection
5
+ # Usually it is ok to use the `Rdkafka::Consumer` directly because we need 1:1 its
6
+ # functionality. There are however cases where we want to have extra recoveries or other
7
+ # handling of errors and settings. This is where this module comes in handy.
8
+ #
9
+ # We do not want to wrap and delegate all via a proxy object for performance reasons, but we
10
+ # do still want to be able to alter some functionalities. This wrapper helps us do it when
11
+ # it would be needed
12
+ class Proxy < SimpleDelegator
13
+ # Errors on which we want to retry
14
+ RETRYABLE_ERRORS = %i[
15
+ all_brokers_down
16
+ timed_out
17
+ ].freeze
18
+
19
+ private_constant :RETRYABLE_ERRORS
20
+
21
+ attr_accessor :wrapped
22
+
23
+ alias __getobj__ wrapped
24
+
25
+ # @param obj [Rdkafka::Consumer, Proxy] rdkafka consumer or consumer wrapped with proxy
26
+ def initialize(obj)
27
+ super
28
+ # Do not allow for wrapping proxy with a proxy. This will prevent a case where we might
29
+ # wrap an already wrapped object with another proxy level. Simplifies passing consumers
30
+ # and makes it safe to wrap without type checking
31
+ @wrapped = obj.is_a?(self.class) ? obj.wrapped : obj
32
+ @config = ::Karafka::App.config.internal.connection.proxy
33
+ end
34
+
35
+ # Proxies the `#query_watermark_offsets` with extra recovery from timeout problems.
36
+ # We impose our own custom timeout to make sure, that high-latency clusters and overloaded
37
+ # clusters can handle our requests.
38
+ #
39
+ # @param topic [String] topic name
40
+ # @param partition [Partition]
41
+ # @return [Array<Integer, Integer>] watermark offsets
42
+ def query_watermark_offsets(topic, partition)
43
+ l_config = @config.query_watermark_offsets
44
+
45
+ with_broker_errors_retry(
46
+ # required to be in seconds, not ms
47
+ wait_time: l_config.wait_time / 1_000.to_f,
48
+ max_attempts: l_config.max_attempts
49
+ ) do
50
+ @wrapped.query_watermark_offsets(topic, partition, l_config.timeout)
51
+ end
52
+ end
53
+
54
+ # Similar to `#query_watermark_offsets`, this method can be sensitive to latency. We handle
55
+ # this the same way
56
+ #
57
+ # @param tpl [Rdkafka::Consumer::TopicPartitionList] tpl to get time offsets
58
+ # @return [Rdkafka::Consumer::TopicPartitionList] tpl with time offsets
59
+ def offsets_for_times(tpl)
60
+ l_config = @config.offsets_for_times
61
+
62
+ with_broker_errors_retry(
63
+ # required to be in seconds, not ms
64
+ wait_time: l_config.wait_time / 1_000.to_f,
65
+ max_attempts: l_config.max_attempts
66
+ ) do
67
+ @wrapped.offsets_for_times(tpl, l_config.timeout)
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ # Runs expected block of code with few retries on all_brokers_down
74
+ # librdkafka can return `all_brokers_down` for scenarios when broker is overloaded or not
75
+ # reachable due to latency.
76
+ # @param max_attempts [Integer] how many attempts (not retries) should we take before failing
77
+ # completely.
78
+ # @param wait_time [Integer, Float] how many seconds should we wait. It uses `#sleep` of Ruby
79
+ # so it needs time in seconds.
80
+ def with_broker_errors_retry(max_attempts:, wait_time: 1)
81
+ attempt ||= 0
82
+ attempt += 1
83
+
84
+ yield
85
+ rescue Rdkafka::RdkafkaError => e
86
+ raise unless RETRYABLE_ERRORS.include?(e.code)
87
+
88
+ if attempt <= max_attempts
89
+ sleep(wait_time)
90
+
91
+ retry
92
+ end
93
+
94
+ raise
95
+ end
96
+ end
97
+ end
98
+ end