fix(metrics): export-only clamp for buffer statistics - #5467
Conversation
LocalMetrics sub/dec could drive buffer size gauges below zero when racey buffer accounting under/over-subtracted (issues fluent#5303, fluent#2712). That produced negative Prometheus series such as fluentd_output_status_buffer_total_bytes. Clamp gauge sub/dec at zero in LocalMetrics, and clamp stage/queue sizes when exporting buffer statistics. Fixes fluent#5303 Signed-off-by: Vedant Madane <6527493+VedantMadane@users.noreply.github.com>
a9e5a03 to
50918f2
Compare
|
Thanks for digging into #5303. However, I think clamping at zero is the wrong direction for the buffer counters: it converts a transient, self-healing negative into a permanent over-count.
That leaves a window in which the enqueue thread can run Take a 100-byte chunk in that window:
Under load this repeats on every flush cycle and accumulates. Note that @total_limit_size > @stage_size_metrics.get + @queue_size_metrics.getSo an effectively empty buffer eventually fails this check and the output starts raising Two related points:
I would suggest fixing the add/sub pairing in One more thing unrelated to the design question: |
There was a problem hiding this comment.
Pull request overview
This PR addresses negative buffer size gauge values that can surface under concurrent buffer stage/queue transitions, ensuring exported buffer metrics do not report negative byte sizes (notably impacting Prometheus consumers).
Changes:
- Clamp
LocalMetricsgaugesub/decoperations so gauge values do not fall below zero. - Clamp buffer
statisticsexport forstage_byte_size/queue_byte_sizeto non-negative values. - Add unit tests to verify gauge
sub/decnever produce negative values.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
lib/fluent/plugin/metrics_local.rb |
Floors gauge decrements/subtractions at zero to prevent negative gauges in the default local metrics backend. |
lib/fluent/plugin/buffer.rb |
Ensures exported buffer statistics never publish negative stage/queue byte sizes by clamping at export time. |
test/plugin/test_metrics_local.rb |
Adds unit tests validating non-negative behavior for gauge sub/dec. |
Suppressed comments (1)
lib/fluent/plugin/buffer.rb:925
available_buffer_space_ratiosis described as a ratio of available space, butbuffer_spacecan still go below 0.0 (or above 1.0) if stage/queue counters drift upward beyond@total_limit_size. This would export negative (or >100) ratios; consider clamping the computed ratio to [0.0, 1.0] before publishing.
buffer_space = 1.0 - ((stage_size + queue_size * 1.0) / @total_limit_size)
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Cover export-path clamping for stage_byte_size/queue_byte_size and non-negative total_queued_size when underlying gauges are negative. Signed-off-by: Vedant Madane <6527493+VedantMadane@users.noreply.github.com>
3b17bb5 to
ce7211a
Compare
When stage/queue counters overshoot total_limit_size the free-space ratio could go negative (or above 100). Clamp the ratio to [0, 1] after non-negative size export. Strengthen #statistics unit tests: - negative gauges export stage/queue/total as 0 and ratio 100.0 - overshoot beyond total_limit_size clamps ratio to 0.0 Refs fluent#5303 Signed-off-by: Vedant Madane <6527493+VedantMadane@users.noreply.github.com>
1eba6ab to
9afb98c
Compare
Signed-off-by: Vedant Madane <6527493+VedantMadane@users.noreply.github.com>
|
Addressed remaining Copilot note on NaN when |
…ze is 0 Signed-off-by: Vedant Madane <6527493+VedantMadane@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
lib/fluent/plugin/metrics_local.rb:68
dec_gaugeno longer reliably returns the updated gauge value because the last expression is the conditional clamp (@store = 0 if ...), which returnsnilwhen no clamp happens. This changes behavior vsinc(and the previousdec_gaugeimplementation) and could break any callers that use the return value. Consider explicitly returning@storeafter clamping.
This issue also appears on line 80 of the same file.
# Buffer size / length gauges must never go negative even if a race
# causes sub/dec to run more times than add/inc (see #5303).
@store -= 1
@store = 0 if @store < 0
lib/fluent/plugin/metrics_local.rb:83
sub_gaugehas the same return-value issue asdec_gauge: the clamp line is the last expression, so the method returnsnilwhen no clamp happens. Returning@storekeeps gauge operations consistent and avoids surprising behavior changes for callers.
# Prevent negative values that leak into Prometheus buffer metrics
# (fluentd_output_status_buffer_total_bytes, etc.). See #5303 / #2712.
@store -= value
@store = 0 if @store < 0
Address Watson1978 review on fluent#5467: - Revert LocalMetrics sub/dec zero-clamping. Flooring the gauge store turns a transient deferred-add vs enqueue_chunk-sub race into a permanent stage_size over-count; Buffer#storable? then refuses writes. Keep self-correcting raw gauge semantics. - Keep export-path [get, 0].max for stage_byte_size/queue_byte_size/ total_queued_size so Prometheus consumers never see negatives. - Simplify available_buffer_space_ratios: denom > 0 ? ratio.clamp(0,1) : 0 (drop dead NaN guard inside the positive-denom branch). - Drop LocalMetrics clamp unit tests; keep statistics export tests. Signed-off-by: Vedant Madane <6527493+VedantMadane@users.noreply.github.com>
|
Thanks @Watson1978 — you were right about the gauge-store clamp. Addressed in 3fe8d18:
I did not move the clamp into the |
|
The export-only approach looks right to me for #5303. Just noting for the record that the underlying race is still open: Filed in #5479 |
Which issue(s) this PR fixes:
Fixes #5303
What this PR does / why we need it:
Buffer size gauges (
stage_byte_size,queue_byte_size, and derivedtotal_queued_size) can go transiently negative when Fluentd core under/over-subtracts during concurrent stage/queue transitions (the deferred@stage_size_metrics.addafter chunk unlock vsenqueue_chunk'ssub— see #2712 / #2734). Those values are mirrored by the Prometheus plugin asfluentd_output_status_buffer_total_bytes/fluentd_output_status_buffer_stage_byte_size, which is what #5303 reports.Clamping the gauge store on
sub/decis the wrong fix: it turns a self-correcting transient negative into a permanent over-count, soBuffer#storable?(which reads the raw gauge) eventually refuses every write. Thanks @Watson1978 for catching that.This PR takes an export-only approach:
LocalMetricsgaugesub/dec/setsemantics unchanged (negatives still allowed so the deferred-add race can self-heal).>= 0only when buildingstatistics(the path Prometheus and the monitor agent consume).available_buffer_space_ratiosto[0, 100]when counters overshoottotal_limit_size, and treattotal_limit_size == 0as 0% free without dividing (no NaN / no dead NaN guard).Docs Changes:
None
Release Note:
General Checklist:
Tests:
test/plugin/test_buffer.rb#statistics: negative underlying gauges export as 0; overshoot clamps ratio to 0;total_limit_size == 0does not raise and ratio stays finite.