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
- Inspect the wrapped cause: 404 → object gone, 403 → credentials, range error → corrupt index.
- Fix credentials/bucket permissions so GetObject (with Range) succeeds.
- Rebuild the page index (ResetTime) if index offsets no longer match stored objects.
- 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
- Rotate credentials before expiry; test GetObject (with Range) access.
- Disable storage lifecycle rules that delete LTX objects referenced by the index.
- Review docs/PROVIDER_COMPATIBILITY.md for S3-compatible endpoints with Range quirks.
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
- fetch page index: %w
- fetch header: %w
- abs: cannot delete ltx file %q: %w
- abs: cannot list blobs: %w
- abs: cannot delete blob %q: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/f38cdd8d1d30936b.
Report an issue: GitHub.