benbjohnson/litestream · error

read page @

Error message

read page %d @ %d: %w

What it means

While building an LTX file from the database and WAL (snapshot path in writeLTXFromDB), litestream reads a page's bytes from the WAL file at the recorded frame offset. A ReadAt failure is wrapped as 'read page %d @ %d:' with the page number and byte offset so you know exactly which WAL frame could not be read.

Solutions

  1. Ensure only one litestream process monitors the database (check for duplicate replicas/instances).
  2. Do not truncate, move, or delete the -wal file manually while replication is running.
  3. If the WAL is corrupt or was reset, run `litestream reset` for the database and let it rebuild from a replica or fresh snapshot.
  4. Check WAL file integrity and disk health if EIO is reported.

Example fix

// before
# two agents on one db
litestream replicate -config /etc/a.yml &
litestream replicate -config /etc/b.yml &   # second process corrupts state
// after
# single process per database
litestream replicate -config /etc/litestream.yml
Defensive patterns

Strategy: retry

Validate before calling

// ensure single writer
if pidFileLocked() { throw new Error('another litestream instance is running') }

Try / catch

if err := db.Sync(ctx); err != nil {
	if isWALReadErr(err) {
		// verify single-process ownership; consider litestream reset
	}
	return err
}

Prevention

When it happens

Trigger: walFile.ReadAt(data, offset+WALFrameHeaderSize) at db.go:2308 returned an error during snapshot LTX encoding: WAL truncated/shrunk concurrently by another checkpoint process, WAL deleted or rotated externally, or an I/O error on the WAL file.

Common situations: Two litestream processes (or litestream plus a manual checkpoint) racing on the same database; WAL reset via `litestream reset` or manual deletion mid-sync; corrupted or truncated WAL after a crash; NFS serving stale file size.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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

Appendix: source

Thrown at db.go:2309

	for pgno := uint32(1); pgno <= commit; pgno++ {
		if pgno == lockPgno {
			continue
		}

		// Check if the caller has canceled during processing.
		select {
		case <-ctx.Done():
			return context.Cause(ctx)
		default:
		}

		// If page exists in the WAL, read from there.
		if offset, ok := pageMap[pgno]; ok {
			db.Logger.Log(ctx, internal.LevelTrace, "encode page from wal", "txid", enc.Header().MinTXID, "offset", offset, "pgno", pgno, "type", "db+wal")

			if n, err := walFile.ReadAt(data, offset+WALFrameHeaderSize); err != nil {
				return fmt.Errorf("read page %d @ %d: %w", pgno, offset, err)
			} else if n != len(data) {
				return fmt.Errorf("short read page %d @ %d", pgno, offset)
			}

			if err := enc.EncodePage(ltx.PageHeader{Pgno: pgno}, data); err != nil {
				return fmt.Errorf("encode ltx frame (pgno=%d): %w", pgno, err)
			}
			continue
		}

		offset := int64(pgno-1) * int64(db.pageSize)
		db.Logger.Log(ctx, internal.LevelTrace, "encode page from database", "offset", offset, "pgno", pgno)

		// Otherwise read directly from the database file.
		if _, err := db.f.ReadAt(data, offset); err != nil {
			return fmt.Errorf("read database page %d: %w", pgno, err)
		}
		if err := enc.EncodePage(ltx.PageHeader{Pgno: pgno}, data); err != nil {

View on GitHub (pinned to 4ed7a308f6)