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
- Verify the database file size covers the page count implied by the WAL commit (`PRAGMA page_count;` vs file size).
- Never replace, truncate, or move the database file while litestream is replicating; stop litestream first.
- Check disk/storage health for EIO; move to healthy storage if the device is failing.
- 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
- Stop litestream before replacing or truncating the database file.
- Always pair the exact DB and WAL files; never mix generations.
- Monitor storage device health for read errors.
- Keep the database on reliable local storage.
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
- read page %d @ %d: %w
- snapshot interval must be greater than 0
- no snapshots available
- failed to read response: %w
- failed to scan directory %s: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/0403d696f1d96b8c.
Report an issue: GitHub.