Skip to content

perf(cache): use synchronous=NORMAL in WAL mode - #871

Merged
cursor[bot] merged 6 commits into
voidzero-dev:mainfrom
magurotuna:perf/cache-wal-synchronous-normal
Oct 21, 2026
Merged

cursor[bot] merged 6 commits into
voidzero-dev:mainfrom
magurotuna:perf/cache-wal-synchronous-normal

Conversation

@magurotuna

Copy link
Copy Markdown
Member

Motivation

Every cache commit in vp run waits for an fsync of the WAL. The cache opens the database in WAL mode but leaves SQLite's default synchronous=FULL, and saving one task's result commits twice: once for cache_entries and once for task_fingerprints. Each cache miss therefore adds two disk syncs before the task counts as finished, so the tasks that depend on it start later.

image

Each run in the chart starts right after vt cache clean, so every task misses the cache and saves its result. The two workspaces are synthetic pnpm workspaces. Each package has one cached task, build, which is a Node.js script that joins five one-line files into dist/out.js. The 3 packages form a chain (app → lib → utils). Each of the 100 packages depends on 0–3 other packages, chosen by a seeded random generator.

Notice that macOS gains almost nothing with this change. This is because a sync there is already fast; SQLite calls fsync(), and on macOS, fsync() moves the data to the drive, but the drive can keep it in its own cache and write it later (fsync(2)).

I haven't measured Windows.

Changes

  • Add the enable_wal function, which runs PRAGMA journal_mode=WAL and checks the returned mode. It sets synchronous=NORMAL only if the result is wal. With NORMAL, commits no longer sync; SQLite syncs the WAL at checkpoints, including the checkpoint when the last connection closes.

Safety

  • In WAL mode, synchronous=NORMAL can't corrupt the database after a power loss or an OS crash, but it can roll back the most recent commits (SQLite docs). For this cache, a rolled-back commit means a cache miss on the next run, so the task runs again. The cache exists only to save time, so this is acceptable.
    • In one rare case, the next run fails instead. When a new entry replaces an old one, vp run first deletes the old entry's output archive. If the new entry is rolled back, the old entry points to the deleted archive, and the run fails with "Cache restore failed. Run vp cache clean to clear the cache". This can already happen on main, because nothing syncs the archives.
  • PRAGMA journal_mode=WAL does not report an error when SQLite cannot use WAL. It keeps the current mode, usually a rollback journal, and returns that mode's name. With a rollback journal, NORMAL has a small chance of corrupting the database after a power loss, so synchronous stays FULL unless the result is wal.
  • A process crash, including SIGKILL, does not lose committed transactions, because the data is already in the OS page cache.
  • The schema and the file format don't change, and SQLite doesn't store synchronous in the file. I checked that main and this PR can use the cache entries that the other wrote.

@fengmk2
fengmk2 requested a review from wan9chi October 21, 2026 07:17
magurotuna and others added 6 commits October 21, 2026 15:47
The cache database runs in WAL mode with SQLite's default
`synchronous=FULL`, so every commit waits for a sync of the WAL. Saving a
task's result commits twice (the cache entry and the task fingerprint), so
each task that misses the cache waits for two syncs before the run moves on.

Set `synchronous=NORMAL` when WAL mode is on. In WAL mode this never
corrupts the database: a power loss or an OS crash can only roll back the
most recent commits, which become cache misses. The WAL is still synced at
checkpoints, including the one when the last connection closes. If SQLite
can't use WAL (for example, on a network share on macOS), `synchronous`
stays `FULL`, because `NORMAL` with a rollback journal can corrupt the
database after a power loss.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@cursor
cursor Bot force-pushed the perf/cache-wal-synchronous-normal branch from b65fb24 to 21f3415 Compare October 21, 2026 15:47

@wan9chi wan9chi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@cursor
cursor Bot merged commit a3c087a into voidzero-dev:main Oct 21, 2026
18 checks passed
fengmk2 added a commit to voidzero-dev/vite-plus that referenced this pull request Oct 22, 2026
Bumps vite-task from `8f73655` to `a3c087a`.

Changelog:
voidzero-dev/vite-task@8f73655...a3c087a#diff-06572a96a58dc510037d5efa622f9bec8519bc1beab13c9f251e97e657a9d4ed

## User-facing vite-task changes

- When tasks miss the cache, `vp run` no longer waits for the disk after
saving each task's result, so the run finishes sooner. If the computer
loses power or the operating system crashes during `vp run`, the cache
entries that the run saved can be lost, and those tasks run again next
time instead of hitting the cache (voidzero-dev/vite-task#871).

No consuming-side code changes were required. Docs are unchanged: the
cache guide does not describe write durability or a disk wait after
cache misses.

There were no earlier unmerged bump pull requests from this automation
to close. The previous bump (#3168) is already merged. Draft #3113 is a
separate preview branch and was left open.

## Validation

- `cargo check --workspace --all-features --all-targets`
- `cargo test` for `vp_command`, `vp_error`, `vp_pm_cli`,
`vp_js_runtime`, `vp_migration`, `vp_shared`, `vp_static_config`,
`vite-plus-cli`, and `vp_global_cli` (all passed)
- Snapshot runner compiled as part of the workspace check. The vite-task
diff does not change CLI help or snapshot-test crate APIs, so recorded
snapshots were not re-recorded.
- `Cargo.lock` only updates the vite-task git revision. Crate versions
are unchanged from the lockfile that landed with #3168.


<div><a
href="https://cursor.com/agents/bc-6f3002ad-049c-4fb1-b89b-79405144c7a5?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/automations/3a586a0e-cc2b-11f1-bec7-b2318963e0b6"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/view-automation-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/view-automation-light.png"><img
alt="View Automation" width="141" height="28"
src="https://cursor.com/assets/images/view-automation-dark.png"></picture></a>&nbsp;</div>

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Charles Wang <wan9chi@users.noreply.github.com>
Co-authored-by: Charles Wang <me@wan9chi.com>
Co-authored-by: MK (fengmk2) <fengmk2@gmail.com>
@magurotuna
magurotuna deleted the perf/cache-wal-synchronous-normal branch October 22, 2026 03:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants