Skip to content

fix(discovery): jitter the refresh retry backoff - #616

Merged
EricAndrechek merged 3 commits into
mainfrom
fix/discovery-retry-jitter
Sep 25, 2026
Merged

EricAndrechek merged 3 commits into
mainfrom
fix/discovery-retry-jitter

Conversation

@EricAndrechek

@EricAndrechek EricAndrechek commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Closes #141. Part of #613.

What

SchemaRegistry.RetryRefresh used to sleep exactly 2s * 2^n, capped at 60s. That means N instances retrying against one recovering ClickHouse fire in lockstep once they all reach the cap. With this change, each sleep is a uniform draw from [0, backoff) (full jitter). The bound still doubles from 2s to 60s.

Why full jitter instead of the ±10% the issue proposed: at the 60s cap, ±10% spreads the retries over only 12s. Full jitter spreads them over the whole 60s window. That makes it the strongest way to break lockstep without state or coordination (AWS's "Exponential Backoff and Jitter" analysis). One side effect: the mean wait halves. So during an outage, a failing tenant's retries, its log lines and wavehouse_schema_refresh_failures_total come about twice as often. Nothing in this repo consumes that counter yet (it was new in #610). The CHANGELOG entry mentions this.

The random source is an injectable retryDelay func(time.Duration) time.Duration field (rand.N by default), set up the same way as the existing firstTick. It adds no dependency.

Tests

  • TestRetryRefresh_BackoffIsBounded now records the backoff each sleep is drawn within (1, 2, 4, 4, 4 ms) instead of timing the wall clock, so it can't flake.
  • TestRetryRefresh_SleepsTheJitteredDelay: the loop sleeps the drawn delay, not the backoff.
  • TestRetryRefresh_DelayIsSpreadOverTheBackoff: 200 draws from the production retryDelay all fall in [0, backoff), reach both the bottom and top quarters, and are nearly all distinct.

Left for later

  • The discovery: add jitter to RetryRefresh exponential backoff for clustered mode #141 criterion "confirm the jitter range once clustered mode has a topology config" is conditional and stays with that work.
  • There is no shared backoff helper yet. internal/auth (the JWKS retry) has its own unjittered loop, and feat/ch-error-classes is adding a ClickHouse backoff in internal/ingest. Consolidating them is a follow-up.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EJr5tY4WQUy2sc4MbW67vL

EricAndrechek and others added 2 commits September 24, 2026 23:28
RetryRefresh slept exactly 2s * 2^n capped at 60s, so instances retrying
against one recovering ClickHouse fired in lockstep. Each sleep is now
drawn uniformly below the backoff (full jitter), via an injectable
retryDelay so the tests pin the backoff sequence without wall-clock sleeps.

Closes #141. Part of #613.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJr5tY4WQUy2sc4MbW67vL
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJr5tY4WQUy2sc4MbW67vL
@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: e0581e82-7488-4548-a0f8-f35aba9b5fbc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added documentation Improvements or additions to documentation go Pull requests that update go code area/api HTTP handlers, routing, middleware area/query Structured query AST, SQL builder area/docs Documentation, site/, README area/app Process wiring (internal/app): component build, run, release labels Sep 25, 2026
@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

📚 Docs preview is live → https://00bdb03b-wavehouse-docs.wave-rf.workers.dev

  • Commit — f5d5e04: Merge remote-tracking branch 'origin/main' into fix/discovery-retry-jitter
  • Author — @EricAndrechek
  • Committed — 2026-09-25 18:44 (UTC-04:00)
  • Deployed — 2026-09-25 19:00 EDT

@github-code-quality

github-code-quality Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: Go

Go

The overall line coverage in commit f5d5e04 in the fix/discovery-retry-... branch remains at 94%, unchanged from commit cb78f78 in the main branch.

Show a line coverage summary of the most impacted files.
File main cb78f78 fix/discovery-retry-... f5d5e04 +/-
internal/mq/embedded.go 89% 88% -1%
internal/ingest/worker.go 98% 97% -1%
internal/discov...ry/discovery.go 96% 96% 0%

Updated September 25, 2026 23:01 UTC

@EricAndrechek
EricAndrechek marked this pull request as ready for review September 25, 2026 22:57
@EricAndrechek
EricAndrechek requested review from a team and taitelee September 25, 2026 22:57
@EricAndrechek
EricAndrechek merged commit 73c75ee into main Sep 25, 2026
32 checks passed
@EricAndrechek
EricAndrechek deleted the fix/discovery-retry-jitter branch September 25, 2026 23:12
EricAndrechek added a commit that referenced this pull request Sep 26, 2026
Brings in #647, #632, #616 and #655. One conflict, in config.go: main
dropped the Cache struct's env-default tag while this branch moved Cache
into backends.go; kept the move.

#632 removed every env-default tag. The four *.backend fields and
cache.l1_max_cost this branch declares in backends.go still carried
theirs, so an explicit `backend: ""` would have become the default. Their
defaults now live in defaults(); an empty backend, which Validate refuses,
is pinned next to server.port's zero as a refusal; the docs test converts
a documented default to a named string type such as MQBackend.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
EricAndrechek added a commit that referenced this pull request Sep 26, 2026
Brings in #618 (squash-merged parent), #619, #632, #616, #655 and #647.
Conflicts resolved by keeping main's content plus this branch's coord
changes; the AGENTS.md package count is now twenty (keyenc + coord).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
EricAndrechek added a commit that referenced this pull request Sep 26, 2026
Brings in main via feat/coord-leases: #618's squash, #619, #632, #616,
#655 and #647. Per #632, the roles default moves from its env-default tag
into defaults(): an explicit `roles: []` now reaches Validate (a
refusedZeros entry pins it), and the doc-defaults test parses the roles
cell as a comma-separated list. instance_id's documented default is
*(empty)*, the value in defaults(); Load resolves it to
<hostname>-<8 hex>.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
EricAndrechek added a commit that referenced this pull request Sep 26, 2026
main now carries #612 and #618 as squashes, plus #632, #622, #627,
#615, #619, #647, #616, #655 and #623. The merge was resolved against the
pre-squash #618 head (f129d57) as its base, so main's version wins for
everything this stack does not own and only the cache stack's changes
(#614, #621, #626 as merged here, and this PR) are re-applied on top.

Warnings keeps main's api-role gate for the cache.redis warnings too: a
split's Deployments differ only in roles, so the API's cover the others'.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017aS7rLrH1RKkUMem7X4ckd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api HTTP handlers, routing, middleware area/app Process wiring (internal/app): component build, run, release area/docs Documentation, site/, README area/query Structured query AST, SQL builder documentation Improvements or additions to documentation go Pull requests that update go code

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

discovery: add jitter to RetryRefresh exponential backoff for clustered mode

1 participant