benbjohnson/litestream · error

read database page %d: %w

Error message

read database page %d: %w

What it means

During snapshot LTX generation, pages not present in the WAL are read directly from the main database file at offset (pgno-1)*pageSize. A failed ReadAt is wrapped as 'read database page %d:' with the page number, meaning the database file could not supply a page that the WAL commit count says exists.

Source

Thrown at db.go:2325

			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 {
			return fmt.Errorf("encode ltx frame (pgno=%d): %w", pgno, err)
		}
	}

	return nil
}

func (db *DB) writeLTXFromWAL(ctx context.Context, enc *ltx.Encoder, walFile *os.File, prevCommit, commit uint32, pageMap map[uint32]int64) error {
	// Create an ordered list of page numbers since the LTX encoder requires it.
	pgnos := make([]uint32, 0, len(pageMap))
	for pgno := range pageMap {
		pgnos = append(pgnos, pgno)
	}
	lockPgno := ltx.LockPgno(uint32(db.pageSize))
	if commit > prevCommit {
		walPgnoN := len(pgnos)

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Verify the database file size covers the page count implied by the WAL commit (`PRAGMA page_count;` vs file size).
  2. Never replace, truncate, or move the database file while litestream is replicating; stop litestream first.
  3. Check disk/storage health for EIO; move to healthy storage if the device is failing.
  4. Restore a consistent DB+WAL pair from a replica and restart replication.
Defensive patterns

Strategy: validation

Validate before calling

const size = fs.statSync(dbPath).size
const pageCount = Math.floor(size / pageSize)
if (pageCount < walCommitCount) throw new Error('DB file smaller than WAL commit; inconsistent pair')

Try / catch

if err := db.Sync(ctx); err != nil {
	if strings.Contains(err.Error(), "read database page") {
		// check DB file size vs page_count; restore consistent pair
	}
	return err
}

Prevention

When it happens

Trigger: db.f.ReadAt(data, offset) at db.go:2324 returned an error: pgno exceeds the database file size (commit count from WAL disagrees with the main DB file, e.g. after external truncation), the DB file was deleted/replaced mid-sync, or an I/O error on the storage device.

Common situations: Database file truncated or replaced while litestream holds it open; mismatched DB/WAL pair after copying files out of band; corrupt or failing disk sectors; read errors on network-backed storage.

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/0403d696f1d96b8c. Report an issue: GitHub.