Skip to content

feat(io): add retry support for OpenDAL stores - #8363

Open
ddupg wants to merge 1 commit into
lance-format:mainfrom
ddupg:fix/ddu-338-opendal-retry
Open

feat(io): add retry support for OpenDAL stores#8363
ddupg wants to merge 1 commit into
lance-format:mainfrom
ddupg:fix/ddu-338-opendal-retry

Conversation

@ddupg

@ddupg ddupg commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Problem

OpenDAL-backed object stores currently propagate temporary backend errors directly. In distributed writes, transient throttling such as HTTP 429 can fail the entire job even though retrying the request would succeed.

Changes

  • Add a shared OpenDAL RetryLayer to S3, Azure Blob/ADLS, GCS, TOS, OSS, COS, Hugging Face, and GooseFS stores.
  • Reuse client_max_retries (including OBJECT_STORE_CLIENT_MAX_RETRIES); the existing default is 3 and 0 disables retries.
  • Retry only errors marked temporary by OpenDAL, with exponential backoff starting at 100 ms, factor 2, a 15-second maximum base delay, and jitter.
  • Preserve the retry configuration when dynamic TOS, OSS, or Hugging Face stores are rebuilt.
  • Add fault-injection coverage for retry success, persistent errors, disabled/exhausted retries, writer payload replay, and writer close.

Testing

  • cargo fmt --all --check
  • cargo test -p lance-io --lib --all-features -- --test-threads=1 --skip uring::tests
  • cargo clippy -p lance-io --all-features --tests --benches -- -D warnings

@github-actions github-actions Bot added A-encoding Encoding, IO, file reader/writer enhancement New feature or request labels Aug 7, 2026
@ddupg
ddupg marked this pull request as ready for review August 7, 2026 07:32

@lance-gatekeeper lance-gatekeeper Bot 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.

Gate recommendation: request changes.

Retrying at the OpenDAL operator boundary is the right direction, but this revision does not yet satisfy the cross-provider client retry contract. A viable revision should pair the shared layer with provider-level throttle classification and tests, preferably fixed upstream, and enforce the existing client_retry_timeout as an elapsed-time budget.

.with_factor(2.0)
.with_jitter();

operator.layer(retry_layer)

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.

OpenDAL 0.58.1 RetryLayer only retries errors that the service marks temporary. The pinned Azblob, Azdls, OSS, COS, and Hugging Face parsers classify HTTP 429 as persistent, so this layer makes only one request for the motivating throttle failure on five advertised HTTP backends; OSS, COS, and Hugging Face also have no outer AIMD fallback. Please fix or upgrade those provider classifiers, or add equivalent classification before this layer, and cover each affected provider.

Reproducer

I pointed an Azblob operator wrapped with finish_opendal_operator(operator, 3) at a local endpoint that always returns 429 and ran:

CARGO_TARGET_DIR=/home/agent/tmp/gate-8363-target-92801910180 cargo test -p lance-io reproducer_azblob_429_is_not_retried --lib -- --nocapture

Expected four requests before exhaustion; observed one request and a persistent, non-temporary error. The disposable test passed those assertions.

}

let retry_layer = RetryLayer::new()
.with_max_times(max_retries)

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.

client_retry_timeout is a documented general object-store retry bound and is honored by native S3, Azure, and GCS, but this helper receives only the retry count. An OpenDAL request can therefore keep backing off long after its configured timeout. Please pass the timeout into the shared construction and enforce an elapsed-time budget, with a paused-time regression.

Reproducer

With paused time, client_max_retries=10, client_retry_timeout=1, and four injected temporary stat failures, I ran:

CARGO_TARGET_DIR=/home/agent/tmp/gate-8363-target-92801910180 cargo test -p lance-io reproducer_client_retry_timeout_is_ignored --lib -- --nocapture

Expected the one-second budget to stop before attempt five; observed attempt five succeed, stat_attempts == 5, and virtual elapsed time greater than one second. The disposable test passed those assertions.

@westonpace westonpace left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks pretty cool for opendal. I wonder if we can add support for AIMD (feel free to do this in a future PR). The way non-dal object stores work is that we first retry 3 times (dependent on client_max_retries) and then we trigger the aimd throttle.

I think all we need to do is wrap the object store with AimdThrottledStore and then update the logic on is_throttle_error to recognize opendal temporary errors.

This will add an additional 3 outer retries (configurable via LANCE_AIMD_MAX_RETRIES) for a total of 9 retries. The outer AIMD retries will both:

  • Cut the rate in half (it will grow back eventually)
  • Have a different delay (on the order of 100-300ms)

Anyways, all of this is fine for future PRs. I think we can merge this once gatekeeper is happy.

Comment on lines +19 to +22
.with_min_delay(Duration::from_millis(100))
.with_max_delay(Duration::from_secs(15))
.with_factor(2.0)
.with_jitter();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice if these were configurable, at least via environment variables. But that can be done in a future PR.

@Xuanwo

Xuanwo commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

I'm thinking of add AIMD in opendal directly

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

Labels

A-encoding Encoding, IO, file reader/writer enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants