Skip to content

fix(rpc): Recover the congestion window after an overload backs it off - #18603

Open
zhichenxu-meta wants to merge 1 commit into
facebookincubator:mainfrom
zhichenxu-meta:export-D116798431
Open

fix(rpc): Recover the congestion window after an overload backs it off#18603
zhichenxu-meta wants to merge 1 commit into
facebookincubator:mainfrom
zhichenxu-meta:export-D116798431

Conversation

@zhichenxu-meta

Copy link
Copy Markdown
Contributor

Summary:
After onError() halves the window down to the floor, it can get stuck there
until the backend returns to full health -- even though the control law says
it should climb back.

The update is

newWindow = w * gradient + stepCoef * sqrt(w)

so one recomputation changes the window by
stepCoef * sqrt(w) - w * (1 - gradient), and the window was stored as an
int64_t. Decrements are large and survive truncation. Increments are often a
fraction of a unit, and truncation discarded them every window, forever.

The case where this bites. BATCH starts at a window of 2. A rate-limit
storm halves it to the floor. If the backend is still partially degraded when
the storm passes -- gradient 0.7, say -- then
sqrt(2) - 2 * (1 - 0.7) = 0.81, under one unit, so the window stays at 2
instead of recovering toward its equilibrium of 11. Throughput stays pinned at
the floor for as long as the backend stays short of healthy.

Carry the window as a double and report limit() as its floor. No new
parameters, no change to the law, no change to limit()'s signature or its
[minWindow, maxWindow] range.

Scope, stated precisely. Growth stalls exactly when
stepCoef * sqrt(w) < w * (1 - gradient) + 1.

  • PER_ROW constructs the window with start == max (default ceiling 100). It
    begins at its ceiling and never grows. Unaffected.
  • BATCH on a healthy backend has gradient ~1.0, where the step is sqrt(w),
    always at least one unit. It already climbs to the 256 ceiling. Unaffected.
  • Descending into congestion, decrements are large until the window nears
    equilibrium, where truncation strands it within about one unit. Marginal.
  • Climbing from the floor at a gradient between 0.5 and 0.79 is the case that
    breaks, and overload recovery is how a live system gets there.

What is not claimed. I could not reproduce the stalling regime on live
traffic. There is no ODS series for the window -- RpcMetrics exports
requests, errors, retries, latency and row counts, but no controller state --
and the runtime stats are per-query only. A scaling experiment on a verifier
cluster, BATCH mode with 4, 8 and 16 batches, gave flat wall times of 50s, 55s
and 53s: healthy traffic grows normally there, as the analysis predicts. The
stalling regime needs a backend held at moderate congestion, which that
cluster does not reproduce.

Lands alone: two files plus tests, no callers affected.

Differential Revision: D116798431

Summary:
After `onError()` halves the window down to the floor, it can get stuck there
until the backend returns to full health -- even though the control law says
it should climb back.

The update is

    newWindow = w * gradient + stepCoef * sqrt(w)

so one recomputation changes the window by
`stepCoef * sqrt(w) - w * (1 - gradient)`, and the window was stored as an
`int64_t`. Decrements are large and survive truncation. Increments are often a
fraction of a unit, and truncation discarded them every window, forever.

**The case where this bites.** BATCH starts at a window of 2. A rate-limit
storm halves it to the floor. If the backend is still partially degraded when
the storm passes -- gradient 0.7, say -- then
`sqrt(2) - 2 * (1 - 0.7) = 0.81`, under one unit, so the window stays at 2
instead of recovering toward its equilibrium of 11. Throughput stays pinned at
the floor for as long as the backend stays short of healthy.

Carry the window as a `double` and report `limit()` as its floor. No new
parameters, no change to the law, no change to `limit()`'s signature or its
`[minWindow, maxWindow]` range.

**Scope, stated precisely.** Growth stalls exactly when
`stepCoef * sqrt(w) < w * (1 - gradient) + 1`.

- PER_ROW constructs the window with `start == max` (default ceiling 100). It
  begins at its ceiling and never grows. Unaffected.
- BATCH on a healthy backend has gradient ~1.0, where the step is `sqrt(w)`,
  always at least one unit. It already climbs to the 256 ceiling. Unaffected.
- Descending into congestion, decrements are large until the window nears
  equilibrium, where truncation strands it within about one unit. Marginal.
- Climbing from the floor at a gradient between 0.5 and 0.79 is the case that
  breaks, and overload recovery is how a live system gets there.

**What is not claimed.** I could not reproduce the stalling regime on live
traffic. There is no ODS series for the window -- `RpcMetrics` exports
requests, errors, retries, latency and row counts, but no controller state --
and the runtime stats are per-query only. A scaling experiment on a verifier
cluster, BATCH mode with 4, 8 and 16 batches, gave flat wall times of 50s, 55s
and 53s: healthy traffic grows normally there, as the analysis predicts. The
stalling regime needs a backend held at moderate congestion, which that
cluster does not reproduce.

Lands alone: two files plus tests, no callers affected.

Differential Revision: D116798431
@netlify

netlify Bot commented Aug 20, 2026

Copy link
Copy Markdown

Deploy Preview for meta-velox canceled.

Name Link
🔨 Latest commit 02e08df
🔍 Latest deploy log https://app.netlify.com/projects/meta-velox/deploys/6a8735043bdd450008203bc4

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 20, 2026
@meta-codesync

meta-codesync Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

@zhichenxu-meta has exported this pull request. If you are a Meta employee, you can view the originating Diff in D116798431.

@github-actions

Copy link
Copy Markdown

Selective Build Plan

Linux release with adapters is running a selective build of 7 cmake targets (out of 620 total). See the CI workflows README for what this means.

Affected targets (7)

Directly changed (6)

Target Changed Files
velox_congestion_controller_test CongestionController.h, CongestionControllerTest.cpp
velox_rpc_aimd_under_load_test CongestionController.h
velox_rpc_operator CongestionController.h
velox_rpc_plan_node_translator CongestionController.h
velox_rpc_state CongestionController.cpp, CongestionController.h
velox_rpc_state_test CongestionController.h

Transitively affected (1)

  • velox_rpc_operator_test

Fast path • Graph from main@ffd016a0fa4c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. meta-exported

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant