feat: add http_server.tcp_rejected_rate metric for connection-cap saturation - #7499
Conversation
|
This pull request does not have a backport label. Could you fix it @ycombinator? 🙏
|
|
Tick the box to add this pull request to the merge queue (same as
|
|
The logic looks right to me, but calculating the rate of change this way within the application looks like an antipattern. Could we emit it as a |
I had looked into this previously (for a similar trigger that we may/may not need: https://github.com/elastic/fleet-controller/issues/626). Unfortunately, Elastic Pod Autoscaler (EPA) doesn't support computing rates from a counter. We could implement this around fleet-controller, but that would require it to poll the That would be a larger architectural change, so this PR keeps the rate calculation in Fleet Server for now. [UPDATE] I've created https://github.com/elastic/elastic-pod-autoscaler/issues/745 so the EPA maintainers can discuss and decide if they want to support deriving rate metrics from counter metrics in the future. |
…uration Adds tcp_rejected (counter) and tcp_rejected_rate (gauge, rejections/sec) to Fleet Server's /stats endpoint. The rate is sampled every 10s from the count of HTTP 429 rejections issued by the chi Throttle middleware (the per-pod connection cap at max_connections), following the same pattern as the existing checkin rejection rate sampler introduced in #7330. tcp_rejected_rate is a saturation signal that complements tcp_active: - tcp_active is a utilization signal — EPA uses it to maintain headroom by scaling before pods fill up. It understates demand when connections are short-lived (e.g. an enrollment burst arriving and clearing between samples). - tcp_rejected_rate fires the moment a pod is at its connection ceiling and returning 429s, catching bursts that tcp_active misses entirely. EPA takes the maximum desired replica count across all configured metrics, so wiring both allows proactive scaling (tcp_active: maintain headroom at steady state) and reactive scaling (tcp_rejected_rate: act immediately under a burst). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d in /stats Only tcp_rejected_rate (the sampled gauge) is needed in /stats for EPA. The raw accumulator doesn't need to be a first-class metric, so replace the statsCounter (which registers with both libbeat and Prometheus registries) with an unexported atomic.Uint64. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ectionRateSampler Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…y noctx linter Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
aae6116 to
c85880b
Compare
macdewee
left a comment
There was a problem hiding this comment.
We could also add test for tcp_rejected_rate should return to 0 within the next sample interval
Added in 47aa51d. |
|
This pull request does not have a backport label. Could you fix it @ycombinator? 🙏
|
What is the problem this PR solves?
Fleet Server's existing autoscaling metric,
http_server.tcp_active, is a utilization signal: EPA uses it to maintain headroom by targeting N active connections per pod. This works well at steady state but understates demand when connections are short-lived — specifically during large-scale agent enrollment, where each enrollment connection completes in under a second.tcp_activeis a point-in-time gauge: it reflects the instantaneous count of open HTTP connections at the moment EPA polls/stats. A burst of enrollment connections that arrive and clear between two EPA polls leaves no trace in the metric. The result is that pods plateau well below the theoretical requirement, agents receive HTTP 429s, and enter exponential backoff — turning a short enrollment phase into a multi-hour convergence tail.How does this PR solve the problem?
Adds
http_server.tcp_rejected_rateto/stats: a gauge, in rejections/sec, derived from an internal counter that increments whenever the chiThrottlemiddleware returns HTTP 429 due to the per-pod connection cap (max_connections). The rate is sampled every 10 s by a newRunConnRejectionRateSamplergoroutine.tcp_rejected_ratevstcp_activetcp_activetcp_rejected_rateBecause EPA takes the maximum desired replica count across all configured metrics, wiring both allows proactive scaling (
tcp_active: headroom at steady state) and reactive scaling (tcp_rejected_rate: act immediately under a burst). Neither metric suppresses the other.The raw rejection count is kept as an unexported
atomic.Uint64— only the derived rate gauge is exposed in/stats, since that is the only value EPA consumes. The chiThrottlemiddleware behaviour is unchanged; the wrapper only observes the response status code.How to test this PR locally
max_connections: Nand start it.heyorwrk).curl localhost:5066/stats | python3 -m json.tool | grep tcp_rejected— confirmtcp_rejected_ratebecomes non-zero after ~10 s.tcp_rejected_rateshould return to 0 within the next sample interval.Design Checklist
Checklist
./changelog/fragmentsusing the changelog toolRelated issues