Skip to content

Commit 6d2779d

Browse files
yarikopticclaude
andcommitted
ENH: Improve zarr upload retry resilience and diagnostics
- Batch-level retry backoff: replace linear sleep(1*N) with exponential backoff (5s, 20s, 40s, 80s, 120s) plus random jitter, giving S3 time to recover from throttling - Reduce parallelism on batch retry: halve ThreadPoolExecutor workers on each retry (5→3→2→1), reducing concurrent connections that may trigger S3 prefix-level throttling - Better failure diagnostics in _handle_failed_items_and_raise: log a summary line with failed/total counts, exception types grouped by count, and a "systematic" flag when all failures share the same exception type. This makes it immediately clear whether failures are random (flaky network) or deterministic (server-side issue) Motivated by investigation of #1821 where a previously-aborted OME-Zarr upload consistently fails with ConnectionAbortedError on every retry attempt for all 188 level-0 chunks (100% failure rate, 2259 connection attempts), while level-1 chunks and other fresh zarr uploads succeed fine from the same machine. Co-Authored-By: Claude Code 2.1.81 / Claude Opus 4.6 <noreply@anthropic.com>
1 parent fa33e8d commit 6d2779d

1 file changed

Lines changed: 28 additions & 4 deletions

File tree

dandi/files/zarr.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
from __future__ import annotations
22

33
from base64 import b64encode
4+
from collections import Counter
45
from collections.abc import Generator, Iterator
56
from concurrent.futures import Future, ThreadPoolExecutor, as_completed
67
from contextlib import closing
78
from dataclasses import dataclass, field, replace
89
from datetime import datetime
910
from enum import Enum
1011
import json
12+
import math
1113
import os
1214
import os.path
1315
from pathlib import Path
16+
import random
1417
from time import sleep
1518
from typing import Any, Optional
1619
import urllib.parse
@@ -745,6 +748,7 @@ def mkzarr() -> str:
745748
items_to_upload = list(items)
746749
max_retries = 5
747750
retry_count = 0
751+
current_jobs = jobs or 5
748752
# Add all items to checksum tree (only done once)
749753
for it in items_to_upload:
750754
zcc.add_leaf(Path(it.entry_path), it.size, it.digest)
@@ -774,7 +778,7 @@ def mkzarr() -> str:
774778
r = client.post(f"/zarr/{zarr_id}/files/", json=uploading)
775779

776780
# Upload files in parallel
777-
with ThreadPoolExecutor(max_workers=jobs or 5) as executor:
781+
with ThreadPoolExecutor(max_workers=current_jobs) as executor:
778782
futures = [
779783
executor.submit(
780784
_upload_zarr_file,
@@ -817,14 +821,22 @@ def mkzarr() -> str:
817821
# Prepare for next iteration with retry items
818822
if items_to_upload := retry_items:
819823
retry_count += 1
824+
current_jobs = max(1, math.ceil(current_jobs / 2))
820825
if retry_count <= max_retries:
821826
lgr.info(
822-
"%s: %s got 403 errors, requesting new URLs",
827+
"%s: %s got 403 errors, requesting new URLs"
828+
" (attempt %d/%d, workers: %d)",
823829
asset_path,
824830
pluralize(len(items_to_upload), "file"),
831+
retry_count,
832+
max_retries,
833+
current_jobs,
834+
)
835+
# Exponential backoff with jitter before retry
836+
sleep(
837+
min(2**retry_count * 5, 120)
838+
+ random.uniform(0, 5)
825839
)
826-
# Small delay before retry
827-
sleep(1 * retry_count)
828840

829841
# Check if we exhausted retries
830842
if items_to_upload:
@@ -899,6 +911,18 @@ def _handle_failed_items_and_raise(
899911
# Log all failures
900912
for item, error in failed_items:
901913
lgr.error("Failed to upload %s: %s", item.filepath, error)
914+
915+
# Summary diagnostics
916+
exc_counts = Counter(type(error).__name__ for _, error in failed_items)
917+
exc_summary = ", ".join(f"{k}: {v}" for k, v in exc_counts.most_common())
918+
lgr.error(
919+
"Upload failure summary: %d/%d files failed; exception types: {%s}%s",
920+
len(failed_items),
921+
len(futures),
922+
exc_summary,
923+
" (systematic — all same exception type)" if len(exc_counts) == 1 else "",
924+
)
925+
902926
# Raise the first error
903927
raise failed_items[0][1]
904928

0 commit comments

Comments
 (0)