fix: add retry with backoff to Databento download_quarter

3 retries with exponential backoff (2s, 4s, 8s) for transient
connection failures (IncompleteBody/UnexpectedEof). Partial files
are deleted before retry so skip-existing logic works correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-03-31 22:57:43 +02:00
parent cb8afe3caf
commit 3d63f73c08

View File

@@ -284,6 +284,9 @@ impl DownloadStats {
}
}
/// Maximum retries for transient download failures (connection resets, EOF).
const MAX_RETRIES: u32 = 3;
async fn download_quarter(
client: &mut HistoricalClient,
symbol: &str,
@@ -307,28 +310,51 @@ async fn download_quarter(
let dt_range = date_range(quarter.start, quarter.end)?;
let params = GetRangeToFileParams::builder()
.dataset(dataset.to_owned())
.symbols(vec![symbol.to_owned()])
.schema(schema)
.stype_in(SType::Parent)
.date_time_range(dt_range)
.path(file_path.clone())
.build();
let mut last_err = None;
for attempt in 0..=MAX_RETRIES {
if attempt > 0 {
// Remove partial file from previous failed attempt
if file_path.exists() {
let _ = fs::remove_file(&file_path);
}
let delay = std::time::Duration::from_secs(2u64.pow(attempt));
println!(
" [RETRY] {} attempt {}/{} in {}s",
filename,
attempt + 1,
MAX_RETRIES + 1,
delay.as_secs()
);
tokio::time::sleep(delay).await;
}
let _decoder = client
.timeseries()
.get_range_to_file(&params)
.await
.with_context(|| {
format!(
"Failed to download {} for {}",
quarter.label, symbol
)
})?;
let params = GetRangeToFileParams::builder()
.dataset(dataset.to_owned())
.symbols(vec![symbol.to_owned()])
.schema(schema)
.stype_in(SType::Parent)
.date_time_range(dt_range.clone())
.path(file_path.clone())
.build();
let meta = fs::metadata(&file_path).context("Failed to read downloaded file metadata")?;
Ok(meta.len())
match client.timeseries().get_range_to_file(&params).await {
Ok(_decoder) => {
let meta =
fs::metadata(&file_path).context("Failed to read downloaded file metadata")?;
return Ok(meta.len());
}
Err(e) => {
last_err = Some(anyhow::anyhow!(
"Failed to download {} for {}: {}",
quarter.label,
symbol,
e
));
}
}
}
Err(last_err.unwrap())
}
/// Upload a single file to MinIO/S3 via rclone.
@@ -598,7 +624,7 @@ async fn main() -> Result<()> {
(false, false, size, quarter.label.clone())
}
Err(e) => {
println!(" [FAIL] {} -- {}", filename, e);
println!(" [FAIL] {} -- {:?}", filename, e);
(false, true, 0, quarter.label.clone())
}
}