Skip to content

Fix unit conversion in Backoff.decorrelatedJittered that stops backoff from growing - #4929

Open
amalsaidov wants to merge 1 commit into
apache:masterfrom
amalsaidov:fix-decorrelated-jitter-unit-conversion
Open

amalsaidov wants to merge 1 commit into
apache:masterfrom
amalsaidov:fix-decorrelated-jitter-unit-conversion

Conversation

@amalsaidov

Copy link
Copy Markdown

Motivation

Backoff.decorrelatedJittered is meant to produce a decorrelated jitter stream where each backoff is drawn from a range up to 3x the previously selected value, capped at maxMs (see the method javadoc and http://www.awsarchitectureblog.com/2015/03/backoff.html).

The conversion of the previous backoff into nanoseconds has its TimeUnit arguments swapped:

long lastNanos = TimeUnit.MILLISECONDS.convert(lastMs, TimeUnit.NANOSECONDS);

TimeUnit.MILLISECONDS.convert(d, NANOSECONDS) interprets d as nanoseconds and returns milliseconds, so it computes lastMs / 1_000_000. Since lastMs is already in milliseconds, lastNanos collapses to 0 for any sub-second backoff.

With lastNanos stuck at 0, randRange = abs(lastNanos * 3 - startNanos) is always startNanos, so every emitted backoff stays in [startMs, 2 * startMs) no matter how many steps elapse. The stream never grows toward maxMs and the decorrelation the strategy is supposed to provide is lost. The other jitter strategies in the same class (exponentialJittered, equalJittered) convert ms to ns correctly with TimeUnit.NANOSECONDS.convert(..., MILLISECONDS).

Changes

  • Backoff.decorrelatedJittered: convert the previous backoff from milliseconds to nanoseconds using TimeUnit.NANOSECONDS.convert(lastMs, TimeUnit.MILLISECONDS), matching how startMs/maxMs are converted a few lines above.
  • Added TestBackoff.testDecorrelatedJitteredGrowsBeyondStart, which fails before the fix (every backoff capped below 2 * startMs, observed max 19 for startMs=10, maxMs=10000) and passes after.

Verified by building and testing only the affected module:

mvn -pl bookkeeper-common -am test -Dtest=TestBackoff

AI-assisted; directed, reviewed and tested by the author.

The decorrelated jitter strategy derives each step's range from the
previously selected backoff (up to 3x it, capped at maxMs). The
conversion of the previous value swapped the TimeUnit arguments:

  TimeUnit.MILLISECONDS.convert(lastMs, TimeUnit.NANOSECONDS)

treats lastMs (already in milliseconds) as nanoseconds, so lastNanos
collapses to lastMs / 1_000_000, which is 0 for any sub-second backoff.
As a result randRange stays fixed at startNanos and the backoff never
grows past 2 * startMs, so it never approaches maxMs and loses the
decorrelation the strategy is meant to provide.

Convert milliseconds to nanoseconds instead, matching how startMs and
maxMs are converted a few lines above. Added a test that pins the
regression: with a large max the sequence must climb past 2 * startMs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants