Skip to content

[CELEBORN-2470] Add backoff with jitter to push retries and log hard split once per file - #3856

Closed
venkata91 wants to merge 6 commits into
apache:mainfrom
venkata91:celeborn-push-retry-backoff
Closed

venkata91 wants to merge 6 commits into
apache:mainfrom
venkata91:celeborn-push-retry-backoff

Conversation

@venkata91

@venkata91 venkata91 commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

JIRA: CELEBORN-2470

Summary

Push retries currently run immediately and can synchronize across many tasks, increasing connection and DNS load when a worker is unhealthy. Hard splits can also emit the same INFO log repeatedly while pushes continue targeting the old file.

This PR:

  • adds exponential backoff with jitter to push retries
  • logs the hard-split cause once per PartitionDataWriter, for both disk and memory files
  • moves the repeated merged-push hard-split message to DEBUG

Configuration

Config Default
celeborn.client.push.retry.backoff.initial 500ms
celeborn.client.push.retry.backoff.max 10s
celeborn.client.push.retry.backoff.jitter 0.5

Setting celeborn.client.push.retry.backoff.initial to 0 disables backoff. celeborn.client.push.retry.backoff.max caps the final delay, including jitter, so with the defaults a retry at the cap waits between 5s and 10s.

Tests

  • added coverage for exponential growth, maximum delay, jitter bounds (including at the cap), disabled backoff, and large retry counts
  • added callback-level coverage for scheduled single and merged HARD_SPLIT retries and revive completion during backoff
  • added concurrent coverage verifying the hard-split log latch triggers once per writer
  • added coverage that repeated memory-shuffle HARD_SPLIT checks log only once

venkata91 and others added 2 commits September 20, 2026 13:55
A hard split does not shrink the partition file, so the file keeps failing
the threshold check in checkDiskFullAndSplit until a new epoch propagates to
every pusher. Until then each concurrent push to that file logged the whole
multi-line "CheckDiskFullAndSplit hardSplit" INFO again. On a cluster where
celeborn.worker.shuffle.partitionSplit.max equalled
celeborn.client.shuffle.partitionSplit.threshold, which makes the SOFT branch
unreachable and every split hard, this produced 20.2M log events in 7 minutes
from a handful of wedged partitions.

Latch the log on the PartitionDataWriter so the cause is recorded once per
split file. The latch lives and dies with the writer, so it needs no cleanup
hook. WorkerSource.WRITE_DATA_HARD_SPLIT_COUNT already carries the rate.

Also fix the sibling warning in handlePushMergedData, which fired once per
hard-split partition per merged push and attributed every hard split to a
full disk. checkDiskFullAndSplit returns HARD_SPLIT for three reasons and
only one of them is disk pressure, so the message misdirected triage. It
carries nothing the INFO does not, and the counter on the next line already
covers rate, so it moves to debug.

Behavior is unchanged, this is log noise only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Push data retries are resubmitted to pushDataRetryPool immediately. The
dueTime computed at each site is not a retry delay: it is the deadline for
the revive busy-wait inside the retry task, so the effective retry cadence
is bounded only by celeborn.client.push.revive.interval, 100ms by default.
Every map task blocked on the same partition therefore retries in lockstep,
and each retry calls dataClientFactory.createClient, so a stuck partition
turns into a synchronised burst of connection setup against one worker.

This is load-bearing rather than cosmetic on the HARD_SPLIT path, because
the two HARD_SPLIT sites pass remainReviveTimes through without decrementing
it. A partition wedged in HARD_SPLIT never exhausts its revive budget, so
the only brake in the system never engages and the retry loop is unbounded.

Schedule retries with an exponentially growing delay, capped and jittered.
pushDataRetryPool becomes a ScheduledExecutorService so the delay costs no
pool thread; sleeping in the task would be worse than the problem, since the
pool defaults to 8 threads that already block on the revive wait.

Backoff applies to all five retry sites, not just HARD_SPLIT. The failure
sites are reached when a worker is already sick, and retrying an unreachable
worker with no delay is what turns one bad worker into a connection storm.

Three new configs, all client side:
  celeborn.client.push.retry.backoff.initial  500ms, 0 disables
  celeborn.client.push.retry.backoff.max      10s, well under the 120s
                                              push data timeout
  celeborn.client.push.retry.backoff.jitter   0.5, equal jitter so the delay
                                              never collapses to zero

Not included, deliberately: making the HARD_SPLIT sites decrement
remainReviveTimes would bound those retries and start failing tasks. That
is a behaviour change and deserves its own ticket. The merged-data
HARD_SPLIT site also has no per-batch attempt counter to grow from, so it
takes the initial delay with jitter; threading a counter through
doPushMergedData is left as follow-up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gauravkm

