risingwavelabs/risingwave · error · BackupError

metadata snapshot is truncated while reading u32

Error message

metadata snapshot is truncated while reading u32

What it means

read_u32_le is a cursor helper used when parsing metadata snapshot sections; it throws BackupError::Other when fewer than 4 bytes remain in the in-memory buffer, reporting "metadata snapshot is truncated while reading u32". It means a length/count field was expected but the section data ran out mid-parse.

Solutions

  1. Validate the snapshot object size against the backup manifest and restore from an intact backup.
  2. Check that the parser's expected section layout matches the snapshot's format_version.
  3. Re-run the restore and check object store read errors/short reads in logs before concluding corruption.
  4. In tests, build payloads via the writer helpers so all u32 length prefixes are emitted.

Example fix

// before: decoding a manually built buffer missing a length prefix
let m = decode_section(&mut &payload[10..])?; // short slice
// after: guarantee the full section is buffered
ensure!(payload.len() >= section_len, "section truncated");
let m = decode_section(&mut &payload[10..])?;
Defensive patterns

Strategy: validation

Validate before calling

// before calling section decoders, ensure the buffer holds the whole section
if buf.len() < expected_section_len {
    return Err(anyhow!("section buffer truncated: {} < {}", buf.len(), expected_section_len));
}

Type guard

fn has_u32(buf: &[u8]) -> bool { buf.len() >= 4 }

Try / catch

match read_u32_le(&mut buf) {
    Ok(v) => v,
    Err(e) if e.to_string().contains("truncated while reading u32") => {
        return Err(anyhow!("snapshot section truncated; restore from intact backup"));
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling decode functions (e.g. decode_prost_message, section list decoders in meta_snapshot_v2) with a buffer cut off before a u32 length prefix — truncated section payload, a hand-crafted or corrupted snapshot body, or reading a slice boundary incorrectly.

Common situations: Restoring a snapshot whose object storage read truncated the buffer; tests feeding partial section bytes; corrupted backup files; mixing snapshot versions where a section is shorter than the parser expects.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/2862d4db1cdfb1f6. Report an issue: GitHub.

Appendix: source

Thrown at src/storage/backup/src/meta_snapshot.rs:258

    }

    fn verify_checksum(&self, checksum: &[u8]) -> BackupResult<()> {
        Self::verify_checksum_with_hasher(&self.hasher, checksum)
    }

    fn verify_checksum_with_hasher(hasher: &XxHash64, checksum: &[u8]) -> BackupResult<()> {
        let expected = u64::from_le_bytes(checksum.try_into().expect("u64 length"));
        let found = hasher.finish();
        if expected != found {
            return Err(BackupError::ChecksumMismatch { expected, found });
        }
        Ok(())
    }
}

pub(crate) fn read_u32_le(buf: &mut &[u8]) -> BackupResult<u32> {
    if buf.remaining() < 4 {
        return Err(BackupError::Other(anyhow::anyhow!(
            "metadata snapshot is truncated while reading u32"
        )));
    }
    Ok(buf.get_u32_le())
}

impl<T: Metadata> Display for MetaSnapshot<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "format_version: {}", self.format_version)?;
        writeln!(f, "id: {}", self.id)?;
        writeln!(f, "{}", self.metadata)?;
        Ok(())
    }
}

View on GitHub (pinned to 6469eb736d)