Skip to content

Retry transient HTTP errors with backoff and jitter - #373

Open
sapols wants to merge 2 commits into
IMAP-Science-Operations-Center:mainfrom
sapols:retry-transient-http-errors
Open

sapols wants to merge 2 commits into
IMAP-Science-Operations-Center:mainfrom
sapols:retry-transient-http-errors

Conversation

@sapols

@sapols sapols commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Change Summary

Overview

Closes #372.

Uses the Retry proposed in the issue, so a GET or HEAD that gets a 429 or 5xx response is retried with exponential backoff and jitter instead of failing. That covers every request made through _make_request, including spice_query, release, and the webpoda queries. The one addition is _LoggingRetry, a small Retry subclass that logs each retried error response as a warning, like the issue asks for; a plain Retry would log those only at debug level, so job logs wouldn't show them otherwise.

Worth knowing:

  • Upload PUTs are no longer resent if the connection closes before S3 responds. The companion imap_processing PR, Retry uploads after a dropped connection imap_processing#3626, retries the whole upload instead and has merged. FYI, imap_L3_processing also uploads through imap-data-access, with no retry of its own, but it pins 1.2.0, so it'll keep the resend until someone bumps it.
  • release is a GET, so it's retried too. A retry after a release that committed but still returned an error would also release any version that arrived in between.
  • Declares urllib3>=2.0, which backoff_jitter needs. imap_processing and sds-data-manager already lock urllib3 2.7.0.
  • A request that keeps failing takes longer to raise: about 3.5 minutes for a query that times out server-side.
  • The retry count and backoff aren't configurable yet, which the issue suggested considering; that can follow if needed.

File changes

  • imap_data_access/io.py: the retry policy and _LoggingRetry.
  • pyproject.toml: urllib3>=2.0.
  • tests/test_io.py: 8 tests that run the real retry loop, mocking only urllib3's connection pool.

Testing

All 159 tests pass, including at the urllib3 2.0 floor. The new retry tests fail against the old adapter.

HTTPAdapter(max_retries=3) retries connection and read errors but not
error responses (apart from 413/429/503 with a Retry-After header), so
a single 503 from the API failed the request, and with it the
processing job that made it. Retry GET and HEAD requests that get a
429/500/502/503/504 response up to 5 times with exponential backoff
and jitter, and log a warning each time an error response is retried.

Only GET and HEAD are retried after an error response or a read error,
because resending an upload or a reprocess request has side effects;
an upload PUT is no longer resent after a read error, such as the
connection closing before the response arrives. raise_on_status=False
keeps the "<status> <reason>: <body>" error once the retries run out.
backoff_jitter needs urllib3 2.0, which is now a declared dependency.
@sapols

sapols commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Whelp. The red CI here isn't from this change. GitHub is moving ubuntu-latest to Ubuntu 26.04, and setup-python has no Python 3.9 build for it, so test (ubuntu-latest, 3.9) fails at setup and fail-fast cancels the other 14 jobs. I opened #374 for it, and I propose #375 as a fix that lets us keep testing Python 3.9. Alternatively, we could use this as an excuse to drop support for Python 3.9, but that'd be a bigger decision for the team (and I suspect we're not yet ready to drop 3.9).

Once we collectively decide on the right fix, I'll update this branch so CI reruns.

@sapols
sapols marked this pull request as ready for review November 5, 2026 02:00
@sapols
sapols requested a review from tmplummer November 5, 2026 02:13

@tmplummer tmplummer 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.

This is definitely outside my wheel house but the changes look fine to me.

sapols added a commit to IMAP-Science-Operations-Center/imap_processing that referenced this pull request Nov 6, 2026
When an upload's PUT loses its connection before S3 responds, urllib3
resends it today. IMAP-Science-Operations-Center/imap-data-access#373
stops that resend, since resending an upload blindly has side effects,
so with that change alone, the "Connection aborted" error would reach
upload_products and fail the job. Retry one level up instead: treat
that error like a 503 SlowDown and run the whole upload again, which
first checks whether S3 already has the file (409 FileAlreadyExists),
and exit with RETRY_EXIT_CODE if it keeps failing.
@sapols

sapols commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

IMAP-Science-Operations-Center/imap_processing#3626 has merged, so imap_processing's dev already has the upload retry, and this can be released whenever it merges. Once #375 (dropping Python 3.9) merges, I'll update this branch so CI reruns.

Added Bryan and Luisa as reviewers in case this is more in their wheelhouse. Open to suggestions if anyone else would be a better reviewer.

@sapols
sapols requested a review from tmplummer November 6, 2026 16:52
@sapols

sapols commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Tests are passing again now that we've dropped Python 3.9

This branch has not been deployed

No deployments
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.

Retry transient HTTP errors (429/5xx) with backoff in _make_request

2 participants