benbjohnson/litestream · critical

non-contiguous ltx file: level=

Error message

non-contiguous ltx file: level=%d, current=%s, next=%s-%s

What it means

While polling a level, an LTX file whose MinTXID does not continue the sequence (info.MinTXID != maxTXID+1) was found. The index would become inconsistent if applied, so the poll aborts with this error. L0 gaps are tolerated (deferred), other levels are hard failures.

Solutions

  1. Compare listed LTX files in storage against expected TXID sequence to find the gap
  2. Run `litestream reset` (or the replica auto-recover option) to rebuild local state from the replica
  3. Ensure only one litestream process replicates each database path
  4. Disable object-store lifecycle policies that delete LTX files under retention
Defensive patterns

Strategy: try-catch

Validate before calling

// verify contiguity before relying on replica reads
infos := listLTXInfos(level)
for i := 1; i < len(infos); i++ {
    if infos[i].MinTXID != infos[i-1].MaxTXID+1 {
        return fmt.Errorf("gap at %s", infos[i].MinTXID)
    }
}

Try / catch

if err := pollOnce(ctx); err != nil {
    var gapErr *ContiguityError // or strings.Contains "non-contiguous ltx file"
    if strings.Contains(err.Error(), "non-contiguous ltx file") {
        return resetAndRehydrate(dbPath) // litestream reset
    }
    return err
}

Prevention

When it happens

Trigger: Replica storage at level > 0 has missing LTX files: files deleted out-of-band, partial snapshot restore, concurrent compaction race, or a storage lifecycle rule removing files.

Common situations: Manually pruning object storage; restoring a subset of LTX files; two litestream processes replicating to the same path; failed compaction leaving holes.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at vfs.go:2743

	index := make(map[uint32]ltx.PageIndexElem)
	maxTXID := prevMaxTXID
	lastCommit := baseCommit
	newCommit := baseCommit
	replaceIndex := false

	for itr.Next() {
		info := itr.Item()

		f.mu.Lock()
		isNextTXID := info.MinTXID == maxTXID+1
		f.mu.Unlock()
		if !isNextTXID {
			if level == 0 && info.MinTXID > maxTXID+1 {
				f.logger.Warn("ltx gap detected at L0, deferring to higher levels", "expected", maxTXID+1, "next", info.MinTXID)
				break
			}
			return maxTXID, nil, newCommit, replaceIndex, fmt.Errorf("non-contiguous ltx file: level=%d, current=%s, next=%s-%s", level, maxTXID, info.MinTXID, info.MaxTXID)
		}

		f.logger.Debug("new ltx file", "level", info.Level, "min", info.MinTXID, "max", info.MaxTXID)

		idx, err := FetchPageIndex(ctx, f.client, info)
		if err != nil {
			return maxTXID, nil, newCommit, replaceIndex, fmt.Errorf("fetch page index: %w", err)
		}
		hdr, err := FetchLTXHeader(ctx, f.client, info)
		if err != nil {
			return maxTXID, nil, newCommit, replaceIndex, fmt.Errorf("fetch header: %w", err)
		}

		if hdr.Commit < lastCommit {
			replaceIndex = true
			index = make(map[uint32]ltx.PageIndexElem)
		}
		lastCommit = hdr.Commit

View on GitHub (pinned to 4ed7a308f6)