PR #4433
Sections
Review

perf: drain delivery backlog and copy backups without compaction ↗

main ← fix/database-maintenance-throughput 37 files +1845 −412 PR #4433 ↗

SQLite backups now copy database pages without a rebuild, and the delivery retention worker repeats productive passes at once, so upgrades block less and old payloads clear sooner.

Why this PR exists

Upgrade backups rebuild the full database with VACUUM INTO. The retention worker also sleeps through productive passes. A large upgrade stalls, and old payloads remain.

Who is affected: Operators with large SQLite databases and installs with delivery payload backlog.

Result: Backups copy pages directly with full validation. Productive retention continues at once. Boot blocks less, and backlog drains sooner.

What it does

Impact at a glance

Feature flags and rollout

No flag gates this change. The page copy and the continuous retention schedule run for every install after deploy. No new setting exists.

No UX changes with flags off

No flag exists. Disabled paths keep the same boot backup and retention behavior as the enabled path.

Architecture, end to end

Boot selection and backup use the new page copy. The retention worker drains backlog in one pass. Compaction keeps the old rebuild path.

flowchart LR
  Boot[Provide SQLite boot] --> Select[Select database] 
  Boot --> Backup[Pre-migration backup]
  Backup --> PageCopy[Native page copy]
  PageCopy --> Validate[Validate and install]
  Worker[Retention worker] --> Pass[Pass with budget]
  Pass --> Phases[Retention, orphans, retirements]
  Phases -- productive --> Pass
  Compact[Explicit compact] --> Vacuum[VACUUM INTO]

Key code changes

Drag to pan. Use the + and − buttons to zoom. Click a node to open the full code. The arrows show how the parts interact.

drag to pan · +/− to zoom · click a node for details

func BackupSQLite(ctx context.Context, source *sql.DB, path string) (err error)
Click for details →

The helper copies a consistent database image without rebuilding tables and indexes.

Page copy entry
func BackupSQLite(ctx context.Context, source *sql.DB, path string) (err error) {
	if err = ctx.Err(); err != nil {
		return err
	}
	file, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0o600)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			_ = os.Remove(path)
			_ = os.Remove(path + "-journal")
		}
	}()
	if err = file.Close(); err != nil {
		return err
	}
	absolute, err := filepath.Abs(path)
	if err != nil {
		return err
	}
