tursodatabase/turso · error

truncated MVCC logical log header: len={} header_size={}

Error message

truncated MVCC logical log header: len={} header_size={}

What it means

scan_mvcc_log requires at least MVCC_LOG_HEADER_SIZE (56) bytes before validating anything. A .db-log that exists but is 1-55 bytes cannot hold a valid lml3 header and fails with HTTP 500. A zero-byte file is special-cased as an empty log, and a missing file triggers the replace-base fallback, so only short-but-nonempty files land here.

Source

Thrown at cli/sync_server.rs:951

        return offset
            .parse::<u64>()
            .map_err(|err| anyhow!("invalid MVCC pull revision offset: {revision}: {err}"));
    }
    // Older page bootstrap responses from this test server used WAL frame
    // numbers. Treat them as "the page snapshot already includes the current
    // logical log" so the required follow-up logical pull becomes a no-op.
    Ok(legacy_default)
}

fn scan_mvcc_log(log: &[u8]) -> Result<MvccLogSnapshot> {
    if log.is_empty() {
        return Ok(MvccLogSnapshot {
            end_offset: 0,
            crc_by_offset: vec![(0, 0)],
        });
    }
    if log.len() < MVCC_LOG_HEADER_SIZE {
        return Err(anyhow!(
            "truncated MVCC logical log header: len={} header_size={}",
            log.len(),
            MVCC_LOG_HEADER_SIZE
        ));
    }
    validate_mvcc_log_header(log)?;
    let mut running_crc = initial_mvcc_log_crc(log)?;
    let mut offset = MVCC_LOG_HEADER_SIZE;
    let mut crc_by_offset = vec![(MVCC_LOG_HEADER_SIZE as u64, running_crc)];

    while offset < log.len() {
        let Some((frame_end, frame_crc)) = read_mvcc_frame_boundary(log, offset, running_crc)?
        else {
            break;
        };
        running_crc = frame_crc;
        offset = frame_end;
        crc_by_offset.push((offset as u64, running_crc));

View on GitHub (pinned to bad083fafb)

Solutions

  1. Move or delete the short .db-log — the server then takes the NotFound path and answers with replace-base pages so clients resync.
  2. Treat .db and .db-log as an atomic pair: recreate both together.
  3. If truncation recurs after crashes, investigate MVCC log write durability (the header should be written atomically or last).
Defensive patterns

Strategy: validation

Validate before calling

if let Ok(meta) = std::fs::metadata(&log_path) {
    let len = meta.len() as usize;
    if len > 0 && len < 56 {
        // short-but-nonempty log will be rejected; remove it so the server
        // falls back to replace-base pages
        std::fs::remove_file(&log_path)?;
    }
}

Try / catch

On 500 'truncated MVCC logical log header', move the .db-log aside (server then serves replace-base pages), let clients re-bootstrap, and investigate why the write was interrupted.

Prevention

When it happens

Trigger: A crash-truncated first write of the log; a stray or manually created small file at the .db-log path; an incompatible writer that produced only a few bytes.

Common situations: Killing the process during log creation; leftover files from a different tool sharing the .db-log name; partially synced/copied file pairs.

Related errors


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