benbjohnson/litestream · error

download snapshot: %w

Error message

download snapshot: %w

What it means

RestoreV3 downloads and decompresses the selected v0.3.x snapshot into a temporary file via r.downloadSnapshotV3; any failure (network read, storage get, decompression, checksum) is wrapped as 'download snapshot: %w'. The temp file is removed on failure so no partial output remains at OutputPath.

Source

Thrown at replica.go:1147

	r.Logger().Debug("found v0.3.x WAL segments", "n", len(segments))

	// Create parent directory if it doesn't exist.
	var dirInfo os.FileInfo
	if db := r.DB(); db != nil {
		dirInfo = db.DirInfo()
	}
	if err := internal.MkdirAll(filepath.Dir(opt.OutputPath), dirInfo); err != nil {
		return fmt.Errorf("create parent directory: %w", err)
	}

	// Create temp file for restore.
	tmpPath := opt.OutputPath + ".tmp"
	defer func() { _ = os.Remove(tmpPath) }()

	// Download and decompress snapshot.
	if err := r.downloadSnapshotV3(ctx, client, snapshot.Generation, snapshot.Index, tmpPath); err != nil {
		return fmt.Errorf("download snapshot: %w", err)
	}

	// Apply WAL segments.
	if err := r.applyWALSegmentsV3(ctx, client, snapshot.Generation, snapshot.Index, segments, tmpPath); err != nil {
		return fmt.Errorf("apply WAL segments: %w", err)
	}

	// Rename to final path.
	if err := os.Rename(tmpPath, opt.OutputPath); err != nil {
		return fmt.Errorf("rename to output path: %w", err)
	}
	if err := internal.FsyncDir(filepath.Dir(opt.OutputPath)); err != nil {
		return fmt.Errorf("sync restore output dir: %w", err)
	}

	if opt.IntegrityCheck != IntegrityCheckNone {
		if err := checkIntegrity(ctx, opt.OutputPath, opt.IntegrityCheck); err != nil {
			if ctx.Err() == nil {

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Inspect the wrapped cause to distinguish network error vs decompression/corruption error
  2. Retry the restore with a longer timeout / more stable connection; downloads are idempotent (temp file is discarded)
  3. Verify the snapshot object's integrity in the backend; if corrupted, restore from an earlier snapshot via RestoreOptions.Timestamp
  4. Increase client timeouts or use a replica client closer to the storage region

Example fix

// before
ctx := context.Background()
err := replica.Restore(ctx, opt) // times out on big snapshot
// after
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
defer cancel()
err := replica.Restore(ctx, opt)
Defensive patterns

Strategy: retry

Validate before calling

// preflight: HEAD the snapshot object to ensure it is downloadable and sized as expected
if _, err := client.SnapshotsV3(ctx, generation); err != nil {
    return fmt.Errorf("snapshot not reachable: %w", err)
}

Try / catch

if err := replica.Restore(ctx, opt); err != nil {
    if strings.Contains(err.Error(), "download snapshot") {
        log.Printf("snapshot download failed, cause=%v", errors.Unwrap(err))
        return retryWithBackoff(ctx, 3, func() error { return replica.Restore(ctx, opt) })
    }
    return err
}

Prevention

When it happens

Trigger: Calling Replica.Restore when fetching the snapshot object from the replica client fails: backend read error mid-stream, context cancellation/timeout during a large download, corrupted snapshot data that fails decompression, or the snapshot object was deleted between listing and download.

Common situations: Large databases and short HTTP timeouts/proxy idle cutoffs; flaky VPN/network during DR; S3 throttling on big GETs; corrupted snapshot object in storage; restore interrupted by ctx cancellation from the caller.

Related errors


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/a07c39510a7761e3. Report an issue: GitHub.