benbjohnson/litestream · error

apply ltx to hydrated file: %w

Error message

apply ltx to hydrated file: %w

What it means

During Hydrator.CatchUp, each listed LTX file is applied to the hydration file via ApplyLTX. This error wraps any failure returned by ApplyLTX — open, header decode, page decode, or page write — indicating catch-up stopped partway through the TXID range.

Source

Thrown at vfs.go:828

// CatchUp applies updates from LTX files between fromTXID and toTXID.
func (h *Hydrator) CatchUp(ctx context.Context, fromTXID, toTXID ltx.TXID) error {
	h.logger.Debug("catching up hydration", "from", fromTXID, "to", toTXID)

	// Fetch LTX files from fromTXID+1 to toTXID
	itr, err := h.client.LTXFiles(ctx, 0, fromTXID+1, false)
	if err != nil {
		return fmt.Errorf("list ltx files for catch-up: %w", err)
	}
	defer itr.Close()

	for itr.Next() {
		info := itr.Item()
		if info.MaxTXID > toTXID {
			break
		}

		if err := h.ApplyLTX(ctx, info); err != nil {
			return fmt.Errorf("apply ltx to hydrated file: %w", err)
		}

		h.mu.Lock()
		h.txid = info.MaxTXID
		h.mu.Unlock()
	}

	return nil
}

// ApplyLTX fetches an entire LTX file and applies its pages to the hydration file.
func (h *Hydrator) ApplyLTX(ctx context.Context, info *ltx.FileInfo) error {
	h.logger.Debug("applying ltx to hydration file", "level", info.Level, "min", info.MinTXID, "max", info.MaxTXID)

	// Fetch entire LTX file
	rc, err := h.client.OpenLTXFile(ctx, info.Level, info.MinTXID, info.MaxTXID, 0, 0)
	if err != nil {
		return fmt.Errorf("open ltx file: %w", err)

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Unwrap to find the ApplyLTX cause (open vs decode vs write) and address it — most often re-fetching the file or freeing disk space.
  2. Adjust retention so level-0 files outlive catch-up consumption, or increase catch-up frequency.
  3. If a file is permanently missing from the replica, abandon incremental catch-up and re-run a full Restore from the latest snapshot.
  4. Ensure no other goroutine closes or truncates the hydration file while CatchUp runs (h.file is shared).

Example fix

// before
if err := h.ApplyLTX(ctx, info); err != nil {
    return fmt.Errorf("apply ltx to hydrated file: %w", err)
}
// after
if err := h.ApplyLTX(ctx, info); err != nil {
    logger.Warn("catch-up failed; falling back to full restore", "txid", info.MaxTXID, "err", err)
    return h.Restore(ctx, latestInfos)
}
Defensive patterns

Strategy: fallback

Validate before calling

// confirm the file still exists on the replica before applying
itr, _ := client.LTXFiles(ctx, info.Level, info.MinTXID, false)
if !itr.Next() { return ErrLTXGone }

Try / catch

if err := h.ApplyLTX(ctx, info); err != nil {
    log.Warn("incremental catch-up failed, falling back to full restore", "err", err)
    return h.Restore(ctx, latestInfos)
}

Prevention

When it happens

Trigger: Calling CatchUp where an intermediate LTX file fails to open (deleted from replica), fails to decode (corrupt/truncated download), or its pages cannot be written (disk full, hydration file closed), causing ApplyLTX to return an error.

Common situations: Retention deleting level-0 files faster than catch-up consumes them; corrupted object in the replica; disk pressure on the hydration volume; concurrent access closing the hydration file mid-apply.

Related errors


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