benbjohnson/litestream · error
remote has newer transactions than expected
Error message
remote has newer transactions than expected
What it means
ErrConflict is returned when the remote replica has newer transactions than the local VFS/reader expected, meaning the local view is stale relative to the replica. In the sqlite3vfs layer (vfs.go:41) it signals that a read/write cannot proceed safely against the remote transaction log without resyncing. The tests use it to assert conflicts are NOT falsely raised in normal operation.
Source
Thrown at vfs.go:41
lru "github.com/hashicorp/golang-lru/v2"
"github.com/markusmobius/go-dateparser"
"github.com/superfly/ltx"
"github.com/psanford/sqlite3vfs"
)
const (
DefaultPollInterval = 1 * time.Second
DefaultCacheSize = 10 * 1024 * 1024 // 10MB
DefaultPageSize = 4096 // SQLite default page size
pageFetchRetryAttempts = 6
pageFetchRetryDelay = 15 * time.Millisecond
)
// ErrConflict is returned when the remote replica has newer transactions than expected.
var ErrConflict = errors.New("remote has newer transactions than expected")
var (
//go:linkname sqlite3vfsFileMap github.com/psanford/sqlite3vfs.fileMap
sqlite3vfsFileMap map[uint64]sqlite3vfs.File
//go:linkname sqlite3vfsFileMux github.com/psanford/sqlite3vfs.fileMux
sqlite3vfsFileMux sync.Mutex
vfsConnectionMap sync.Map // map[uintptr]uint64
)
// VFS implements the SQLite VFS interface for Litestream.
// It is intended to be used for read replicas that read directly from S3.
// When WriteEnabled is true, also supports writes with periodic sync.
type VFS struct {
client ReplicaClient
logger *slog.Logger
View on GitHub (pinned to 4ed7a308f6)
Solutions
- Close and reopen the database (or the VFS file) so local state is refreshed from the current replica position
- Trigger a fresh sync/restore to pull the newer transactions before retrying writes
- Ensure only one writer works against a given replica at a time; litestream is a disaster-recovery tool, not a multi-master system
- Check for a real conflict: compare remote TXID vs expected before assuming stale local state
Example fix
// before
if _, err := sqldb.Exec("INSERT ...", v); err != nil {
return err
}
// after
if _, err := sqldb.Exec("INSERT ...", v); err != nil {
if errors.Is(err, litestream.ErrConflict) {
reopenAndResync() // refresh local state from replica, then retry once
}
return err
} Defensive patterns
Strategy: try-catch
Validate before calling
// compare remote TXID with local expected before writing remoteTXID, err := store.CurrentTXID(ctx, dbPath)
Type guard
func isConflict(err error) bool {
return errors.Is(err, litestream.ErrConflict) || strings.Contains(err.Error(), "conflict")
} Try / catch
if _, err := sqldb.Exec("INSERT ...", v); err != nil {
if isConflict(err) {
if err := resyncAndReopen(ctx); err != nil { return err } // refresh from replica, retry once
}
return err
} Prevention
- Enforce a single writer per replica
- Resync after remote restore/compaction operations before writing
- Avoid caching replica TXIDs across process restarts
- Treat conflict as 'stale local state' and reopen rather than retry blindly
When it happens
Trigger: A page fetch or transaction read encounters a remote TXID newer than the locally expected transaction; writes issued through the litestream VFS while another process advanced the replica ahead; stale cached page/txn state after a remote compaction or restore.
Common situations: Two clients writing through the VFS against the same replica; a remote restore/compaction happened between reads; cached replica metadata outlived a remote write by another host.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- %w: expected TXID %d but remote has %d
- sync interval must be greater than 0
- lease not held
- disk full
- validation failed
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/079367099eb9fdb2.
Report an issue: GitHub.