benbjohnson/litestream · error
sync ltx file: %w
Error message
sync ltx file: %w
What it means
The LTX file fsync failed with an error that was NOT classified as disk-full. Litestream wraps the raw OS error with 'sync ltx file:' so the failure is attributed to the durability flush of the in-progress LTX file. The temp file is left behind and the transaction is not committed to LTX.
Source
Thrown at db.go:2218
s.walSize = sz
})
if err := enc.Close(); err != nil {
if isDiskFullError(err) {
return result, NewLTXError("stage-write", tmpFilename, 0, uint64(txID), uint64(txID), fmt.Errorf("%w: %w", ErrDiskFull, err))
}
return result, fmt.Errorf("close ltx encoder: %w", err)
}
// Sync & close LTX file.
db.setSyncDiagPhase(diagPhaseFsyncLTX, func(s *diagState) {
s.txID = txID
s.walSize = sz
})
if err := ltxFile.Sync(); err != nil {
if isDiskFullError(err) {
return result, NewLTXError("stage-sync", tmpFilename, 0, uint64(txID), uint64(txID), fmt.Errorf("%w: %w", ErrDiskFull, err))
}
return result, fmt.Errorf("sync ltx file: %w", err)
}
if err := ltxFile.Close(); err != nil {
if isDiskFullError(err) {
return result, NewLTXError("stage-close", tmpFilename, 0, uint64(txID), uint64(txID), fmt.Errorf("%w: %w", ErrDiskFull, err))
}
return result, fmt.Errorf("close ltx file: %w", err)
}
// Atomically rename file to final path.
db.setSyncDiagPhase(diagPhaseRenameLTX, func(s *diagState) {
s.txID = txID
s.walSize = sz
})
if err := os.Rename(tmpFilename, filename); err != nil {
db.maxLTXFileInfos.Lock()
delete(db.maxLTXFileInfos.m, 0) // clear cache if in unknown state
db.maxLTXFileInfos.Unlock()
db.invalidatePosCache()View on GitHub (pinned to 4ed7a308f6)
Solutions
- Inspect the wrapped OS error (dmesg / journalctl) to identify the underlying I/O problem.
- Run filesystem checks (fsck) and monitor SMART health of the underlying device.
- Move the database directory to a local, reliable filesystem instead of a network mount.
- Delete the stale temp file and retry the sync once the underlying issue is resolved.
Example fix
// before
// generic handling loses the failing file stage
log.Println(err)
// after
var ltxErr *LTXError
if errors.As(err, <xErr) {
log.Printf("ltx stage=%s file=%s: %v", ltxErr.Stage, ltxErr.Filename, ltxErr)
} Defensive patterns
Strategy: try-catch
Type guard
func isLTXStageErr(err error) (stage string, ok bool) {
var e *LTXError
if errors.As(err, &e) { return e.Stage, true }
return "", false
} Try / catch
if err := db.Sync(ctx); err != nil {
var le *LTXError
if errors.As(err, &le) && le.Stage == "stage-sync" {
// inspect wrapped OS error; check dmesg for device issues
}
return err
} Prevention
- Keep the database on a local, POSIX-compliant filesystem, not NFS.
- Monitor SMART/device health and filesystem errors.
- Avoid read-only remount scenarios by supervising mount state.
- Clean up orphaned temp LTX files after failures.
When it happens
Trigger: ltxFile.Sync() at db.go:2214 returned an I/O error other than ENOSPC: EIO from failing hardware, EINVAL from unusual mount options, EROFS, or a network filesystem (NFS/EFS) reporting a flush error.
Common situations: Disk or controller hardware errors; mounting the data dir on NFS/S3-FS where fsync is unreliable; read-only remount after a filesystem error; container storage driver failures.
Understand the failure class
Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.
Related errors
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/28337818cdcb3c4e.
Report an issue: GitHub.