benbjohnson/litestream · error

close l1 iterator: %w

Error message

close l1 iterator: %w

What it means

Wraps an error from closing the L1 file iterator after determining maxL1TXID. Iterator Close flushes pending backend errors (e.g. failed pagination of a cloud listing), so a close error means the listing may have been incomplete and the computed max TXID cannot be trusted.

Source

Thrown at db.go:3041

	db.Logger.Debug("starting l0 retention enforcement", "retention", db.L0Retention)

	dbName := filepath.Base(db.Path())

	// Determine the highest TXID that has been compacted into L1.
	itr, err := db.Replica.Client.LTXFiles(ctx, 1, 0, false)
	if err != nil {
		return fmt.Errorf("fetch l1 files: %w", err)
	}
	var maxL1TXID ltx.TXID
	for itr.Next() {
		info := itr.Item()
		if info.MaxTXID > maxL1TXID {
			maxL1TXID = info.MaxTXID
		}
	}
	if err := itr.Close(); err != nil {
		return fmt.Errorf("close l1 iterator: %w", err)
	}
	if maxL1TXID == 0 {
		internal.L0RetentionGaugeVec.WithLabelValues(dbName, "eligible").Set(0)
		internal.L0RetentionGaugeVec.WithLabelValues(dbName, "not_compacted").Set(0)
		internal.L0RetentionGaugeVec.WithLabelValues(dbName, "too_recent").Set(0)
		return nil
	}

	threshold := time.Now().Add(-db.L0Retention)
	itr, err = db.Replica.Client.LTXFiles(ctx, 0, 0, false)
	if err != nil {
		return fmt.Errorf("fetch l0 files: %w", err)
	}
	defer itr.Close()

	var (
		deleted           []*ltx.FileInfo
		lastInfo          *ltx.FileInfo

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Retry the operation; a later successful Close confirms a complete listing.
  2. Prune old files via retention so listings stay small.
  3. Check provider throttling limits and request pacing for large buckets.
  4. Confirm custom ReplicaClient implementations propagate page-fetch errors through Close.

Example fix

// before
if err := itr.Close(); err != nil { return fmt.Errorf("close l1 iterator: %w", err) }
// after (caller retry with backoff)
if err := enforceL0Retention(ctx, db); err != nil && isTransient(err) {
    time.Sleep(backoff)
    err = enforceL0Retention(ctx, db)
}
Defensive patterns

Strategy: retry

Try / catch

if err := itr.Close(); err != nil {
    if isTransient(err) { return retryWithBackoff(ctx) }
    return fmt.Errorf("close l1 iterator: %w", err)
}

Prevention

When it happens

Trigger: L0 retention run where itr.Close() returns an error from the underlying remote listing, typically a failed S3/GS continuation-token page fetch that the iterator surfaces only on Close (db.go:3041).

Common situations: Large buckets whose listing spans many pages; transient 5xx or timeout on a later page; provider throttling during long listings.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


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