tursodatabase/turso · error

invalid MVCC logical log header length: {header_len}

Error message

invalid MVCC logical log header length: {header_len}

What it means

The MVCC logical log begins with a fixed 56-byte header (MVCC_LOG_HEADER_SIZE). Bytes 6-8 hold a little-endian u16 header length that must equal 56; validate_mvcc_log_header throws when any other value is present. The magic (0x4C4D4C32), version (3), and flags checks run first, so reaching this error means the file claims to be a current MVCC log but encodes a different header layout, and the scanner refuses to misread field offsets.

Source

Thrown at cli/sync_server.rs:995

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"
        ));
    }
    let stored_crc = read_u32_le(log, MVCC_LOG_HEADER_CRC_START)?;
    let mut crc_buf = [0u8; MVCC_LOG_HEADER_SIZE];
    crc_buf.copy_from_slice(&log[..MVCC_LOG_HEADER_SIZE]);
    crc_buf[MVCC_LOG_HEADER_CRC_START..MVCC_LOG_HEADER_SIZE].fill(0);
    let expected_crc = crc32c::crc32c(&crc_buf);
    if stored_crc != expected_crc {
        return Err(anyhow!("MVCC logical log header checksum mismatch"));

View on GitHub (pinned to bad083fafb)

Solutions

  1. Re-pull or regenerate the MVCC logical log from the producer; a header-length mismatch means the local copy cannot be parsed safely.
  2. Hexdump the first 56 bytes (xxd -l 56 <log>) and verify the magic 0x4C4D4C32 at offset 0 and the u16 at offsets 6-8 equals 56 (0x38).
  3. Confirm writer and reader run the same engine build (56-byte header, MVCC_LOG_VERSION 3) and upgrade the older side.
  4. Check the file was not mixed up or partially copied: compare size and hash against the producer's log.

Example fix

// before
let snapshot = scan_mvcc_log(&log)?; // errors: invalid MVCC logical log header length

// after: verify the header layout before scanning, then re-pull on mismatch
fn mvcc_header_length_ok(log: &[u8]) -> bool {
    log.len() >= MVCC_LOG_HEADER_SIZE
        && u16::from_le_bytes([log[6], log[7]]) as usize == MVCC_LOG_HEADER_SIZE
}
if !mvcc_header_length_ok(&log) {
    std::fs::remove_file(&log_path)?;
    return rebootstrap_log_from_server();
}
let snapshot = scan_mvcc_log(&log)?;
Defensive patterns

Strategy: validation

Validate before calling

const MVCC_LOG_HEADER_SIZE: usize = 56;
fn mvcc_header_length_ok(log: &[u8]) -> bool {
    log.len() >= MVCC_LOG_HEADER_SIZE
        && u16::from_le_bytes([log[6], log[7]]) as usize == MVCC_LOG_HEADER_SIZE
}
// run before scan_mvcc_log:
if !mvcc_header_length_ok(&log) {
    anyhow::bail!("log header length is not {MVCC_LOG_HEADER_SIZE}; re-pull the log");
}

Type guard

fn mvcc_header_length_ok(log: &[u8]) -> bool {
    log.len() >= MVCC_LOG_HEADER_SIZE
        && u16::from_le_bytes([log[6], log[7]]) as usize == MVCC_LOG_HEADER_SIZE
}

Try / catch

match scan_mvcc_log(&log) {
    Ok(snapshot) => { /* serve deltas from crc_by_offset */ }
    Err(err) if err.to_string().contains("invalid MVCC logical log header length") => {
        // deterministic format error: re-bootstrap, never retry the same bytes
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: scan_mvcc_log(&log) on a log whose bytes 6-8 decode to anything other than 56: a log written by an experimental or divergent build with a changed header size, a header corrupted after writing, or a hand-built test fixture that filled the length field incorrectly.

Common situations: Sync client and server built from different Turso revisions during a rolling upgrade; experimental MVCC format iterations that kept version=3 but changed header layout; the wrong file copied over the logical log path; truncated-then-repaired copies that garbled the middle of the header.

Related errors


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/fbc1bb1637e1c611. Report an issue: GitHub.