benbjohnson/litestream · error
read page %d @ %d: %w
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.
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)
Solutions
- Ensure only one litestream process monitors the database (check for duplicate replicas/instances).
- Do not truncate, move, or delete the -wal file manually while replication is running.
- If the WAL is corrupt or was reset, run `litestream reset` for the database and let it rebuild from a replica or fresh snapshot.
- 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
- Run exactly one litestream process per database.
- Never manually truncate/delete the -wal file during replication.
- After WAL corruption, use `litestream reset` rather than patching files.
- Monitor for external checkpointers touching the WAL.
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
- read database page %d: %w
- frame salts until: %w
- short read page %d @ %d
- snapshot interval must be greater than 0
- no snapshots available
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/656b377f3e0bd80e.
Report an issue: GitHub.