risingwavelabs/risingwave · error · BackupError

meta snapshot is missing checksum

Error message

meta snapshot is missing checksum

What it means

MetaSnapshot::read_to_end reads the whole snapshot payload and requires at least 8 trailing bytes for the u64 checksum; if data.len() < size_of::<u64>() it throws BackupError::Decoding("meta snapshot is missing checksum"). It indicates the snapshot stream is too short to even contain the trailing checksum field, i.e. severely truncated or not a meta snapshot at all.

Solutions

  1. Verify the snapshot object exists and is non-empty (check object size in backup storage) and re-run restore against a complete backup.
  2. Confirm the snapshot key/path points at the meta snapshot object, not a manifest or other artifact.
  3. Re-create the backup with `risectl meta snapshot` (or the backup service) and validate the stored size before restoring.
  4. If the stream is intentionally short in tests, seed it with a payload that includes an 8-byte checksum tail.

Example fix

// before: decoding whatever the reader returns
let snap = MetaSnapshotV2::read_to_end().await?;
// after: sanity-check object size first
let size = storage.get_object_size(&key).await?;
if size < 8 { return Err(anyhow!("snapshot object {} truncated", key)); }
let snap = MetaSnapshotV2::read_to_end().await?;
Defensive patterns

Strategy: validation

Validate before calling

let size = storage.get_object_size(&key).await?;
if size < 8 {
    return Err(anyhow!("snapshot object {} too small ({} bytes) to contain a checksum", key, size));
}

Type guard

fn has_checksum_tail(data: &[u8]) -> bool { data.len() >= std::mem::size_of::<u64>() }

Try / catch

match snapshot.read_to_end().await {
    Ok(data) => data,
    Err(e) if e.to_string().contains("missing checksum") =>
        return Err(anyhow!("snapshot object empty/truncated; pick a valid backup")),
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling read_to_end() on a MetaSnapshotV1/V2 whose underlying reader yields fewer than 8 bytes total (empty or near-empty object, wrong key fetched, truncated upload).

Common situations: Fetching the wrong object key from backup storage; an empty snapshot object created by a failed backup job; a file replaced/truncated on disk in local backup testing; restoring a v2 snapshot from an empty S3 prefix.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

    pub async fn skip_exact(&mut self, mut len: usize) -> BackupResult<()> {
        const BUFFER_SIZE: usize = 1024;
        let mut buf = [0; BUFFER_SIZE];
        while len > 0 {
            let read_len = len.min(BUFFER_SIZE);
            self.reader.read_exact(&mut buf[..read_len]).await?;
            self.hasher.write(&buf[..read_len]);
            len -= read_len;
        }
        Ok(())
    }

    pub async fn read_to_end(mut self) -> BackupResult<Vec<u8>> {
        let mut data = vec![];
        self.reader.read_to_end(&mut data).await?;
        if data.len() < size_of::<u64>() {
            return Err(BackupError::Decoding(
                anyhow::anyhow!("meta snapshot is missing checksum").into(),
            ));
        }

        let checksum = data.split_off(data.len() - size_of::<u64>());
        self.hasher.write(&data);
        self.verify_checksum(&checksum)?;
        Ok(data)
    }

    pub async fn finish_after_skipping_to_end(self) -> BackupResult<()> {
        let Self { reader, mut hasher } = self;
        let mut bytes_stream = reader.into_bytes_stream();
        let mut tail = BytesMut::with_capacity(size_of::<u64>());

        while let Some(bytes) = bytes_stream.next().await.transpose()? {
            let payload_len = tail.len() + bytes.len();
            if payload_len <= size_of::<u64>() {
                tail.extend_from_slice(&bytes);

View on GitHub (pinned to 6469eb736d)