Copy link
Copy Markdown
Contributor

Looks good from the code review; no P0/P1 issues found. I checked single and merged push retries, revive deadlines and retry budgets, and the atomic per-writer log guard, which leaves split decisions and counters unchanged. Existing retry/split tests do not cover the new backoff timing or log latch yet, and CI currently shows only the two passing label checks.

@venkata91
venkata91 marked this pull request as ready for review October 1, 2026 04:36
@venkata91

Copy link
Copy Markdown
Contributor Author

Looks good from the code review; no P0/P1 issues found. I checked single and merged push retries, revive deadlines and retry budgets, and the atomic per-writer log guard, which leaves split decisions and counters unchanged. Existing retry/split tests do not cover the new backoff timing or log latch yet, and CI currently shows only the two passing label checks.

Addressed the review comment by adding necessary tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@gauravkm

gauravkm commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Thanks for adding the tests. Looks good from the code review; no P0/P1 issues found. I checked the shared backoff helper across single and merged retries, including revive deadlines and retry budgets. The new tests cover exponential growth, the cap, jitter, disabled backoff, large retry counts, and concurrent access to the per-writer log latch. They do not exercise scheduled retry/deadline integration. CI currently shows only the two passing label checks, with no build or test results yet.

@venkata91 venkata91 changed the title [CELEBORN-XXXX] Add backoff with jitter to push retries and log hard split once per file [CELEBORN-2470] Add backoff with jitter to push retries and log hard split once per file Oct 1, 2026
@venkata91

Copy link
Copy Markdown
Contributor Author

They do not exercise scheduled retry/deadline integration.
@gauravkm can you elaborate what do you mean by that?

@gauravkm

gauravkm commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

I meant that testPushRetryBackoff calls pushRetryBackoffMs() directly: it checks the calculated delay, but does not exercise the push callback and scheduled retry together. For example, a focused test could trigger a failed/HARD_SPLIT push with jitter disabled, check the scheduled delay, then control revive completion and verify that successful completion clears the tracked batch or exhausted retries surface the expected failure.

Single pushes calculate the revive deadline before scheduling; merged pushes calculate it inside the scheduled task, so revive completion during backoff versus remaining pending at the deadline are useful cases to cover. This was a coverage suggestion, not a newly identified correctness issue. The helper and latch tests you added are useful.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@gauravkm

gauravkm commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Looks good from the code review; no P0/P1 issues found. I checked that the shared scheduling helper preserves all five retry paths. The new tests address the callback-wiring part of my suggestion: they check the requested 100 ms delay, complete revive before invoking the captured retry, and verify both single and merged resends, plus single-push cleanup. Deadline expiry and retry exhaustion remain outside this coverage. CI currently shows passing label checks only; seven workflows await maintainer approval, so there are no build/test results yet.

@zaynt4606

Copy link
Copy Markdown
Contributor

Thanks for adding the retry tests. I noticed two remaining edge cases:

  1. pushRetryBackoffMs() caps the base delay before applying jitter. With backoff.max=10s and the default jitter of 0.5, the scheduled delay can reach 15s. Could we cap the final delay and test jitter at the cap, or clarify that backoff.max only bounds the base delay?
  2. checkDiskFullAndSplit() still logs memory-shuffle HARD_SPLIT at INFO on every call. The new shouldLogHardSplit() guard only covers the disk-file branch. Could we apply it to the memory branch as well and test repeated calls?

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@zaynt4606

Copy link
Copy Markdown
Contributor

Thanks for addressing both review points. One small documentation follow-up: the description of celeborn.client.push.retry.backoff.jitter still says that a value of 0.5 spreads retries over [0.5, 1.5] times the computed delay. With the new final cap, once the base delay reaches backoff.max (10s by default), the actual range is 5–10s, rather than 5–15s. Could you update the config description in CelebornConf.scala and regenerate docs/configuration/client.md to reflect the capped range? The implementation and new cap test look good.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@venkata91

Copy link
Copy Markdown
Contributor Author

@zaynt4606 thanks, good catch. Updated in 79fa9fd: the jitter description now says the range is capped at backoff.max (e.g. [5s, 10s] once the computed delay reaches the default 10s), backoff.max now says it bounds the delay including jitter, and docs/configuration/client.md is regenerated. I also updated the PR description to mention the cap and the tests added in the last round.

@zaynt4606 zaynt4606 closed this in b34cd83 Oct 13, 2026
@zaynt4606

Copy link
Copy Markdown
Contributor

Thanks @venkata91 for the contribution!
Merged to main(v1.0.0).

@zaynt4606 zaynt4606 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants