{"record":{"id":"79144603d9679b8e","repo":"benbjohnson/litestream","slug":"unaligned-wal-offset-d-for-page-size-d","errorCode":null,"errorMessage":"unaligned wal offset %d for page size %d","messagePattern":"unaligned wal offset (.+?) for page size (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"wal_reader.go","lineNumber":64,"sourceCode":"\t// Ensure we are not starting on the first page since we need to read the previous.\n\tif offset <= WALHeaderSize {\n\t\treturn nil, fmt.Errorf(\"offset (%d) must be greater than the wal header size (%d)\", offset, WALHeaderSize)\n\t}\n\n\tr := &WALReader{r: rd, logger: logger}\n\n\t// Read header to determine page size & byte order.\n\tif err := r.readHeader(); err != nil {\n\t\treturn nil, fmt.Errorf(\"read header: %w\", err)\n\t}\n\n\t// Load in salt in case the beginning of the file has been overwritten.\n\tr.salt1, r.salt2 = salt1, salt2\n\n\t// Ensure offset is positioned on a frame start.\n\tframeSize := int64(r.pageSize + WALFrameHeaderSize)\n\tif (offset-WALHeaderSize)%frameSize != 0 {\n\t\treturn nil, fmt.Errorf(\"unaligned wal offset %d for page size %d\", offset, r.pageSize)\n\t}\n\tr.frameN = int((offset - WALHeaderSize) / frameSize)\n\n\t// Read previous page to load checksum. Context errors are returned as-is\n\t// so callers don't mistake a cancellation for a frame mismatch.\n\tr.frameN--\n\tif _, _, err := r.readFrame(ctx, make([]byte, r.pageSize), false); err != nil {\n\t\tif ctx.Err() != nil {\n\t\t\treturn nil, context.Cause(ctx)\n\t\t}\n\t\treturn nil, &PrevFrameMismatchError{Err: err}\n\t}\n\n\treturn r, nil\n}\n\n// PageSize returns the page size from the header. Must call ReadHeader() first.\nfunc (r *WALReader) PageSize() uint32 { return r.pageSize }","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/wal_reader.go#L46-L82","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nframeSize := pageSize + WALFrameHeaderSize\nr, err := NewWALReaderWithOffset(ctx, f, savedOffset, logger)\n// after: check alignment first\nframeSize := int64(pageSize + WALFrameHeaderSize)\nif (savedOffset-WALHeaderSize)%frameSize != 0 {\n    savedOffset = WALHeaderSize // fall back to start of WAL\n}\nr, err := NewWALReaderWithOffset(ctx, f, savedOffset, logger)","handlingStrategy":"validation","validationCode":"func alignedWALOffset(offset, pageSize int64) bool {\n    frameSize := pageSize + 24\n    return offset >= 32 && (offset-32)%int64(frameSize) == 0\n}","typeGuard":null,"tryCatchPattern":"r, err := NewWALReaderWithOffset(ctx, f, off, logger)\nvar alignErr interface{ Error() string }\nif err != nil && strings.Contains(err.Error(), \"unaligned wal offset\") {\n    off = 32 // restart from beginning of WAL\n    r, err = NewWALReaderWithOffset(ctx, f, off, logger)\n}","preventionTips":["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"],"tags":["sqlite","wal","offset","alignment"],"backgroundTag":"invalid-argument-value","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}