tursodatabase/turso · error
invalid MVCC logical log frame magic at offset
Error message
invalid MVCC logical log frame magic at offset {offset}: {frame_magic:#x} What it means
While walking frames, read_mvcc_frame_boundary requires the u32 at the current offset to be either MVCC_TX_FRAME_MAGIC or MVCC_TX_EXT_FRAME_MAGIC. A different value means the walk landed on bytes that are not a frame start. Note the distinction: a torn final frame (not enough bytes for header+trailer) returns Ok(None) and the scan stops cleanly, so this error means the bytes are present but wrong - real mid-log corruption, not truncation.
Solutions
- Restore the MVCC log from a known-good backup
- Regenerate the log (delete + resync via replace-base fallback)
- Ensure exactly one sync-server process owns the log file and writes are atomic (write to temp, fsync, rename/append)
Defensive patterns
Strategy: try-catch
Try / catch
match scan_mvcc_log(&log) {
Ok(snapshot) => serve(snapshot),
Err(err) if err.to_string().starts_with("invalid MVCC logical log frame magic") => {
warn!("mid-log corruption at {}: {err}", err);
// truncate to last known-good prefix is NOT safe to improvise:
// restore from backup or regenerate the log
restore_or_rebuild()?;
}
Err(err) => return Err(err),
} Prevention
- Guarantee single-writer ownership of the log file (one sync-server process)
- Append frames atomically and fsync before advertising the new length
- Do not copy a log while it is actively being appended
When it happens
Trigger: scan_mvcc_log iterates offsets after the 56-byte header; at some offset the first u32 is neither frame magic - e.g. garbage appended to the log, a frame overwritten mid-way, or a payload-size error in a previous frame that desynced the walk.
Common situations: Two processes appending to the same logical log concurrently; a crash mid-append followed by an unrelated write; copying the log with a tool that altered bytes; disk-level corruption.
Related errors
- invalid MVCC logical log frame end magic at offset
- invalid MVCC logical log header flags
- invalid MVCC logical log header length
- invalid MVCC logical log magic
- MVCC logical log extension block missing flag at offset
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20).
Data as JSON: /api/errors/92c41f0424c1650e.
Report an issue: GitHub.
Appendix: source
Thrown at cli/sync_server.rs:1154
.try_into()
.expect("fixed-size salt slice"),
);
Ok(crc32c::crc32c(&salt.to_le_bytes()))
}
fn read_mvcc_frame_boundary(
log: &[u8],
offset: usize,
running_crc: u32,
) -> Result<Option<(usize, u32)>> {
if log.len() - offset < MVCC_TX_HEADER_SIZE + MVCC_TX_TRAILER_SIZE {
return Ok(None);
}
let frame_magic = read_u32_le(log, offset)?;
let has_extension_header = frame_magic == MVCC_TX_EXT_FRAME_MAGIC;
if frame_magic != MVCC_TX_FRAME_MAGIC && !has_extension_header {
return Err(anyhow!(
"invalid MVCC logical log frame magic at offset {offset}: {frame_magic:#x}"
));
}
let header_size = if has_extension_header {
MVCC_TX_EXT_HEADER_SIZE
} else {
MVCC_TX_HEADER_SIZE
};
if log.len() - offset < header_size + MVCC_TX_TRAILER_SIZE {
return Ok(None);
}
let payload_size = usize::try_from(read_u64_le(log, offset + 4)?)
.map_err(|_| anyhow!("MVCC logical log payload size overflows usize"))?;
let extension_size = if has_extension_header {
let extension_size = usize::try_from(read_u64_le(log, offset + 24)?)
.map_err(|_| anyhow!("MVCC logical log extension size overflows usize"))?;
let extension_record_count = read_u32_le(log, offset + 32)?;
let frame_flags = read_u32_le(log, offset + 36)?;
if frame_flags & !MVCC_TX_FRAME_FLAG_HAS_EXTENSION_BLOCK != 0 {View on GitHub (pinned to 492c4a71cd)