benbjohnson/litestream · critical

upload LTX: %w

Error message

upload LTX: %w

What it means

syncToRemoteWithLock built an LTX file from the dirty pages and failed to upload it to the replica via client.WriteLTXFile. The dirty pages remain dirty and pendingTXID is not advanced, so no data was committed remotely; the wrapped error is the storage client's failure (network, credentials, permissions, conflict).

Source

Thrown at vfs.go:2012

	// Double-check dirty pages exist
	if len(f.dirty) == 0 {
		return nil
	}

	ctx := f.ctx

	// Check for conflicts
	if err := f.checkForConflict(ctx); err != nil {
		return err
	}

	// Create LTX file from dirty pages
	ltxReader := f.createLTXFromDirty()

	// Upload LTX file to remote
	info, err := f.client.WriteLTXFile(ctx, 0, f.pendingTXID, f.pendingTXID, ltxReader)
	if err != nil {
		return fmt.Errorf("upload LTX: %w", err)
	}

	f.logger.Info("synced to remote",
		"txid", info.MaxTXID,
		"pages", len(f.dirty),
		"size", info.Size)

	f.expectedTXID = f.pendingTXID
	f.pendingTXID++
	f.pos = ltx.Pos{TXID: f.expectedTXID}

	if f.vfs != nil {
		f.vfs.writeMu.Lock()
		if f.expectedTXID > f.vfs.lastSyncedTXID {
			f.vfs.lastSyncedTXID = f.expectedTXID
		}
		f.vfs.writeMu.Unlock()
	}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Check the wrapped cause: fix network egress, refresh credentials, or correct bucket IAM permissions.
  2. If it's a TXID conflict, ensure only one writer replicates per database (use leasing) or litestream reset local state and re-enable.
  3. Retry after transient failures — dirty state is preserved and the next sync tick will retry automatically.
  4. Verify replica client config (endpoint, region, bucket) with litestream ltx listing against the same bucket.
  5. Increase storage request timeouts / check rate limits if errors are throttling responses.

Example fix

// before
client := s3.NewReplicaClient() // uses expired env credentials
// after
client = s3.NewReplicaClient()
client.AccessKeyID, client.SecretAccessKey = refreshedKeys() // rotate before sync
Defensive patterns

Strategy: retry

Validate before calling

// Go: pre-flight replica write check before enabling write support
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
itr, err := client.LTXFiles(ctx, 0, 0, false)
if err != nil { return fmt.Errorf("replica not reachable: %w", err) }
itr.Close()

Try / catch

if err := file.Sync(); err != nil {
    if strings.HasPrefix(err.Error(), "upload LTX:") {
        if errors.Is(err, context.DeadlineExceeded) || isTransient(err) {
            time.Sleep(backoff) // dirty pages preserved; next tick retries
        }
    }
}

Prevention

When it happens

Trigger: Sync() during transactions-off periods, periodic syncLoop tick, or the final sync in SetWriteEnabled(false), whenever the replica client cannot write the LTX file: network outage, expired AWS/S3 credentials, bucket permissions, or a TXID conflict detected just before upload.

Common situations: S3/Cloudflare R2 outages or throttling; IAM policy revoking PutObject on the bucket; rotating credentials without restarting litestream; concurrent writer from another node advancing remote TXID (competing writers without leasing); firewall blocking egress to the storage endpoint.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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