tursodatabase/turso · critical
PreparingWalStart requires a WAL
Error message
PreparingWalStart requires a WAL
What it means
Cache-spill state machine: after the WAL header write (SpillState::PreparingWalStart) is durable, the pager issues the fsync that marks the WAL initialized via self.wal.as_ref().expect("PreparingWalStart requires a WAL"). Reaching this state on a pager with no WAL attached (self.wal == None) panics.
Source
Thrown at core/storage/pager.rs:4188
let completions = self.spill_pages_to_disk(&pages, &mut group)?;
if completions.is_empty() {
self.finish_ephemeral_spill(&pages);
return Ok(IOResult::Done(()));
}
*self.spill_state.write() =
SpillState::WritingToDisk { pages, completions };
io_yield_one!(group.build());
}
}
}
}
SpillState::PreparingWalStart { pages, completion } => {
if !completion.succeeded() {
io_yield_one!(completion);
}
// Header (and any truncate) durable — issue the fsync that
// marks the WAL initialized.
let wal = self.wal.as_ref().expect("PreparingWalStart requires a WAL");
let finish_c = wal.prepare_wal_finish(self.get_sync_type())?;
*self.spill_state.write() = SpillState::PreparingWalFinish {
pages,
completion: finish_c,
};
continue;
}
SpillState::PreparingWalFinish { pages, completion } => {
if !completion.succeeded() {
io_yield_one!(completion);
}
// WAL is now initialized; append the spill frames.
return self.spill_append_frames_to_wal(pages);
}
SpillState::WritingToWal { pages, completions } => {
for c in &completions {
if !c.succeeded() {
io_yield_one!(c.clone());View on GitHub (pinned to 492c4a71cd)
Solutions
- Report to Turso: spill-without-WAL should be rejected with an error, not a panic
- Open file-backed databases in WAL mode when large write statements are expected
- Increase the page cache so the dirty set fits without spilling
- In embedder code, disable spilling explicitly on WAL-less databases with pager.set_spill_enabled(false)
Example fix
// before: in-memory database, spill enabled by default under cache pressure let db = Database::open_file(io, ":memory:", false)?; // no WAL // huge INSERT ... overwhelms cache -> spill -> panic // after: keep dirty set within cache / disable spill on WAL-less pagers pager.set_spill_enabled(false); // and/or commit in smaller batches
Defensive patterns
Strategy: validation
Validate before calling
// For WAL-less databases (in-memory or otherwise), keep the dirty set below
// the cache so spill never triggers, or disable spill explicitly in embedder code:
if !pager.has_wal_enabled_for_your_setup() {
pager.set_spill_enabled(false);
}
// Application level: open file databases in WAL mode before large writes:
conn.execute("PRAGMA journal_mode=WAL", ())?; Try / catch
let result = std::panic::catch_unwind(AssertUnwindSafe(|| do_big_write(&conn)));
if result.is_err() { conn.close().ok(); /* reopen in WAL mode or raise cache size */ } Prevention
- Use WAL journal mode for file databases that will run large write statements
- Size the page cache above your largest expected dirty set for in-memory databases
- Commit in smaller batches to stay under the spill threshold
- Treat occurrence as an engine bug (spill without WAL) and report it
When it happens
Trigger: Cache spill triggered (dirty pages exceed the page cache) on a database opened without a WAL - in-memory databases or a journal configuration where Pager::wal was never set - so the spill state machine advances into WAL-dependent states.
Common situations: Large single statements on :memory: databases overflowing the cache, engine regressions allowing spill without WAL, custom open paths producing a WAL-less pager.
Related errors
- spill_append_frames_to_wal requires a WAL
- result should be set
- buffer not loaded
- DB should not be initialized and should not do any IO
- Page size too small, a ptrmap page cannot map any db pages.
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20).
Data as JSON: /api/errors/46f2993cdd736645.
Report an issue: GitHub.