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

  1. Close and reopen the database (or the VFS file) so local state is refreshed from the current replica position
  2. Trigger a fresh sync/restore to pull the newer transactions before retrying writes
  3. Ensure only one writer works against a given replica at a time; litestream is a disaster-recovery tool, not a multi-master system
  4. 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

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


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/079367099eb9fdb2. Report an issue: GitHub.