racecar 2.12.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a4e6b0c4d52637bd8c6bb2c5e3d69013889053973195a5d73ac5d5645047f985
4
- data.tar.gz: 23425d552932665cafa03cf245d3d4ef879e053e9e9c60d1eb172455f405f3f3
3
+ metadata.gz: 994ab85c49335ccb0a2aee9d7f0c43f1bdc4ee7cd48e19cc01f2eba884f8ec1d
4
+ data.tar.gz: 680ccd2f1adaa3cfa46ca66086234667959b916da2e1c4f8748d2a47b82454f2
5
5
  SHA512:
6
- metadata.gz: 62d52be41cb3821a5d6534fea43deea8d02dd8a8f03d3449249be0fcdcbf5c842cbb2be8aac7065b97fb515b16370ab93f114ab44dd85dce6a22091d9ac04de4
7
- data.tar.gz: 006efc7649ddb4257a80e1f2400a62e9e9cba0fa5573d3947f47293d4d77b9b701d66ebf6d465cf91fbf209771e5699ddc9cd7df238ecddf7f54ffbab4eb0c15
6
+ metadata.gz: 1be971cb7d6cb591e0b3961e680057c49768dcf10dded68d4ec293e07ba32ad432d2ba2d3e4a57012a7f37831be655422fcad330efe5394e1a58bc56929fca59
7
+ data.tar.gz: 5a2b04c4cf2783f60b52acf38f0ed8e06a80a6b057d58d10d3419c84281edc719435fe6f420587bf358012adb917aa5e970b71d2d39645194bb1d024f0f39267
@@ -0,0 +1,4 @@
1
+ # CODEOWNERS file
2
+ # This file defines who should review code changes in this repository.
3
+
4
+ * @zendesk/core-gem-owners
@@ -2,9 +2,9 @@ name: CI
2
2
 
3
3
  on:
4
4
  pull_request:
5
- branches: ["master"]
5
+ branches: ["main"]
6
6
  push:
7
- branches: ["master"]
7
+ branches: ["main"]
8
8
 
9
9
  jobs:
10
10
  unit-specs:
@@ -1,12 +1,26 @@
1
- name: Publish Gem
1
+ name: Publish to RubyGems.org
2
2
 
3
3
  on:
4
4
  push:
5
- tags: v*
5
+ branches: main
6
+ paths: lib/racecar/version.rb
7
+ workflow_dispatch:
6
8
 
7
9
  jobs:
8
- call-workflow:
9
- uses: zendesk/gw/.github/workflows/ruby-gem-publication.yml@main
10
- secrets:
11
- RUBY_GEMS_API_KEY: ${{ secrets.RUBY_GEMS_API_KEY }}
12
- RUBY_GEMS_TOTP_DEVICE: ${{ secrets.RUBY_GEMS_TOTP_DEVICE }}
10
+ publish:
11
+ runs-on: ubuntu-latest
12
+ environment: rubygems-publish
13
+ if: github.repository_owner == 'zendesk'
14
+ permissions:
15
+ id-token: write
16
+ contents: write
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - name: Set up Ruby
20
+ uses: ruby/setup-ruby@v1
21
+ with:
22
+ bundler-cache: false
23
+ ruby-version: "3.4"
24
+ - name: Install dependencies
25
+ run: bundle install
26
+ - uses: rubygems/release-gem@v1
data/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 2.13.0
6
+
7
+ * Fix `ConsumerSet#pause` raising `NameError` when `OpenStruct` is not loaded by
8
+ replacing the seek message with a `Struct`.
9
+ * Commit stored offsets on a timer while idle to refresh their retention, so an
10
+ idle consumer group doesn't lose its position in the topics after
11
+ `offsets.retention.ms` expires. Enabled by default; set
12
+ `offset_commit_on_idle=false` to disable.
13
+
5
14
  ## 2.12.0
6
15
 
