benbjohnson/litestream · error
WALReader.ReadFrame(): buffer size (%d) must match page size
Error message
WALReader.ReadFrame(): buffer size (%d) must match page size (%d)
What it means
ReadFrame/readFrame fill the caller-supplied buffer with one page per frame. The buffer must be exactly pageSize bytes (as declared in the WAL header); this error is thrown when len(data) differs from the WAL's page size, so the frame read would overflow or underfill the buffer.
Source
Thrown at wal_reader.go:149
return nil
}
// ReadFrame reads the next frame from the WAL and returns the page number.
// Returns io.EOF at the end of the valid WAL.
func (r *WALReader) ReadFrame(ctx context.Context, data []byte) (pgno, commit uint32, err error) {
return r.readFrame(ctx, data, true)
}
func (r *WALReader) readFrame(ctx context.Context, data []byte, verifyChecksum bool) (pgno, commit uint32, err error) {
select {
case <-ctx.Done():
return 0, 0, context.Cause(ctx)
default:
}
if len(data) != int(r.pageSize) {
return 0, 0, fmt.Errorf("WALReader.ReadFrame(): buffer size (%d) must match page size (%d)", len(data), r.pageSize)
}
frameSize := r.pageSize + WALFrameHeaderSize
offset := WALHeaderSize + (int64(r.frameN) * int64(frameSize))
// Read WAL frame header.
hdr := make([]byte, WALFrameHeaderSize)
if n, err := r.r.ReadAt(hdr, offset); n != len(hdr) {
return 0, 0, io.EOF
} else if err != nil {
return 0, 0, err
}
// Read WAL page data.
if n, err := r.r.ReadAt(data, offset+WALFrameHeaderSize); n != len(data) {
return 0, 0, io.EOF
} else if err != nil {
return 0, 0, errView on GitHub (pinned to 4ed7a308f6)
Solutions
- Query the actual page size (PRAGMA page_size) and allocate the read buffer from it, not a constant
- Obtain the page size from the WALReader (r.pageSize / pageSize field) after NewWALReader* returns, then size the buffer
- If the page size changed mid-stream, re-open the WAL reader so the new header/page size is picked up
- Never reuse a buffer sized for a previous page size across readers
Example fix
// before data := make([]byte, 4096) n0, n1, err := reader.ReadFrame(ctx, data) // after data := make([]byte, reader.PageSize()) n0, n1, err := reader.ReadFrame(ctx, data)
Defensive patterns
Strategy: validation
Validate before calling
// derive the buffer from the reader, never a constant
buf := make([]byte, r.PageSize())
if len(buf) != int(r.PageSize()) { panic("page size mismatch") } Try / catch
_, _, err := reader.ReadFrame(ctx, data)
if err != nil && strings.Contains(err.Error(), "must match page size") {
data = make([]byte, reader.PageSize()) // re-size and retry
_, _, err = reader.ReadFrame(ctx, data)
} Prevention
- Always size frame buffers from the WAL reader's reported page size
- Re-read PRAGMA page_size after VACUUM or migration; never hardcode 4096
- Do not share buffers across databases with different page sizes
- Reopen the reader if the database's page size may have changed mid-stream
When it happens
Trigger: Calling ReadFrame with a buffer sized from a stale or assumed page size while the WAL header declares a different one; using the default 4096-byte buffer against a database with page_size=8192 etc.
Common situations: Database page size changed via 'PRAGMA page_size' or VACUUM between replication runs; hardcoding 4096 in code that reads frames; mixing readers across databases with different page sizes.
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
- set synchronous: %w
- checkpoint: %w
- checkpoint failed: %w
- sync database %s: %w
- enable wal failed, mode=%q
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/6e7d7ad294b966cf.
Report an issue: GitHub.