tursodatabase/turso · critical

truncated MVCC logical log header: len=

Error message

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

What it means

scan_mvcc_log found the .db-log file non-empty but shorter than the fixed MVCC log header size, so it cannot even read magic/version/flags. This indicates a truncated or corrupted logical log: a partial write, a crash during log creation, or a foreign/wrong file sitting at the .db-log path.

Solutions

  1. Restore the database and .db-log together from a consistent backup
  2. If the database itself is intact and disposable, delete the invalid .db-log and let clients re-bootstrap (server rebuilds the log)
  3. Check disk space and filesystem health to stop recurrence
Defensive patterns

Strategy: fallback

Validate before calling

// pre-flight before serving logical pulls
let meta = std::fs::metadata(&log_path)?;
if 0 < meta.len() && (meta.len() as usize) < MVCC_LOG_HEADER_SIZE {
    return Err(anyhow!("logical log file is truncated; restore or delete it and re-bootstrap"));
}

Try / catch

match scan_mvcc_log(&log_bytes) {
    Ok(snapshot) => snapshot,
    Err(e) if e.to_string().contains("truncated MVCC logical log header") => {
        // log is unusable: rebuild from the authoritative database and force client re-bootstrap
        regenerate_logical_log()?;
        scan_mvcc_log(&std::fs::read(&log_path)?)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Server hard-killed while the logical log was being created or extended; disk-full truncation; a truncated file copied in as <db>.db-log; then any MVCC logical pull triggers the scan and fails.

Common situations: Power loss or kill -9 during heavy write load; copying files mid-write; filesystem faults; test fixtures with sliced log files.

Related errors


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

Appendix: source

Thrown at cli/sync_server.rs:1067

            .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 492c4a71cd)