karafka 1.4.12 → 2.2.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +10 -9
- data/.github/workflows/ci.yml +169 -31
- data/.rspec +4 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +716 -607
- data/CONTRIBUTING.md +10 -19
- data/Gemfile +7 -0
- data/Gemfile.lock +69 -92
- data/LICENSE +17 -0
- data/LICENSE-COMM +89 -0
- data/LICENSE-LGPL +165 -0
- data/README.md +48 -47
- data/bin/benchmarks +99 -0
- data/bin/create_token +22 -0
- data/bin/integrations +310 -0
- data/bin/karafka +5 -14
- data/bin/record_rss +50 -0
- data/bin/rspecs +6 -0
- data/bin/scenario +29 -0
- data/bin/stress_many +13 -0
- data/bin/stress_one +13 -0
- data/bin/verify_license_integrity +37 -0
- data/bin/wait_for_kafka +24 -0
- data/certs/cert_chain.pem +26 -0
- data/certs/karafka-pro.pem +11 -0
- data/config/locales/errors.yml +97 -0
- data/config/locales/pro_errors.yml +59 -0
- data/docker-compose.yml +19 -11
- data/karafka.gemspec +26 -22
- data/lib/active_job/karafka.rb +17 -0
- data/lib/active_job/queue_adapters/karafka_adapter.rb +32 -0
- data/lib/karafka/active_job/consumer.rb +49 -0
- data/lib/karafka/active_job/current_attributes/loading.rb +36 -0
- data/lib/karafka/active_job/current_attributes/persistence.rb +28 -0
- data/lib/karafka/active_job/current_attributes.rb +42 -0
- data/lib/karafka/active_job/dispatcher.rb +69 -0
- data/lib/karafka/active_job/job_extensions.rb +34 -0
- data/lib/karafka/active_job/job_options_contract.rb +32 -0
- data/lib/karafka/admin.rb +313 -0
- data/lib/karafka/app.rb +47 -23
- data/lib/karafka/base_consumer.rb +260 -29
- data/lib/karafka/cli/base.rb +67 -36
- data/lib/karafka/cli/console.rb +18 -12
- data/lib/karafka/cli/help.rb +24 -0
- data/lib/karafka/cli/info.rb +47 -12
- data/lib/karafka/cli/install.rb +23 -14
- data/lib/karafka/cli/server.rb +101 -44
- data/lib/karafka/cli/topics.rb +146 -0
- data/lib/karafka/cli.rb +24 -27
- data/lib/karafka/connection/client.rb +553 -90
- data/lib/karafka/connection/consumer_group_coordinator.rb +48 -0
- data/lib/karafka/connection/listener.rb +294 -38
- data/lib/karafka/connection/listeners_batch.rb +40 -0
- data/lib/karafka/connection/messages_buffer.rb +84 -0
- data/lib/karafka/connection/pauses_manager.rb +46 -0
- data/lib/karafka/connection/proxy.rb +98 -0
- data/lib/karafka/connection/raw_messages_buffer.rb +101 -0
- data/lib/karafka/connection/rebalance_manager.rb +105 -0
- data/lib/karafka/contracts/base.rb +17 -0
- data/lib/karafka/contracts/config.rb +130 -11
- data/lib/karafka/contracts/consumer_group.rb +32 -187
- data/lib/karafka/contracts/server_cli_options.rb +80 -19
- data/lib/karafka/contracts/topic.rb +65 -0
- data/lib/karafka/contracts.rb +1 -1
- data/lib/karafka/embedded.rb +36 -0
- data/lib/karafka/env.rb +46 -0
- data/lib/karafka/errors.rb +37 -21
- data/lib/karafka/helpers/async.rb +33 -0
- data/lib/karafka/helpers/colorize.rb +26 -0
- data/lib/karafka/helpers/multi_delegator.rb +2 -2
- data/lib/karafka/instrumentation/callbacks/error.rb +39 -0
- data/lib/karafka/instrumentation/callbacks/rebalance.rb +64 -0
- data/lib/karafka/instrumentation/callbacks/statistics.rb +51 -0
- data/lib/karafka/instrumentation/logger_listener.rb +303 -0
- data/lib/karafka/instrumentation/monitor.rb +13 -61
- data/lib/karafka/instrumentation/notifications.rb +79 -0
- data/lib/karafka/instrumentation/proctitle_listener.rb +7 -16
- data/lib/karafka/instrumentation/vendors/appsignal/base.rb +30 -0
- data/lib/karafka/instrumentation/vendors/appsignal/client.rb +122 -0
- data/lib/karafka/instrumentation/vendors/appsignal/dashboard.json +222 -0
- data/lib/karafka/instrumentation/vendors/appsignal/errors_listener.rb +30 -0
- data/lib/karafka/instrumentation/vendors/appsignal/metrics_listener.rb +331 -0
- data/lib/karafka/instrumentation/vendors/datadog/dashboard.json +1 -0
- data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +155 -0
- data/lib/karafka/instrumentation/vendors/datadog/metrics_listener.rb +264 -0
- data/lib/karafka/instrumentation/vendors/kubernetes/liveness_listener.rb +176 -0
- data/lib/karafka/licenser.rb +78 -0
- data/lib/karafka/messages/batch_metadata.rb +52 -0
- data/lib/karafka/messages/builders/batch_metadata.rb +60 -0
- data/lib/karafka/messages/builders/message.rb +40 -0
- data/lib/karafka/messages/builders/messages.rb +36 -0
- data/lib/karafka/{params/params.rb → messages/message.rb} +20 -13
- data/lib/karafka/messages/messages.rb +71 -0
- data/lib/karafka/{params → messages}/metadata.rb +4 -6
- data/lib/karafka/messages/parser.rb +14 -0
- data/lib/karafka/messages/seek.rb +12 -0
- data/lib/karafka/patches/rdkafka/bindings.rb +122 -0
- data/lib/karafka/patches/rdkafka/opaque.rb +36 -0
- data/lib/karafka/pro/active_job/consumer.rb +47 -0
- data/lib/karafka/pro/active_job/dispatcher.rb +86 -0
- data/lib/karafka/pro/active_job/job_options_contract.rb +45 -0
- data/lib/karafka/pro/cleaner/errors.rb +27 -0
- data/lib/karafka/pro/cleaner/messages/message.rb +46 -0
- data/lib/karafka/pro/cleaner/messages/messages.rb +42 -0
- data/lib/karafka/pro/cleaner.rb +41 -0
- data/lib/karafka/pro/contracts/base.rb +23 -0
- data/lib/karafka/pro/contracts/server_cli_options.rb +111 -0
- data/lib/karafka/pro/encryption/cipher.rb +58 -0
- data/lib/karafka/pro/encryption/contracts/config.rb +79 -0
- data/lib/karafka/pro/encryption/errors.rb +27 -0
- data/lib/karafka/pro/encryption/messages/middleware.rb +46 -0
- data/lib/karafka/pro/encryption/messages/parser.rb +56 -0
- data/lib/karafka/pro/encryption/setup/config.rb +48 -0
- data/lib/karafka/pro/encryption.rb +47 -0
- data/lib/karafka/pro/iterator/expander.rb +95 -0
- data/lib/karafka/pro/iterator/tpl_builder.rb +155 -0
- data/lib/karafka/pro/iterator.rb +170 -0
- data/lib/karafka/pro/loader.rb +106 -0
- data/lib/karafka/pro/performance_tracker.rb +84 -0
- data/lib/karafka/pro/processing/collapser.rb +62 -0
- data/lib/karafka/pro/processing/coordinator.rb +147 -0
- data/lib/karafka/pro/processing/filters/base.rb +61 -0
- data/lib/karafka/pro/processing/filters/delayer.rb +70 -0
- data/lib/karafka/pro/processing/filters/expirer.rb +51 -0
- data/lib/karafka/pro/processing/filters/inline_insights_delayer.rb +78 -0
- data/lib/karafka/pro/processing/filters/throttler.rb +84 -0
- data/lib/karafka/pro/processing/filters/virtual_limiter.rb +52 -0
- data/lib/karafka/pro/processing/filters_applier.rb +105 -0
- data/lib/karafka/pro/processing/jobs/consume_non_blocking.rb +39 -0
- data/lib/karafka/pro/processing/jobs/revoked_non_blocking.rb +37 -0
- data/lib/karafka/pro/processing/jobs_builder.rb +50 -0
- data/lib/karafka/pro/processing/partitioner.rb +69 -0
- data/lib/karafka/pro/processing/scheduler.rb +75 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom.rb +70 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_lrj_mom_vp.rb +76 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom.rb +72 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_ftr_mom_vp.rb +76 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom.rb +66 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_lrj_mom_vp.rb +70 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_mom.rb +64 -0
- data/lib/karafka/pro/processing/strategies/aj/dlq_mom_vp.rb +69 -0
- data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom.rb +38 -0
- data/lib/karafka/pro/processing/strategies/aj/ftr_lrj_mom_vp.rb +66 -0
- data/lib/karafka/pro/processing/strategies/aj/ftr_mom.rb +38 -0
- data/lib/karafka/pro/processing/strategies/aj/ftr_mom_vp.rb +58 -0
- data/lib/karafka/pro/processing/strategies/aj/lrj_mom.rb +37 -0
- data/lib/karafka/pro/processing/strategies/aj/lrj_mom_vp.rb +82 -0
- data/lib/karafka/pro/processing/strategies/aj/mom.rb +36 -0
- data/lib/karafka/pro/processing/strategies/aj/mom_vp.rb +52 -0
- data/lib/karafka/pro/processing/strategies/base.rb +26 -0
- data/lib/karafka/pro/processing/strategies/default.rb +105 -0
- data/lib/karafka/pro/processing/strategies/dlq/default.rb +137 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr.rb +61 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj.rb +75 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +71 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom_vp.rb +43 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_vp.rb +41 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +69 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_mom_vp.rb +41 -0
- data/lib/karafka/pro/processing/strategies/dlq/ftr_vp.rb +40 -0
- data/lib/karafka/pro/processing/strategies/dlq/lrj.rb +64 -0
- data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +65 -0
- data/lib/karafka/pro/processing/strategies/dlq/lrj_mom_vp.rb +36 -0
- data/lib/karafka/pro/processing/strategies/dlq/lrj_vp.rb +39 -0
- data/lib/karafka/pro/processing/strategies/dlq/mom.rb +68 -0
- data/lib/karafka/pro/processing/strategies/dlq/mom_vp.rb +37 -0
- data/lib/karafka/pro/processing/strategies/dlq/vp.rb +40 -0
- data/lib/karafka/pro/processing/strategies/ftr/default.rb +111 -0
- data/lib/karafka/pro/processing/strategies/ftr/vp.rb +40 -0
- data/lib/karafka/pro/processing/strategies/lrj/default.rb +85 -0
- data/lib/karafka/pro/processing/strategies/lrj/ftr.rb +69 -0
- data/lib/karafka/pro/processing/strategies/lrj/ftr_mom.rb +67 -0
- data/lib/karafka/pro/processing/strategies/lrj/ftr_mom_vp.rb +40 -0
- data/lib/karafka/pro/processing/strategies/lrj/ftr_vp.rb +39 -0
- data/lib/karafka/pro/processing/strategies/lrj/mom.rb +77 -0
- data/lib/karafka/pro/processing/strategies/lrj/mom_vp.rb +38 -0
- data/lib/karafka/pro/processing/strategies/lrj/vp.rb +36 -0
- data/lib/karafka/pro/processing/strategies/mom/default.rb +46 -0
- data/lib/karafka/pro/processing/strategies/mom/ftr.rb +53 -0
- data/lib/karafka/pro/processing/strategies/mom/ftr_vp.rb +37 -0
- data/lib/karafka/pro/processing/strategies/mom/vp.rb +35 -0
- data/lib/karafka/pro/processing/strategies/vp/default.rb +124 -0
- data/lib/karafka/pro/processing/strategies.rb +22 -0
- data/lib/karafka/pro/processing/strategy_selector.rb +84 -0
- data/lib/karafka/pro/processing/virtual_offset_manager.rb +147 -0
- data/lib/karafka/pro/routing/features/active_job/builder.rb +45 -0
- data/lib/karafka/pro/routing/features/active_job.rb +26 -0
- data/lib/karafka/pro/routing/features/base.rb +24 -0
- data/lib/karafka/pro/routing/features/dead_letter_queue/contracts/topic.rb +53 -0
- data/lib/karafka/pro/routing/features/dead_letter_queue.rb +27 -0
- data/lib/karafka/pro/routing/features/delaying/config.rb +27 -0
- data/lib/karafka/pro/routing/features/delaying/contracts/topic.rb +41 -0
- data/lib/karafka/pro/routing/features/delaying/topic.rb +59 -0
- data/lib/karafka/pro/routing/features/delaying.rb +29 -0
- data/lib/karafka/pro/routing/features/expiring/config.rb +27 -0
- data/lib/karafka/pro/routing/features/expiring/contracts/topic.rb +41 -0
- data/lib/karafka/pro/routing/features/expiring/topic.rb +59 -0
- data/lib/karafka/pro/routing/features/expiring.rb +27 -0
- data/lib/karafka/pro/routing/features/filtering/config.rb +40 -0
- data/lib/karafka/pro/routing/features/filtering/contracts/topic.rb +44 -0
- data/lib/karafka/pro/routing/features/filtering/topic.rb +51 -0
- data/lib/karafka/pro/routing/features/filtering.rb +27 -0
- data/lib/karafka/pro/routing/features/inline_insights/config.rb +32 -0
- data/lib/karafka/pro/routing/features/inline_insights/contracts/topic.rb +41 -0
- data/lib/karafka/pro/routing/features/inline_insights/topic.rb +52 -0
- data/lib/karafka/pro/routing/features/inline_insights.rb +26 -0
- data/lib/karafka/pro/routing/features/long_running_job/config.rb +28 -0
- data/lib/karafka/pro/routing/features/long_running_job/contracts/topic.rb +40 -0
- data/lib/karafka/pro/routing/features/long_running_job/topic.rb +42 -0
- data/lib/karafka/pro/routing/features/long_running_job.rb +28 -0
- data/lib/karafka/pro/routing/features/patterns/builder.rb +38 -0
- data/lib/karafka/pro/routing/features/patterns/config.rb +54 -0
- data/lib/karafka/pro/routing/features/patterns/consumer_group.rb +72 -0
- data/lib/karafka/pro/routing/features/patterns/contracts/consumer_group.rb +62 -0
- data/lib/karafka/pro/routing/features/patterns/contracts/pattern.rb +46 -0
- data/lib/karafka/pro/routing/features/patterns/contracts/topic.rb +41 -0
- data/lib/karafka/pro/routing/features/patterns/detector.rb +71 -0
- data/lib/karafka/pro/routing/features/patterns/pattern.rb +95 -0
- data/lib/karafka/pro/routing/features/patterns/patterns.rb +35 -0
- data/lib/karafka/pro/routing/features/patterns/topic.rb +50 -0
- data/lib/karafka/pro/routing/features/patterns/topics.rb +53 -0
- data/lib/karafka/pro/routing/features/patterns.rb +33 -0
- data/lib/karafka/pro/routing/features/pausing/contracts/topic.rb +51 -0
- data/lib/karafka/pro/routing/features/pausing/topic.rb +44 -0
- data/lib/karafka/pro/routing/features/pausing.rb +25 -0
- data/lib/karafka/pro/routing/features/throttling/config.rb +32 -0
- data/lib/karafka/pro/routing/features/throttling/contracts/topic.rb +44 -0
- data/lib/karafka/pro/routing/features/throttling/topic.rb +69 -0
- data/lib/karafka/pro/routing/features/throttling.rb +30 -0
- data/lib/karafka/pro/routing/features/virtual_partitions/config.rb +30 -0
- data/lib/karafka/pro/routing/features/virtual_partitions/contracts/topic.rb +55 -0
- data/lib/karafka/pro/routing/features/virtual_partitions/topic.rb +56 -0
- data/lib/karafka/pro/routing/features/virtual_partitions.rb +27 -0
- data/lib/karafka/pro.rb +13 -0
- data/lib/karafka/process.rb +24 -8
- data/lib/karafka/processing/coordinator.rb +181 -0
- data/lib/karafka/processing/coordinators_buffer.rb +62 -0
- data/lib/karafka/processing/executor.rb +155 -0
- data/lib/karafka/processing/executors_buffer.rb +72 -0
- data/lib/karafka/processing/expansions_selector.rb +22 -0
- data/lib/karafka/processing/inline_insights/consumer.rb +41 -0
- data/lib/karafka/processing/inline_insights/listener.rb +19 -0
- data/lib/karafka/processing/inline_insights/tracker.rb +128 -0
- data/lib/karafka/processing/jobs/base.rb +55 -0
- data/lib/karafka/processing/jobs/consume.rb +45 -0
- data/lib/karafka/processing/jobs/idle.rb +24 -0
- data/lib/karafka/processing/jobs/revoked.rb +22 -0
- data/lib/karafka/processing/jobs/shutdown.rb +23 -0
- data/lib/karafka/processing/jobs_builder.rb +28 -0
- data/lib/karafka/processing/jobs_queue.rb +150 -0
- data/lib/karafka/processing/partitioner.rb +24 -0
- data/lib/karafka/processing/result.rb +42 -0
- data/lib/karafka/processing/scheduler.rb +22 -0
- data/lib/karafka/processing/strategies/aj_dlq_mom.rb +44 -0
- data/lib/karafka/processing/strategies/aj_mom.rb +21 -0
- data/lib/karafka/processing/strategies/base.rb +52 -0
- data/lib/karafka/processing/strategies/default.rb +158 -0
- data/lib/karafka/processing/strategies/dlq.rb +88 -0
- data/lib/karafka/processing/strategies/dlq_mom.rb +49 -0
- data/lib/karafka/processing/strategies/mom.rb +29 -0
- data/lib/karafka/processing/strategy_selector.rb +47 -0
- data/lib/karafka/processing/worker.rb +93 -0
- data/lib/karafka/processing/workers_batch.rb +27 -0
- data/lib/karafka/railtie.rb +141 -0
- data/lib/karafka/routing/activity_manager.rb +84 -0
- data/lib/karafka/routing/builder.rb +45 -19
- data/lib/karafka/routing/consumer_group.rb +56 -20
- data/lib/karafka/routing/consumer_mapper.rb +1 -12
- data/lib/karafka/routing/features/active_job/builder.rb +33 -0
- data/lib/karafka/routing/features/active_job/config.rb +15 -0
- data/lib/karafka/routing/features/active_job/contracts/topic.rb +44 -0
- data/lib/karafka/routing/features/active_job/proxy.rb +14 -0
- data/lib/karafka/routing/features/active_job/topic.rb +33 -0
- data/lib/karafka/routing/features/active_job.rb +13 -0
- data/lib/karafka/routing/features/base/expander.rb +59 -0
- data/lib/karafka/routing/features/base.rb +71 -0
- data/lib/karafka/routing/features/dead_letter_queue/config.rb +19 -0
- data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +46 -0
- data/lib/karafka/routing/features/dead_letter_queue/topic.rb +41 -0
- data/lib/karafka/routing/features/dead_letter_queue.rb +16 -0
- data/lib/karafka/routing/features/declaratives/config.rb +18 -0
- data/lib/karafka/routing/features/declaratives/contracts/topic.rb +33 -0
- data/lib/karafka/routing/features/declaratives/topic.rb +44 -0
- data/lib/karafka/routing/features/declaratives.rb +14 -0
- data/lib/karafka/routing/features/inline_insights/config.rb +15 -0
- data/lib/karafka/routing/features/inline_insights/contracts/topic.rb +27 -0
- data/lib/karafka/routing/features/inline_insights/topic.rb +31 -0
- data/lib/karafka/routing/features/inline_insights.rb +40 -0
- data/lib/karafka/routing/features/manual_offset_management/config.rb +15 -0
- data/lib/karafka/routing/features/manual_offset_management/contracts/topic.rb +27 -0
- data/lib/karafka/routing/features/manual_offset_management/topic.rb +35 -0
- data/lib/karafka/routing/features/manual_offset_management.rb +18 -0
- data/lib/karafka/routing/proxy.rb +22 -21
- data/lib/karafka/routing/router.rb +24 -10
- data/lib/karafka/routing/subscription_group.rb +110 -0
- data/lib/karafka/routing/subscription_groups_builder.rb +65 -0
- data/lib/karafka/routing/topic.rb +87 -24
- data/lib/karafka/routing/topics.rb +46 -0
- data/lib/karafka/runner.rb +52 -0
- data/lib/karafka/serialization/json/deserializer.rb +7 -15
- data/lib/karafka/server.rb +113 -37
- data/lib/karafka/setup/attributes_map.rb +348 -0
- data/lib/karafka/setup/config.rb +256 -175
- data/lib/karafka/status.rb +54 -7
- data/lib/karafka/templates/example_consumer.rb.erb +16 -0
- data/lib/karafka/templates/karafka.rb.erb +33 -55
- data/lib/karafka/time_trackers/base.rb +14 -0
- data/lib/karafka/time_trackers/pause.rb +122 -0
- data/lib/karafka/time_trackers/poll.rb +69 -0
- data/lib/karafka/version.rb +1 -1
- data/lib/karafka.rb +91 -17
- data/renovate.json +9 -0
- data.tar.gz.sig +0 -0
- metadata +330 -168
- metadata.gz.sig +0 -0
- data/MIT-LICENCE +0 -18
- data/certs/mensfeld.pem +0 -25
- data/config/errors.yml +0 -41
- data/lib/karafka/assignment_strategies/round_robin.rb +0 -13
- data/lib/karafka/attributes_map.rb +0 -63
- data/lib/karafka/backends/inline.rb +0 -16
- data/lib/karafka/base_responder.rb +0 -226
- data/lib/karafka/cli/flow.rb +0 -48
- data/lib/karafka/cli/missingno.rb +0 -19
- data/lib/karafka/code_reloader.rb +0 -67
- data/lib/karafka/connection/api_adapter.rb +0 -158
- data/lib/karafka/connection/batch_delegator.rb +0 -55
- data/lib/karafka/connection/builder.rb +0 -23
- data/lib/karafka/connection/message_delegator.rb +0 -36
- data/lib/karafka/consumers/batch_metadata.rb +0 -10
- data/lib/karafka/consumers/callbacks.rb +0 -71
- data/lib/karafka/consumers/includer.rb +0 -64
- data/lib/karafka/consumers/responders.rb +0 -24
- data/lib/karafka/consumers/single_params.rb +0 -15
- data/lib/karafka/contracts/consumer_group_topic.rb +0 -19
- data/lib/karafka/contracts/responder_usage.rb +0 -54
- data/lib/karafka/fetcher.rb +0 -42
- data/lib/karafka/helpers/class_matcher.rb +0 -88
- data/lib/karafka/helpers/config_retriever.rb +0 -46
- data/lib/karafka/helpers/inflector.rb +0 -26
- data/lib/karafka/instrumentation/stdout_listener.rb +0 -140
- data/lib/karafka/params/batch_metadata.rb +0 -26
- data/lib/karafka/params/builders/batch_metadata.rb +0 -30
- data/lib/karafka/params/builders/params.rb +0 -38
- data/lib/karafka/params/builders/params_batch.rb +0 -25
- data/lib/karafka/params/params_batch.rb +0 -60
- data/lib/karafka/patches/ruby_kafka.rb +0 -47
- data/lib/karafka/persistence/client.rb +0 -29
- data/lib/karafka/persistence/consumers.rb +0 -45
- data/lib/karafka/persistence/topics.rb +0 -48
- data/lib/karafka/responders/builder.rb +0 -36
- data/lib/karafka/responders/topic.rb +0 -55
- data/lib/karafka/routing/topic_mapper.rb +0 -53
- data/lib/karafka/serialization/json/serializer.rb +0 -31
- data/lib/karafka/setup/configurators/water_drop.rb +0 -36
- 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
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
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
|
-
|
11
|
-
|
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(
|
14
|
-
|
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
|
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
|
-
|
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
|
-
#
|
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::
|
33
|
-
# won't crash the whole
|
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
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
#
|
65
|
-
#
|
66
|
-
|
67
|
-
|
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
|