{"record":{"id":"d10b5b203ef9ce05","repo":"risingwavelabs/risingwave","slug":"unexpected-bytes-after-meta-snapshot-checksum","errorCode":null,"errorMessage":"unexpected bytes after meta snapshot checksum","messagePattern":"unexpected bytes after meta snapshot checksum","errorType":"exception","errorClass":"BackupError","httpStatus":null,"severity":"error","filePath":"src/storage/backup/src/meta_snapshot.rs","lineNumber":236,"sourceCode":"\n        if tail.len() != size_of::<u64>() {\n            return Err(BackupError::Decoding(\n                anyhow::anyhow!(\"meta snapshot is missing checksum\").into(),\n            ));\n        }\n        Self::verify_checksum_with_hasher(&hasher, &tail)\n    }\n\n    pub async fn finish(mut self) -> BackupResult<()> {\n        let mut checksum = [0; size_of::<u64>()];\n        self.reader.read_exact(&mut checksum).await?;\n        self.verify_checksum(&checksum)?;\n\n        let mut trailing = [0; 1];\n        let n = self.reader.read(&mut trailing).await?;\n        if n != 0 {\n            return Err(BackupError::Decoding(\n                anyhow::anyhow!(\"unexpected bytes after meta snapshot checksum\").into(),\n            ));\n        }\n        Ok(())\n    }\n\n    fn verify_checksum(&self, checksum: &[u8]) -> BackupResult<()> {\n        Self::verify_checksum_with_hasher(&self.hasher, checksum)\n    }\n\n    fn verify_checksum_with_hasher(hasher: &XxHash64, checksum: &[u8]) -> BackupResult<()> {\n        let expected = u64::from_le_bytes(checksum.try_into().expect(\"u64 length\"));\n        let found = hasher.finish();\n        if expected != found {\n            return Err(BackupError::ChecksumMismatch { expected, found });\n        }\n        Ok(())\n    }\n}","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/storage/backup/src/meta_snapshot.rs#L218-L254","documentation":"MetaSnapshot::finish verifies the trailing checksum then reads one extra byte to confirm nothing follows it; if the reader still returns bytes it throws \"unexpected bytes after meta snapshot checksum\". The snapshot stream contains trailing data beyond the defined format, so the object is not a well-formed meta snapshot.","triggerScenarios":"Calling finish() on a reader where bytes remain after the checksum — e.g. the snapshot object has appended/future-incompatible sections written by a newer tool, or the object wraps the snapshot with extra framing/headers.","commonSituations":"A newer RisingWave version wrote extra sections into the snapshot and an older binary tries to read it; an export script concatenated metadata after the snapshot; double-reading a stream that was not consumed exactly once; manual edits to a backup file.","solutions":["Upgrade the RisingWave/risectl binary to a version matching (or newer, with V2 section skipping) the backup producer before restoring.","Verify the snapshot object is exactly the writer's output (compare byte length/checksum from the backup manifest).","Recreate the backup without post-processing (no compression-on-write tricks, no concatenation).","If intentional forward-compat data is present, port the reader to skip unknown trailing sections like decode_hummock_sequences_from_stream does."],"exampleFix":"// before: strict full read\nsnapshot.finish().await?;\n// after: skip unknown trailing sections when tolerated, or ensure exact match\nlet n = reader.read(&mut trailing).await?;\nif n != 0 && !allow_trailing { snapshot.finish().await?; }","handlingStrategy":"try-catch","validationCode":"let size = storage.get_object_size(&key).await?;\nlet expected = manifest.snapshot_size; // written at backup time\nif size != expected {\n    return Err(anyhow!(\"snapshot size mismatch: {} != {}\", size, expected));\n}","typeGuard":"fn is_exact_size(actual: u64, expected: u64) -> bool { actual == expected }","tryCatchPattern":"match snapshot.finish().await {\n    Ok(()) => {},\n    Err(e) if e.to_string().contains(\"unexpected bytes after\") => {\n        log::warn!(\"trailing data in snapshot; producer likely newer version\");\n        // handle per policy: upgrade reader, or reject\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Keep the restoring binary version >= the version that produced the backup.","Never post-process or append to snapshot objects after writing.","Compare restored object byte length to the manifest before decoding."],"tags":["rust","backup","snapshot-format","trailing-data"],"backgroundTag":"unexpected-response-shape","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}