Bounded step copy
func copySQLitePages(ctx context.Context, source, destination *sqlite3.SQLiteConn, pages int) (err error) {
	backup, err := destination.Backup("main", source, "main")
	if err != nil {
		return err
	}
	defer func() { err = errors.Join(err, backup.Finish()) }()
	lastProgress := time.Now()
	for {
		if err := ctx.Err(); err != nil {
			return err
		}
		remaining, total := backup.Remaining(), backup.PageCount()
		done, err := backup.Step(pages)
		if err != nil {
			return err
		}
		if done {
			return ctx.Err()
		}
	}
func createPreMigrationBackup(ctx context.Context, writer *sqlx.DB, backupDir, storedVersion string) (string, int64, error)
Click for details →

Startup takes the pre-migration snapshot with the page copy, then validates and installs it.

Staged copy and install
func createPreMigrationBackup(ctx context.Context, writer *sqlx.DB, backupDir, storedVersion string) (string, int64, error) {
	path := snapshotPath(backupDir, storedVersion)
	stagingDir, err := os.MkdirTemp(backupDir, ".kandev-backup-*")
	if err != nil {
		return "", 0, fmt.Errorf("create private backup staging directory: %w", err)
	}
	defer func() { _ = os.RemoveAll(stagingDir) }()
	stagedPath := filepath.Join(stagingDir, filepath.Base(path))
	stopTracking := trackBackupProgress(ctx, stagedPath)
	size, err := CopySQLiteSnapshotContext(ctx, writer, stagedPath)
	stopTracking()
	if err != nil {
		return "", 0, fmt.Errorf("pre-migration backup failed: %w", err)
	}
	if err := os.Chmod(stagedPath, 0o600); err != nil {
		return "", 0, fmt.Errorf("protect staged pre-migration backup: %w", err)
	}
	if _, err := inspectSQLiteCandidate(stagedPath); err != nil {
		return "", 0, fmt.Errorf("validate staged pre-migration backup: %w", err)
	}
	if err := installStagedSQLite(stagedPath, path); err != nil {
		return "", 0, fmt.Errorf("install pre-migration backup: %w", err)
	}
	return path, size, nil
}
func inspectSQLiteCandidateWithMode(path string, checkIntegrity bool) (sqliteCandidate, error)
Click for details →

Startup checks the current database with a light read, and adopts a legacy database with a page copy.

Lightweight current check
current, err := inspectSQLiteCandidateLightweight(currentPath)
	if err != nil {
		return sqliteSelection{}, err
	}
	if current.exists && current.hasTaskHistory {
		selection := sqliteSelection{
			path:              currentPath,
			existedBeforeOpen: true,
			outcome:           sqliteSelectionExisting,
		}
		logSQLiteSelection(log, cfg.SourceFor("database.path"), selection)
		return selection, nil
	}
Legacy adopt with copy
source, err := openSQLiteReadOnly(legacyPath)
	if err != nil {
		return fmt.Errorf("open legacy sqlite database: %w", err)
	}
	defer func() { _ = source.Close() }()
	if _, err := CopySQLiteSnapshot(source, stagedPath); err != nil {
		return fmt.Errorf("snapshot legacy sqlite database: %w", err)
	}
func (w *Worker) runPass(ctx context.Context) Snapshot
Click for details →

The worker repeats productive phases at once and returns to idle delay only when no work remains.

Continuous pass loop
func (w *Worker) runPass(ctx context.Context) Snapshot {
	started := w.opts.Now()
	deadline := started.Add(passBudget)
	snapshot := Snapshot{OldestEligibleAgeSeconds: -1}
	phases := []string{"retention", "orphans", "retirements"}
	for len(phases) > 0 {
		if ctx.Err() != nil {
			break
		}
		remaining := deadline.Sub(w.opts.Now())
		if remaining < batchBudget {
			snapshot.ContinuationPending = committedRetentionWork(snapshot) > 0
			break
		}
		phase := phases[0]
		phases = phases[1:]
		before := committedRetentionWork(snapshot)
		if !w.runPassBatch(ctx, phase, remaining, &snapshot) {
			break
		}
		if committedRetentionWork(snapshot) > before {
			phases = append(phases, phase)
		}
	}
Immediate continuation
default:
			w.retryAttempt = 0
			if snapshot.ContinuationPending {
				continue
			}
		}
Read the changes as a list

Native SQLite page copy

apps/backend/internal/db/sqlite_backup.go

The helper copies a consistent database image without rebuilding tables and indexes.

Page copy entry
func BackupSQLite(ctx context.Context, source *sql.DB, path string) (err error) {
	if err = ctx.Err(); err != nil {
		return err
	}
	file, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0o600)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			_ = os.Remove(path)
			_ = os.Remove(path + "-journal")
		}
	}()
	if err = file.Close(); err != nil {
		return err
	}
	absolute, err := filepath.Abs(path)
	if err != nil {
		return err
	}
