benbjohnson/litestream · error

fetch page: %w

Error message

fetch page: %w

What it means

FetchPage retrieves a page's bytes from the replica at a recorded level/txid/offset. On a non-retryable error the read fails immediately with "fetch page: %w"; only transient errors are retried (and eventually surface as BusyError). This error wraps the underlying storage/transport failure verbatim.

Source

Thrown at vfs.go:1599

				p[i] = 0
			}
			return len(p), nil
		}
		f.logger.Error("page not found", "page", pgno)
		return 0, fmt.Errorf("page not found: %d", pgno)
	}

	var data []byte
	var lastErr error
	ctx := f.ctx
	for attempt := 0; attempt < pageFetchRetryAttempts; attempt++ {
		_, data, lastErr = FetchPage(ctx, f.client, elem.Level, elem.MinTXID, elem.MaxTXID, elem.Offset, elem.Size)
		if lastErr == nil {
			break
		}
		if !isRetryablePageError(lastErr) {
			f.logger.Error("cannot fetch page", "page", pgno, "attempt", attempt+1, "error", lastErr)
			return 0, fmt.Errorf("fetch page: %w", lastErr)
		}

		if attempt == pageFetchRetryAttempts-1 {
			f.logger.Error("cannot fetch page after retries", "page", pgno, "attempts", pageFetchRetryAttempts, "error", lastErr)
			return 0, sqlite3vfs.BusyError
		}

		delay := pageFetchRetryDelay * time.Duration(attempt+1)
		f.logger.Warn("transient page fetch error, retrying", "page", pgno, "attempt", attempt+1, "delay", delay, "error", lastErr)

		timer := time.NewTimer(delay)
		select {
		case <-timer.C:
		case <-f.ctx.Done():
			timer.Stop()
			return 0, fmt.Errorf("fetch page: %w", lastErr)
		}
		timer.Stop()

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Inspect the wrapped cause: 404 → object gone, 403 → credentials, range error → corrupt index.
  2. Fix credentials/bucket permissions so GetObject (with Range) succeeds.
  3. Rebuild the page index (ResetTime) if index offsets no longer match stored objects.
  4. Prevent lifecycle deletion of in-use LTX files or disable client-side retention if a cloud lifecycle policy manages cleanup.

Example fix

// before: lifecycle deletes objects the index still references
Lifecycle: { Expire: "1d" } // index entries older than 1d 404

// after: manage cleanup with litestream retention, not storage expiry
// (remove bucket lifecycle rule; keep Store.RetentionEnabled default)
Defensive patterns

Strategy: retry

Validate before calling

// preflight: credentials and object reachability
if _, err := client.Head(ctx, objectPath); err != nil {
    return fmt.Errorf("replica object unavailable before read: %w", err)
}

Try / catch

if _, err := file.ReadAt(p, off); err != nil {
    var s3err awserr.RequestFailure
    if errors.As(err, &s3err) && s3err.StatusCode() == 404 {
        // LTX object gone: rebuild index or re-replicate
    }
}

Prevention

When it happens

Trigger: A page read requiring a remote fetch where the replica client returns a permanent error: 404 (object deleted), 403 (bad credentials), range errors from a corrupt offset/size in the index, or malformed responses.

Common situations: Lifecycle policies deleting LTX objects while the index still references them; expired/rotated cloud credentials; provider incompatibility (non-S3 endpoints rejecting Range requests); corrupted index offsets after a bad rebuild.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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