tursodatabase/turso · warning
unsupported MVCC logical log version
Error message
unsupported MVCC logical log version {} What it means
TursoSyncServer serves incremental MVCC sync from an append-only logical log whose 56-byte header encodes a format version in byte 4 (after the 0x4C4D4C32 magic); this build only accepts MVCC_LOG_VERSION = 3. validate_mvcc_log_header throws when that byte differs, meaning the log was written by an older or newer tursodb release. It is the one error is_nonportable_mvcc_log_error recognizes, so /pull-updates degrades to a replace-base (full pages) fallback instead of failing the request.
Solutions
- Run the sync server with the same tursodb release that wrote the MVCC log (byte 4 must equal MVCC_LOG_VERSION = 3)
- If version skew is intentional, archive or delete the MVCC logical log so the server starts fresh; clients resynchronize through the replace-base fallback
- When changing the log format in a fork, bump MVCC_LOG_VERSION and extend is_nonportable_mvcc_log_error so older readers fall back instead of erroring
Example fix
// before
let snapshot = scan_mvcc_log(&log)?;
// after: version skew falls back to replace-base pages instead of failing
let snapshot = match scan_mvcc_log(&log) {
Ok(snapshot) => snapshot,
Err(err) if is_nonportable_mvcc_log_error(&err) => {
info!("MVCC log not portable; serving replace-base pages: {err}");
return self.handle_logical_fallback(
req,
fallback_revision,
&legacy_current_revision,
db_size,
);
}
Err(err) => return Err(err),
}; Defensive patterns
Strategy: fallback
Validate before calling
// Before serving logical pulls, check the version byte:
fn mvcc_log_version_is_supported(log: &[u8]) -> bool {
log.len() >= 5 && log[4] == MVCC_LOG_VERSION // 3 in this build
} Try / catch
match scan_mvcc_log(&log) {
Ok(snapshot) => { /* serve logical deltas */ }
Err(err) if is_nonportable_mvcc_log_error(&err) => {
// version skew: degrade to replace-base pages
self.handle_logical_fallback(req, fallback_revision, &legacy_current_revision, db_size)
}
Err(err) => Err(err),
} Prevention
- Keep the sync-server binary and the process that writes the MVCC log on the same tursodb release
- Back up or delete the MVCC logical log before upgrading/downgrading the binary
- Never hand-copy logical logs between nodes running different versions
When it happens
Trigger: A POST /pull-updates logical pull where the MVCC log file exists and is at least 56 bytes, and scan_mvcc_log -> validate_mvcc_log_header reads log[4] with any value other than 3 - e.g. a version-2 log from an older build or version 4+ from a newer build.
Common situations: Upgrading or downgrading the tursodb sync-server binary while keeping the old MVCC logical log on disk; copying a log file between nodes running different releases; replaying a captured log from CI against a newer parser.
Related errors
- invalid MVCC logical log magic
- unsupported MVCC logical log frame flags at offset
- buffer too short for u32 at offset
- buffer too short for u64 at offset
- invalid MVCC logical log frame end magic at offset
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20).
Data as JSON: /api/errors/44b04ca2fdd9c99c.
Report an issue: GitHub.
Appendix: source
Thrown at cli/sync_server.rs:1103
}
Ok(MvccLogSnapshot {
end_offset: offset as u64,
crc_by_offset,
})
}
fn is_nonportable_mvcc_log_error(err: &anyhow::Error) -> bool {
let message = err.to_string();
message.starts_with("unsupported MVCC logical log version ")
}
fn validate_mvcc_log_header(log: &[u8]) -> Result<()> {
if read_u32_le(log, 0)? != MVCC_LOG_MAGIC {
return Err(anyhow!("invalid MVCC logical log magic"));
}
if log[4] != MVCC_LOG_VERSION {
return Err(anyhow!("unsupported MVCC logical log version {}", log[4]));
}
if log[5] & 0b1111_1110 != 0 {
return Err(anyhow!("invalid MVCC logical log header flags"));
}
let header_len = u16::from_le_bytes([log[6], log[7]]) as usize;
if header_len != MVCC_LOG_HEADER_SIZE {
return Err(anyhow!(
"invalid MVCC logical log header length: {header_len}"
));
}
if log[MVCC_LOG_HEADER_RESERVED_START..MVCC_LOG_HEADER_CRC_START]
.iter()
.any(|byte| *byte != 0)
{
return Err(anyhow!(
"MVCC logical log header reserved bytes must be zero"
));
}View on GitHub (pinned to 492c4a71cd)