benbjohnson/litestream · error

read ltx page index: %w

Error message

read ltx page index: %w

What it means

Init reads the whole LTX file to locate the page index; io.ReadAll failures are wrapped as "read ltx page index". This means the object opened successfully but streaming its bytes failed mid-read. The wrapped error carries the actual transport/storage failure.

Source

Thrown at replica_client.go:132

// fetchPageIndexData fetches a chunk of the end of the file to get the page index.
// If the fetch was smaller than the actual page index, another call is made to fetch the rest.
func fetchPageIndexData(ctx context.Context, client ReplicaClient, info *ltx.FileInfo) (io.ReadCloser, error) {
	// Fetch the end of the file to get the page index.
	offset := info.Size - DefaultEstimatedPageIndexSize
	if offset < 0 {
		offset = 0
	}

	f, err := client.OpenLTXFile(ctx, info.Level, info.MinTXID, info.MaxTXID, offset, 0)
	if err != nil {
		return nil, fmt.Errorf("open ltx file: %w", err)
	}
	defer f.Close()

	// If we have read the full size of the page index, return the page index block as a reader.
	b, err := io.ReadAll(f)
	if err != nil {
		return nil, fmt.Errorf("read ltx page index: %w", err)
	}
	// The replica may return fewer bytes than a page index footer occupies, for
	// example if the file is truncated or otherwise corrupt. Slicing the footer
	// off the end unchecked would index past the start of the buffer and panic.
	if len(b) < ltx.TrailerSize+8 {
		return nil, fmt.Errorf("ltx file too short to contain a page index: %d bytes", len(b))
	}

	size := binary.BigEndian.Uint64(b[len(b)-ltx.TrailerSize-8:])
	if off := len(b) - int(size) - ltx.TrailerSize - 8; off > 0 {
		return io.NopCloser(bytes.NewReader(b[off:])), nil
	}

	// Otherwise read the file from the start of the page index.
	f, err = client.OpenLTXFile(ctx, info.Level, info.MinTXID, info.MaxTXID, info.Size-ltx.TrailerSize-8-int64(size), 0)
	if err != nil {
		return nil, fmt.Errorf("open ltx file: %w", err)
	}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Retry the operation; this is usually a transient stream failure (inspect the wrapped %w error).
  2. Check for provider throttling (HTTP 503 SlowDown) and add backoff.
  3. Increase client/proxy read timeouts for large LTX files.
  4. Verify network stability between the host and the storage endpoint.

Example fix

// before: single attempt
b, err := litestream.Init(ctx, client, level, minTXID, maxTXID)
// after: retry transient read failures
var r io.ReadCloser
for i := 0; i < 3; i++ {
    r, err = litestream.Init(ctx, client, level, minTXID, maxTXID)
    if err == nil || !isTransient(err) { break }
    time.Sleep(time.Duration(1<<i) * time.Second)
}
Defensive patterns

Strategy: retry

Validate before calling

if err := pingReplica(ctx, client); err != nil { return fmt.Errorf("replica unreachable: %w", err) }

Try / catch

r, err := litestream.Init(ctx, client, level, minTXID, maxTXID)
if err != nil {
    if isTransientNetErr(err) { r, err = retryWithBackoff(3, func() (io.ReadCloser, error) { return litestream.Init(ctx, client, level, minTXID, maxTXID) }) }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: Calling Init when client.OpenLTXFile succeeds but the subsequent read of the page index block fails — interrupted connection to the replica, aborted range read, or storage backend I/O error while reading the object body.

Common situations: Flaky network to S3/GCS/Azure during restore; proxy or load balancer cutting long reads; provider throttling mid-stream; oversized LTX files hitting read timeouts.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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