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
- Verify the snapshot object exists and is non-empty (check object size in backup storage) and re-run restore against a complete backup.
- Confirm the snapshot key/path points at the meta snapshot object, not a manifest or other artifact.
- Re-create the backup with `risectl meta snapshot` (or the backup service) and validate the stored size before restoring.
- 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
- Always fetch snapshots via their manifest key, never a guessed path.
- Check object content_length > 8 before attempting decode.
- Validate backups periodically with a dry-run decode.
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
- metadata snapshot is truncated while reading u32
- prost payload length
- unexpected EOF while decoding meta snapshot
- BackupStorage error
- Checksum mismatch: expected
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)