Stream snapshot download without storing the archive - #12862
Conversation
The 2.5x guard ran at the extraction stage, after the tarball was already on disk, so passing it effectively required ~3.5x the snapshot in volume space. Probe the snapshot size upfront and demand the remaining download bytes plus a 1.5x extraction estimate before downloading; at extraction demand only the 1.5x estimate. The pre-check runs outside the retry loop so low disk aborts instead of retrying forever.
|
Claude finished @svlachakis's task in 12m 46s —— View job Review: streaming snapshot download
The core streaming machinery holds up. I worked through the producer/consumer protocol specifically looking for ordering and buffer-lifetime bugs and did not find one: chunk indices are handed out monotonically, Findings below are mostly about behaviour at the edges — what happens when the network stalls, when the source is hostile, and when the operator mis-sets a knob. High1. The 23 new tests never run in CI. Medium2. No stall detection; timeouts aren't retryable. 3. 4. Verify-then-extract is inverted, and the extractor doesn't validate links. Streaming necessarily writes tar entries before the checksum is known, and 5. 6. A thrice-rotated source aborts node startup and leaves the third attempt's partial DB behind, while corrupt archives and checksum mismatches are deliberately downgraded to "delete and keep running". Details Low7. A 8. The decompressor is chosen from 9. When the server reports no length, truncation is undetected and 10. The 11. 12. Two smaller items I didn't file inline: NotesI could not build or run the test suite in this environment ( |
Stall timeout on body reads with retry, reject tar link entries, keep a fully downloaded archive and extract it two-phase, bound StreamingConnections to 16, require a checksum when the size is unknown, graceful stop after repeated source changes
|
@claude review again, I adapted the comments that were valid. |
|
Claude finished @svlachakis's task in 6m 45s —— View job Re-review: streaming snapshot download
Reviewed Prior findings — status
High1. The test suite still never runs in CI. - Nethermind.Init.Snapshot.TestI'm unable to modify files under Medium2. 3. Streaming silently loses all progress across a node restart. The checkpoint is never advanced below Low4. 5. The two behaviours the hardening commit introduced at the initializer level — exhausted source-change restarts returning gracefully, and the stall timeout being retried rather than fatal — are the two without tests. 6. Carried over unchanged, all still minor: the parallel path still doesn't prefix-skip a 7. Neither path caps total retry time — an unreachable host retries forever at a 300 s ceiling and node startup never completes. Pre-existing in Notes
Nothing blocks merge on correctness grounds. Finding 1 is mechanical and finding 3 is a docs change; finding 2 is a one-line hardening I'd take before this ships to operators on slow links. • branch |
Explicit infinite HttpClient timeout with a 100s header guard so body reads are governed only by the stall timeout, one stall CTS per response instead of per read, document that a node restart restarts the download, cover exhausted restarts and stalls
|
@claude review again |
|
Claude finished @svlachakis's task in 7m 35s —— View job Re-review: streaming snapshot download
Reviewed Two new tests are the right ones: Prior findings — status
Medium1. The two-phase downloader shares this client and has no stall timeout of its own. 2. Low3. Disarming the reused stall source with 4. The rangeless path still sizes 5. Carried over unchanged, all still minor: the parallel path doesn't prefix-skip a Notes
Nothing here blocks merge on correctness. Finding 1 is the one I'd take before this ships, and it's a fix to the default path rather than to the new feature. |
Shared StallGuardedReader (TryReset, no per-read CTS, no disarm race) used by streaming, skip, and the two-phase downloader; probe retried with backoff instead of failing startup on one blip
|
@claude review again. |
|
Claude finished @svlachakis's task in 6m 8s —— View job Re-review: streaming snapshot download
Reviewed I chased the one thing I'd flagged as a possible regression — whether One design detail I liked on re-reading:
Prior findings — status
Also confirmed from earlier rounds: Low1. 2. The 2-minute stall budget now exists as two independent literals ( 3. Retry-forever is now uniform across probe, chunk fetch, sequential read, and the two-phase download — which is what I asked for, but it means an unreachable host makes node startup hang indefinitely (warning every ≤300 s) where the probe previously aborted it. That's the better default for a bootstrap-only step; still the strongest argument for the overall deadline in finding 6, and still out of scope here. 4. Carried over unchanged, all minor: the parallel path doesn't prefix-skip a 5. Notes
No Critical, High, or Medium findings remain. From my side this is ready. |
An extraction that writes zero files (StripComponents mismatch) now fails instead of completing, a completed checkpoint with a missing or empty database directory reinitializes instead of skipping, and a mounted-but-empty db directory (lost+found only) no longer counts as an existing database
|
@claude review again |
|
Claude finished @svlachakis's task in 5m 14s —— View job Re-review: streaming snapshot download
Reviewed
I also re-derived the One thing I verified rather than assumed: rewinding the checkpoint to Prior findings — status
Low1. The new checkpoint rewind can discard a complete archive under 2. The emptiness guard is tar-only, and zip is now worse than before. 3. 4. Carried over unchanged, all minor: 5. Test coverage for Notes
No Critical, High, or Medium findings. All three new items are narrow edges of an otherwise good hardening commit. • branch |
Checkpoint rewind keeps a complete archive (Downloaded, not Started), the empty-extraction guard covers zip and tar in one place using the database-exists predicate, and DatabaseExists tolerates unreadable directories
Changes
Snapshot.Streaming(defaultfalse): the snapshot is downloaded and extracted in a single pass, without ever storing the archive on disk. Peak disk usage drops from archive plus extracted database to the extracted database alone (for the mainnet archive snapshot: ~2.4TB instead of ~4.4TB).Snapshot.StreamingConnections(default 4) parallel connections fetch 64 MiB ranges and deliver them in order into a live zstd/gzip-to-tar extractor. Memory is bounded by the connection window; nothing is buffered to disk.Range/If-Rangefrom the exact byte, including partial resume inside a chunk. A200response to a range request is handled by skipping the already-delivered prefix instead of feeding duplicate bytes to the decompressor; a changedETagor content length aborts and restarts the download cleanly (up to 3 attempts). Servers without range support fall back to a single connection that resumes by re-reading and skipping.Snapshot.Checksumafter extraction. On a checksum mismatch or a corrupt archive the extracted database is deleted and the node continues running, matching the two-phase behavior.Contains #12861
Types of changes
What types of changes does your code introduce?
Testing
Requires testing
If yes, did you write tests?
Notes on testing
23 tests against a local HTTP server with injected faults: chunked and sequential delivery with exact byte and hash verification, every range dropped mid-response once, servers that ignore range requests (resume by skipping), source rotation detected via
ETagand via content length when noETagis present, permanent HTTP errors aborting instead of retrying, corrupt archives deleting the partial database without failing node startup, checksum mismatch, insufficient disk space, zip rejection, and end-to-end runs throughInitDatabaseSnapshotwithStreaming=true.Documentation
Requires documentation update
Requires explanation in Release Notes
Snapshot.Streaming=truestreams the snapshot directly into the database directory, removing the need to provision disk space for the archive on top of the extracted database.