7
16
  * Add tests against Ruby 3.4
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- racecar (2.12.0)
4
+ racecar (2.13.0)
5
5
  king_konf (~> 1.0.0)
6
6
  rdkafka (>= 0.15.0)
7
7
 
@@ -45,7 +45,7 @@ GEM
45
45
  byebug (~> 11.0)
46
46
  pry (>= 0.13, < 0.15)
47
47
  rake (13.0.6)
48
- rdkafka (0.18.0)
48
+ rdkafka (0.21.0)
49
49
  ffi (~> 1.15)
50
50
  mini_portile2 (~> 2.6)
51
51
  rake (> 12)
data/README.md CHANGED
@@ -349,6 +349,7 @@ end
349
349
  The consumers will checkpoint their positions from time to time in order to be able to recover from failures. This is called _committing offsets_, since it's done by tracking the offset reached in each partition being processed, and committing those offset numbers to the Kafka offset storage API. If you can tolerate more double-processing after a failure, you can increase the interval between commits in order to better performance. You can also do the opposite if you prefer less chance of double-processing.
350
350
 
351
351
  - `offset_commit_interval` – How often to save the consumer's position in Kafka. Default is every 10 seconds.
352
+ - `offset_commit_on_idle` – Whether to keep committing the stored position on a timer even when no messages are being processed. Kafka retains committed offsets for only a finite period (`offsets.retention.ms`); without these keep-alive commits, an idle consumer group can lose its position and fall back to `auto.offset.reset` on the next restart, causing reprocessing or skipped messages. Default is `true`.
352
353
 
353
354
  #### Timeouts & intervals
354
355
 
