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

  1. Run the sync server with the same tursodb release that wrote the MVCC log (byte 4 must equal MVCC_LOG_VERSION = 3)
  2. If version skew is intentional, archive or delete the MVCC logical log so the server starts fresh; clients resynchronize through the replace-base fallback
  3. 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

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


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)