benbjohnson/litestream · error
unaligned wal offset %d for page size %d
Error message
unaligned wal offset %d for page size %d
What it means
The reader requires 'offset' to land exactly on a WAL frame boundary: after the 32-byte WAL header, frames are (pageSize+24) bytes each, so (offset-WALHeaderSize) must be a multiple of frameSize. This error is thrown when a caller passes an offset that does not correspond to the start of a frame for the page size stored in the WAL header.
Source
Thrown at wal_reader.go:64
// Ensure we are not starting on the first page since we need to read the previous.
if offset <= WALHeaderSize {
return nil, fmt.Errorf("offset (%d) must be greater than the wal header size (%d)", offset, WALHeaderSize)
}
r := &WALReader{r: rd, logger: logger}
// Read header to determine page size & byte order.
if err := r.readHeader(); err != nil {
return nil, fmt.Errorf("read header: %w", err)
}
// Load in salt in case the beginning of the file has been overwritten.
r.salt1, r.salt2 = salt1, salt2
// Ensure offset is positioned on a frame start.
frameSize := int64(r.pageSize + WALFrameHeaderSize)
if (offset-WALHeaderSize)%frameSize != 0 {
return nil, fmt.Errorf("unaligned wal offset %d for page size %d", offset, r.pageSize)
}
r.frameN = int((offset - WALHeaderSize) / frameSize)
// Read previous page to load checksum. Context errors are returned as-is
// so callers don't mistake a cancellation for a frame mismatch.
r.frameN--
if _, _, err := r.readFrame(ctx, make([]byte, r.pageSize), false); err != nil {
if ctx.Err() != nil {
return nil, context.Cause(ctx)
}
return nil, &PrevFrameMismatchError{Err: err}
}
return r, nil
}
// PageSize returns the page size from the header. Must call ReadHeader() first.
func (r *WALReader) PageSize() uint32 { return r.pageSize }View on GitHub (pinned to 4ed7a308f6)
Solutions
- Persist and reuse the offset together with the page size, and validate the pair before reopening
- Only pass offsets previously returned by the reader (frame starts), never hand-computed values
- Recompute alignment: offset must equal WALHeaderSize + n*(pageSize+24); if your stored offset fails, reset to 0 and re-read the WAL from the start
- If the page size changed, restart replication from the beginning of the current WAL
Example fix
// before
frameSize := pageSize + WALFrameHeaderSize
r, err := NewWALReaderWithOffset(ctx, f, savedOffset, logger)
// after: check alignment first
frameSize := int64(pageSize + WALFrameHeaderSize)
if (savedOffset-WALHeaderSize)%frameSize != 0 {
savedOffset = WALHeaderSize // fall back to start of WAL
}
r, err := NewWALReaderWithOffset(ctx, f, savedOffset, logger) Defensive patterns
Strategy: validation
Validate before calling
func alignedWALOffset(offset, pageSize int64) bool {
frameSize := pageSize + 24
return offset >= 32 && (offset-32)%int64(frameSize) == 0
} Try / catch
r, err := NewWALReaderWithOffset(ctx, f, off, logger)
var alignErr interface{ Error() string }
if err != nil && strings.Contains(err.Error(), "unaligned wal offset") {
off = 32 // restart from beginning of WAL
r, err = NewWALReaderWithOffset(ctx, f, off, logger)
} Prevention
- Persist the page size alongside any saved WAL offset and validate the pair on resume
- Only reuse offsets obtained from the reader itself
- Reset to offset 32 (start of WAL) when alignment fails
- Re-validate stored offsets after any VACUUM or page_size change
When it happens
Trigger: Passing an arbitrary byte offset to NewWALReaderWithOffset — e.g. an offset saved against a different page size, an offset captured mid-frame, or an offset from a different WAL file.
Common situations: Resuming replication from a persisted offset after the database page size changed (VACUUM/PRAGMA page_size); restoring an offset from config that was recorded for another database; off-by-one when computing frame offsets manually.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- new wal reader with offset: %w
- set synchronous: %w
- checkpoint: %w
- checkpoint failed: %w
- sync database %s: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/79144603d9679b8e.
Report an issue: GitHub.