Bounded step copy
func copySQLitePages(ctx context.Context, source, destination *sqlite3.SQLiteConn, pages int) (err error) {
	backup, err := destination.Backup("main", source, "main")
	if err != nil {
		return err
	}
	defer func() { err = errors.Join(err, backup.Finish()) }()
	lastProgress := time.Now()
	for {
		if err := ctx.Err(); err != nil {
			return err
		}
		remaining, total := backup.Remaining(), backup.PageCount()
		done, err := backup.Step(pages)
		if err != nil {
			return err
		}
		if done {
			return ctx.Err()
		}
	}

Boot backup uses page copy

apps/backend/internal/persistence/provider.go

Startup takes the pre-migration snapshot with the page copy, then validates and installs it.

Staged copy and install
func createPreMigrationBackup(ctx context.Context, writer *sqlx.DB, backupDir, storedVersion string) (string, int64, error) {
	path := snapshotPath(backupDir, storedVersion)
	stagingDir, err := os.MkdirTemp(backupDir, ".kandev-backup-*")
	if err != nil {
		return "", 0, fmt.Errorf("create private backup staging directory: %w", err)
	}
	defer func() { _ = os.RemoveAll(stagingDir) }()
	stagedPath := filepath.Join(stagingDir, filepath.Base(path))
	stopTracking := trackBackupProgress(ctx, stagedPath)
	size, err := CopySQLiteSnapshotContext(ctx, writer, stagedPath)
	stopTracking()
	if err != nil {
		return "", 0, fmt.Errorf("pre-migration backup failed: %w", err)
	}
	if err := os.Chmod(stagedPath, 0o600); err != nil {
		return "", 0, fmt.Errorf("protect staged pre-migration backup: %w", err)
	}
	if _, err := inspectSQLiteCandidate(stagedPath); err != nil {
		return "", 0, fmt.Errorf("validate staged pre-migration backup: %w", err)
	}
	if err := installStagedSQLite(stagedPath, path); err != nil {
		return "", 0, fmt.Errorf("install pre-migration backup: %w", err)
	}
	return path, size, nil
}

Light selection and legacy adopt

apps/backend/internal/persistence/sqlite_selection.go

Startup checks the current database with a light read, and adopts a legacy database with a page copy.

Lightweight current check
current, err := inspectSQLiteCandidateLightweight(currentPath)
	if err != nil {
		return sqliteSelection{}, err
	}
	if current.exists && current.hasTaskHistory {
		selection := sqliteSelection{
			path:              currentPath,
			existedBeforeOpen: true,
			outcome:           sqliteSelectionExisting,
		}
		logSQLiteSelection(log, cfg.SourceFor("database.path"), selection)
		return selection, nil
	}
Legacy adopt with copy
source, err := openSQLiteReadOnly(legacyPath)
	if err != nil {
		return fmt.Errorf("open legacy sqlite database: %w", err)
	}
	defer func() { _ = source.Close() }()
	if _, err := CopySQLiteSnapshot(source, stagedPath); err != nil {
		return fmt.Errorf("snapshot legacy sqlite database: %w", err)
	}

Retention drains without sleep

apps/backend/internal/task/deliveryretention/worker.go

The worker repeats productive phases at once and returns to idle delay only when no work remains.

Continuous pass loop
func (w *Worker) runPass(ctx context.Context) Snapshot {
	started := w.opts.Now()
	deadline := started.Add(passBudget)
	snapshot := Snapshot{OldestEligibleAgeSeconds: -1}
	phases := []string{"retention", "orphans", "retirements"}
	for len(phases) > 0 {
		if ctx.Err() != nil {
			break
		}
		remaining := deadline.Sub(w.opts.Now())
		if remaining < batchBudget {
			snapshot.ContinuationPending = committedRetentionWork(snapshot) > 0
			break
		}
		phase := phases[0]
		phases = phases[1:]
		before := committedRetentionWork(snapshot)
		if !w.runPassBatch(ctx, phase, remaining, &snapshot) {
			break
		}
		if committedRetentionWork(snapshot) > before {
			phases = append(phases, phase)
		}
	}
Immediate continuation
default:
			w.retryAttempt = 0
			if snapshot.ContinuationPending {
				continue
			}
		}

Risk

5 / 10 Medium
1 low5 medium10 high

Why this score

  • Boot backup runs before migrations, and a copy failure still aborts startup.
  • Retention now uses more CPU and I/O while a backlog exists.
  • Page copies keep free pages, so files stay larger than compacted copies.

Trade-offs and review notes

Where to look first

  1. Read the native copy stall and cleanup path in sqlite_backup.go first.
  2. Check staged validation and atomic install in provider.go and sqlite_selection.go.
  3. Confirm continuation only follows committed work in worker.go runPass.
  4. Check maintenance and repair reuse of the shared copy helper.