benbjohnson/litestream · error

reopen ltx file at offset %d: %w

Error message

reopen ltx file at offset %d: %w

What it means

ResumableReader.Read() tried to reopen the remote LTX file stream at the current offset after a connection drop and got a non-retryable error: os.ErrNotExist (file gone, e.g. compacted away), context.Canceled/DeadlineExceeded, or the reader's context already canceled. The read fails immediately instead of entering the retry/backoff path.

Source

Thrown at internal/resumable_reader.go:86

// resumableReaderBackoff is the base delay between retry attempts, doubling
// per attempt. Zero-delay retries land every attempt inside the same provider
// throttle window (e.g. Tigris 408 load shedding), guaranteeing exhaustion.
const resumableReaderBackoff = 250 * time.Millisecond

func (r *ResumableReader) Read(p []byte) (int, error) {
	if r.err != nil {
		return 0, r.err
	}

	for {
		// Reopen the stream from the current offset if the previous
		// connection was closed (rc is nil after a retry).
		if r.rc == nil {
			rc, err := r.client.OpenLTXFile(r.ctx, r.level, r.minTXID, r.maxTXID, r.offset, 0)
			if err != nil {
				if errors.Is(err, os.ErrNotExist) || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || r.ctx.Err() != nil {
					return 0, fmt.Errorf("reopen ltx file at offset %d: %w", r.offset, err)
				}
				if retryErr := r.retry(fmt.Errorf("reopen ltx file at offset %d: %w", r.offset, err)); retryErr != nil {
					return 0, retryErr
				}
				r.logger.Debug("reopen ltx file failed, retrying",
					"level", r.level, "min", r.minTXID, "max", r.maxTXID,
					"offset", r.offset, "error", err, "attempt", r.retryN)
				continue
			}
			r.rc = rc
		}

		n, err := r.rc.Read(p)
		r.offset += int64(n)

		if err == nil {
			return n, nil
		}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. If ErrNotExist: restart the restore/replication from the latest available position — the original segment no longer exists
  2. If context canceled/deadline: rerun with a longer deadline or without canceling; the error reflects the caller's own cancellation
  3. Check replica retention settings — increase retention so segments aren't removed during active reads
  4. Use `litestream ltx -level <level>` to see which LTX files actually exist on the replica
  5. Verify only one litestream instance manages the replica (compaction by a second instance may race active reads)
Defensive patterns

Strategy: try-catch

Try / catch

n, err := rr.Read(buf)
if err != nil {
    if errors.Is(err, os.ErrNotExist) {
        // segment compacted away — restart from current replica position
    } else if errors.Is(err, context.Canceled) {
        // caller canceled — expected during shutdown
    }
    return err
}

Prevention

When it happens

Trigger: Reading a streamed LTX file when the connection dropped, then on reopen OpenLTXFile returns NotExist because the LTX file was compacted/removed on the replica in the meantime, or the caller's context was canceled (shutdown, timeout, test cancellation).

Common situations: Long-running restore interrupted by litestream compaction removing the exact segment; restore canceled by the user (Ctrl-C) mid-stream; restore exceeding a deadline; TestResumableReader_ContextCancelAbortsBackoff exercising the cancel path.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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