@@ -418,7 +419,7 @@ Racecar supports [Datadog](https://www.datadoghq.com/) monitoring integration. I
418
419
  - `datadog_namespace` – The namespace to use for Datadog metrics.
419
420
  - `datadog_tags` – Tags that should always be set on Datadog metrics.
420
421
 
421
- Furthermore, there's a [standard Datadog dashboard configuration file](https://raw.githubusercontent.com/zendesk/racecar/master/extra/datadog-dashboard.json) that you can import to get started with a Racecar dashboard for all of your consumers.
422
+ Furthermore, there's a [standard Datadog dashboard configuration file](https://raw.githubusercontent.com/zendesk/racecar/main/extra/datadog-dashboard.json) that you can import to get started with a Racecar dashboard for all of your consumers.
422
423
 
423
424
  #### Consumers Without Rails
424
425
 
@@ -729,6 +730,22 @@ There can be behavioural inconsistencies between running the specs on your machi
729
730
 
730
731
  Please note - your code directory is mounted as a volume, so you can make code changes without needing to rebuild
731
732
 
733
+ ### Releasing a new version
734
+
735
+ A new version is published to RubyGems.org every time a change to `version.rb` is pushed to the `main` branch.
736
+ In short, follow these steps:
737
+ 1. Update `version.rb`,
738
+ 2. run `bundle lock` to update `Gemfile.lock`,
739
+ 3. merge this change into `main`, and
740
+ 4. look at [the action](https://github.com/zendesk/racecar/actions/workflows/publish.yml) for output.
741
+
742
+ To create a pre-release from a non-main branch:
743
+ 1. change the version in `version.rb` to something like `2.13.0.pre.1` or `3.0.0.beta.2`,
744
+ 2. push this change to your branch,
745
+ 3. go to [Actions → “Publish to RubyGems.org” on GitHub](https://github.com/zendesk/racecar/actions/workflows/publish.yml),
746
+ 4. click the “Run workflow” button,
747
+ 5. pick your branch from a dropdown.
748
+
732
749
  ## Contributing
733
750
 
734
751
  Bug reports and pull requests are welcome on [GitHub](https://github.com/zendesk/racecar). Feel free to [join our Slack team](https://ruby-kafka-slack.herokuapp.com/) and ask how best to contribute!
data/Rakefile CHANGED
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "bundler/setup"
3
4
  require "bundler/gem_tasks"
4
5
  require "rspec/core/rake_task"
5
6
 
6
- # Pushing to rubygems is handled by a github workflow
7
- ENV["gem_push"] = "false"
8
-
9
7
  RSpec::Core::RakeTask.new(:spec)
10
8
 
11
9
  task :default => :spec
@@ -23,6 +23,9 @@ module Racecar
23
23
  desc "How frequently to commit offset positions"
24
24
  float :offset_commit_interval, default: 10
25
25
 
26
+ desc "Commit stored offsets on a timer even when no messages have been processed, refreshing their retention so an idle consumer group doesn't lose its position"
27
+ boolean :offset_commit_on_idle, default: true
28
+
26
29
  desc "How often to send a heartbeat message to Kafka"
27
30
  float :heartbeat_interval, default: 10
28
31
 
@@ -3,6 +3,8 @@
3
3
  module Racecar
4
4
  class ConsumerSet
5
5
  MAX_POLL_TRIES = 10
6
+ SeekMessage = Struct.new(:topic, :partition, :offset, keyword_init: true)
7
+ private_constant :SeekMessage
6
8
 
7
9
  def initialize(config, logger, instrumenter = NullInstrumenter)
8
10
  @config, @logger = config, logger
@@ -100,8 +102,8 @@ module Racecar
100
102
  end
101
103
 
102
104
  consumer.pause(filtered_tpl)
103
- fake_msg = OpenStruct.new(topic: topic, partition: partition, offset: offset)
104
- consumer.seek(fake_msg)
105
+ seek_message = SeekMessage.new(topic: topic, partition: partition, offset: offset)
106
+ consumer.seek(seek_message)
105
107
 
106
108
  @paused_tpls[topic][partition] = [consumer, filtered_tpl]
107
109
  end
@@ -49,6 +49,7 @@ module Racecar
49
49
  def run
50
50
  install_signal_handlers
51
51
  @stop_requested = false
52
+ @last_keep_alive_commit_at = monotonic_time
52
53
 
53
54
  # Configure the consumer with a producer so it can produce messages and
54
55
  # with a consumer so that it can support advanced use-cases.
@@ -68,18 +69,22 @@ module Racecar
68
69
  loop do
69
70
  break if @stop_requested
70
71
  resume_paused_partitions
72
+ @offset_advanced_this_loop = false
71
73
 
72
74
  @instrumenter.instrument("start_main_loop", instrumentation_payload)
73
75
  @instrumenter.instrument("main_loop", instrumentation_payload) do
74
76
  case process_method
75
77
  when :batch then
76
- msg_per_part = consumer.batch_poll(config.max_wait_time_ms).group_by(&:partition)
77
- msg_per_part.each_value do |messages|
78
- process_batch(messages)
78
+ messages = consumer.batch_poll(config.max_wait_time_ms)
79
+ msg_per_part = messages.group_by(&:partition)
80
+ msg_per_part.each_value do |batch|
81
+ process_batch(batch)
79
82
  end
83
+ maybe_keep_alive_commit(!@offset_advanced_this_loop)
80
84
  when :single then
81
85
  message = consumer.poll(config.max_wait_time_ms)
82
86
  process(message) if message
87
+ maybe_keep_alive_commit(!@offset_advanced_this_loop)
83
88
  end
84
89
  end
85
90
  end
@@ -185,6 +190,7 @@ module Racecar
185
190
  processor.process(Racecar::Message.new(message, retries_count: pause.pauses_count))
186
191
  processor.deliver!
187
192
  consumer.store_offset(message)
193
+ @offset_advanced_this_loop = true
188
194
  end
189
195
  rescue => e
190
196
  instrumentation_payload[:unrecoverable_delivery_error] = reset_producer_on_unrecoverable_delivery_errors(e)
@@ -217,6 +223,7 @@ module Racecar
217
223
  processor.process_batch(racecar_messages)
218
224
  processor.deliver!
219
225
  consumer.store_offset(messages.last)
226
+ @offset_advanced_this_loop = true
220
227
  end
221
228
  rescue => e
222
229
  instrumentation_payload[:unrecoverable_delivery_error] = reset_producer_on_unrecoverable_delivery_errors(e)
@@ -289,5 +296,38 @@ module Racecar
289
296
  end
290
297
  end
291
298
  end
299
+
300
+ # librdkafka's auto-commit only writes an OffsetCommit when the stored
301
+ # offset has changed, so a consumer that never advances its stored offset
302
+ # -- whether idle or stuck on failing messages -- never commits. Kafka
303
+ # retains committed offsets for only a finite period (offsets.retention.ms,
304
+ # default 7 days); once they expire the group loses its position and a
305
+ # restart falls back to auto.offset.reset, causing reprocessing or skipped
306
+ # messages. Re-committing the already stored position on a timer refreshes
307
+ # its retention. This only fires when the stored offset wasn't advanced
308
+ # this iteration, so it never duplicates the normal commit path, and it
309
+ # never advances past a stored offset, so at-least-once semantics are
310
+ # unchanged.
311
+ def maybe_keep_alive_commit(offset_stale)
312
+ return unless offset_stale
313
+ return unless config.offset_commit_on_idle
314
+ return if monotonic_time - @last_keep_alive_commit_at < config.offset_commit_interval
315
+
316
+ consumer.commit
317
+ @last_keep_alive_commit_at = monotonic_time
318
+ rescue Rdkafka::RdkafkaError => e
319
+ # Retry on the next idle iteration (the timer isn't advanced on failure),
320
+ # but throttle the warning so a persistent failure doesn't spam the log
321
+ # every iteration.
322
+ now = monotonic_time
323
+ unless @last_keep_alive_commit_error_at && now - @last_keep_alive_commit_error_at < config.offset_commit_interval
324
+ @last_keep_alive_commit_error_at = now
325
+ logger.warn "Keep-alive offset commit failed: #{e}"
326
+ end
327
+ end
328
+
329
+ def monotonic_time
330
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
331
+ end
292
332
  end
293
333
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Racecar
4
- VERSION = "2.12.0"
4
+ VERSION = "2.13.0"
5
5
  end
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: racecar
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.12.0
4
+ version: 2.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Schierbeck
8
8
  - Benjamin Quorning
9
- autorequire:
10
9
  bindir: exe
11
10
  cert_chain: []
12
- date: 2025-02-27 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: king_konf
@@ -149,7 +148,6 @@ dependencies:
149
148
  - - ">="
150
149
  - !ruby/object:Gem::Version
151
150
  version: '0'
152
- description:
153
151
  email:
154
152
  - dschierbeck@zendesk.com
155
153
  - bquorning@zendesk.com
@@ -159,6 +157,7 @@ executables:
159
157
  extensions: []
160
158
  extra_rdoc_files: []
161
159
  files:
160
+ - ".github/CODEOWNERS"
162
161
  - ".github/dependabot.yml"
163
162
  - ".github/workflows/ci.yml"
164
163
  - ".github/workflows/publish.yml"
@@ -214,7 +213,6 @@ homepage: https://github.com/zendesk/racecar
214
213
  licenses:
215
214
  - Apache License Version 2.0
216
215
  metadata: {}
217
- post_install_message:
218
216
  rdoc_options: []
219
217
  require_paths:
220
218
  - lib
@@ -229,8 +227,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
229
227
  - !ruby/object:Gem::Version
230
228
  version: '0'
231
229
  requirements: []
232
- rubygems_version: 3.5.22
233
- signing_key:
230
+ rubygems_version: 3.6.9
234
231
  specification_version: 4
235
232
  summary: A framework for running Kafka consumers
236
233
  test_files: []