risingwavelabs/risingwave · error · BackupError

unexpected EOF while decoding meta snapshot

Error message

unexpected EOF while decoding meta snapshot

What it means

BackupError::from(std::io::Error) maps an UnexpectedEof IO error to BackupError::Decoding with the message "unexpected EOF while decoding meta snapshot". It means the reader reached end-of-file while reading a metadata snapshot from backup storage, i.e. the snapshot file/stream is shorter than the decoder expected. The conversion exists so truncated snapshot reads surface as decoding errors rather than generic storage errors.

Solutions

  1. Re-upload or restore from a different, intact meta snapshot backup; verify the snapshot object's byte length matches expectations.
  2. Check that the backup was fully written (compare content_length/checksum recorded at creation time against the stored object).
  3. Verify you are decoding with the reader version matching the snapshot format version (V1 vs V2).
  4. If reproducible, capture the object store logs / read path (ObjectDataStreamReader) and file a bug with the backup manifest.

Example fix

// before: blindly decoding a snapshot path from env
let snap = MetaSnapshotV2::decode_from_reader(reader).await?;
// after: validate snapshot size/known-good backup first
assert_snapshot_intact(&manifest, &storage)?; // compares length + checksum
let snap = MetaSnapshotV2::decode_from_reader(reader).await?;
Defensive patterns

Strategy: validation

Validate before calling

let size = storage.get_object_size(&snapshot_key).await?;
if size < MIN_SNAPSHOT_SIZE { return Err(anyhow!("snapshot {} truncated ({} bytes)", snapshot_key, size)); }
// optionally verify recorded checksum before decoding
verify_manifest_checksum(&manifest, &storage, &snapshot_key).await?;

Type guard

fn is_snapshot_intact(manifest: &BackupManifest, actual_size: u64) -> bool {
    actual_size == manifest.snapshot_size && actual_size >= 8
}

Try / catch

match MetaSnapshotV2::decode_from_reader(reader).await {
    Ok(snap) => snap,
    Err(e) if e.to_string().contains("unexpected EOF") => {
        return Err(anyhow!("snapshot corrupted/truncated; use another backup"));
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Reading a meta snapshot via MetaSnapshotV1/V2 readers (decode_from_reader, read_to_end, decode_hummock_sequences_from_stream, finish) when the underlying object storage reader returns UnexpectedEof mid-decode, e.g. the snapshot object was truncated, partially uploaded, or the object reader's expected size exceeds the actual stored bytes.

Common situations: A backup upload was interrupted so the stored snapshot object is truncated; the wrong (older) snapshot version is fetched with a newer reader expecting more data; object storage returns fewer bytes than the manifest claims; manual tampering or a corrupted backup file.

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/dddd93818fff4ace. Report an issue: GitHub.

Appendix: source

Thrown at src/storage/backup/src/error.rs:67

        #[source]
        BoxedError,
    ),
    #[error("Checksum mismatch: expected {expected}, found: {found}")]
    ChecksumMismatch { expected: u64, found: u64 },
    #[error("Meta storage is not empty before being restored")]
    NonemptyMetaStorage,
    #[error(transparent)]
    Other(
        #[from]
        #[backtrace]
        anyhow::Error,
    ),
}

impl From<std::io::Error> for BackupError {
    fn from(err: std::io::Error) -> Self {
        if err.kind() == std::io::ErrorKind::UnexpectedEof {
            Self::Decoding(anyhow::anyhow!("unexpected EOF while decoding meta snapshot").into())
        } else {
            Self::BackupStorage(err.into())
        }
    }
}

View on GitHub (pinned to 6469eb736d)