benbjohnson/litestream · error

max retries exceeded reading ltx file (level=%d, min=%s, max

Error message

max retries exceeded reading ltx file (level=%d, min=%s, max=%s, offset=%d): %w

What it means

ResumableReader.readAt retries failed reads of an LTX file from replica storage up to resumableReaderMaxRetries times. When every attempt fails (typically due to provider throttling or transient storage errors), it wraps the last error in this message with the level, TXID range, and byte offset that could not be read. It signals that replication/restore reading of this LTX segment must stop because storage is persistently unavailable.

Source

Thrown at internal/resumable_reader.go:165

		return r.rc.Close()
	}
	return nil
}

func (r *ResumableReader) close() {
	// The stream is already being discarded after a read failure, so a close
	// error should not stop recovery. Log it only to aid debugging.
	if err := r.rc.Close(); err != nil {
		r.logger.Debug("close ltx file",
			"level", r.level, "min", r.minTXID, "max", r.maxTXID,
			"offset", r.offset, "error", err)
	}
}

func (r *ResumableReader) retry(err error) error {
	r.retryN++
	if r.retryN > resumableReaderMaxRetries {
		r.err = fmt.Errorf("max retries exceeded reading ltx file (level=%d, min=%s, max=%s, offset=%d): %w",
			r.level, r.minTXID, r.maxTXID, r.offset, err)
		return r.err
	}

	// Wait before the caller reopens. Retrying with no delay lands every
	// attempt inside the same provider throttle window, so the attempts are
	// spent without the provider ever getting a chance to recover.
	select {
	case <-r.ctx.Done():
		r.err = r.ctx.Err()
		return r.err
	case <-time.After(resumableReaderBackoff << (r.retryN - 1)):
	}
	return nil
}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Check the wrapped inner error (%w) to find the root cause — it is usually a storage-provider error, not an LTX problem
  2. Verify the LTX object still exists in the replica storage for the reported level/TXID range
  3. Back off and retry the operation later if the cause is provider throttling (503/SlowDown)
  4. Check network connectivity / egress limits between the host and the replica provider
  5. If objects are persistently missing or corrupt, run `litestream reset` to clear local LTX state and resync

Example fix

// caller side: tolerate transient storage errors with outer backoff
// before
err := reader.Read(buf)
// after
var maxRetriesErr *fmt.Errorf
err := backoff.Retry(func() error {
	err = reader.Read(buf)
	if err != nil && strings.Contains(err.Error(), "max retries exceeded reading ltx file") {
		return backoff.Permanent(err) // storage unavailable; outer alert/recover
	}
	return err
}, backoff.NewExponentialBackOff())
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check replica reachability before long reads
if err := db.Sync(ctx); err != nil {
	log.Printf("replica unreachable, deferring read: %v", err)
}

Type guard

// Check availability through the client before bulk reads
if err := client.Init(ctx); err != nil {
	return fmt.Errorf("replica storage unavailable: %w", err)
}

Try / catch

// Treat as terminal for this pass; alert and retry the whole operation later
if err := reader.Read(buf); err != nil {
	if strings.Contains(err.Error(), "max retries exceeded reading ltx file") {
		return fmt.Errorf("replica storage persistently unavailable (check inner cause): %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: ReplicaClient.Read repeatedly returns errors while reading an LTX object at r.offset for the given level/TXID range — e.g. S3 returns 503 SlowDown on every attempt, the object was deleted mid-read, or the connection keeps dropping — so r.retryN exceeds resumableReaderMaxRetries.

Common situations: Cloud storage rate limiting during heavy compaction/restore; network partitions between litestream and the replica bucket; accidentally deleted or lifecycle-expired LTX objects; provider outages; retry backoff window still falling inside the provider throttle window.

Related errors


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