benbjohnson/litestream · critical

non-contiguous ltx files: have up to %s but next file starts

Error message

non-contiguous ltx files: have up to %s but next file starts at %s

What it means

During a latest-restore plan (no explicit TXID/timestamp), CalcRestorePlan validates that the LTX file sequence is contiguous. If the next available file's MinTXID is greater than currentMax+1, there is a gap in the replicated LTX chain and restoring the full sequence is impossible without data loss, so the plan is rejected.

Source

Thrown at replica.go:1617

		logger.Debug("matching LTX file for restore",
			"filename", ltx.FormatFilename(next.candidate.MinTXID, next.candidate.MaxTXID),
			"level", next.candidate.Level)
		infos = append(infos, next.candidate)
		currentMax = next.candidate.MaxTXID
		next.candidate = nil

		if txID != 0 && currentMax >= txID {
			break
		}
	}

	if len(infos) > 0 && txID == 0 && timestamp.IsZero() {
		for _, cursor := range cursors {
			if err := cursor.ensureCurrent(); err != nil {
				return nil, err
			}
			if cursor.current != nil && cursor.current.MinTXID > currentMax+1 {
				return nil, fmt.Errorf("non-contiguous ltx files: have up to %s but next file starts at %s", currentMax, cursor.current.MinTXID)
			}
		}
	}

	if len(infos) == 0 {
		return nil, ErrTxNotAvailable
	}
	if txID != 0 && infos.MaxTXID() < txID {
		return nil, ErrTxNotAvailable
	}

	return infos, nil
}

type restoreLevelCursor struct {
	// itr streams LTX file infos for a single level in filename order.
	itr ltx.FileIterator
	// current holds the last item read from itr but not yet evaluated.

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Run `litestream ltx -level all` on the replica to find the missing TXID range, then re-replicate or restore the missing files.
  2. Use `litestream reset` if local LTX state is corrupted, then let replication rebuild.
  3. Restore to an older timestamp/TXID before the gap instead of latest.
  4. Fix storage lifecycle/retention settings so mid-chain files are not deleted; disable retention only when a cloud lifecycle policy owns cleanup.

Example fix

// before
$ litestream restore -replica s3 db.sqlite
// error: non-contiguous ltx files: have up to 0000000000000123 but next file starts at 0000000000000200
// after
// restore to a point before the gap:
$ litestream restore -timestamp 2024-06-01T00:00:00Z db.sqlite
Defensive patterns

Strategy: validation

Validate before calling

// Go: pre-validate TXID contiguity before a latest restore
infos, err := FindLTXFiles(ctx, client, 0, true, nil)
if err != nil { return err }
for i := 1; i < len(infos); i++ {
    if infos[i].MinTXID > infos[i-1].MaxTXID+1 {
        return fmt.Errorf("gap between %d and %d", infos[i-1].MaxTXID, infos[i].MinTXID)
    }
}

Try / catch

if err := restoreLatest(); err != nil {
    if strings.HasPrefix(err.Error(), "non-contiguous ltx files") {
        // restore to a timestamp/TXID before the gap instead
        return restoreWithTimestamp(lastKnownGoodTime)
    }
    return err
}

Prevention

When it happens

Trigger: Restoring 'latest' when generation files were deleted out of order (lifecycle policy, manual rm, partial upload) or when files from a rolled/compacted level are missing, leaving TXID gaps.

Common situations: Cloud storage retention rules deleting older LTX files while newer ones remain; interrupted compaction; copying replica data manually and skipping files; restoring across generations with a missing boundary file.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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