quickwit-oss/quickwit · error · io::Error (InvalidData)

unsupported split recovery metadata format version: {version

Error message

unsupported split recovery metadata format version: {version} (at line 70; candidate line 75 wraps a dynamic protobuf decode error as io::Error InvalidData)

What it means

The final step of `SplitRecoveryMetadata::deserialize` decodes the remaining bytes with prost (`Self::decode`); any protobuf failure is converted into an io::Error of kind InvalidData via map_err. This means corrupt, truncated, or non-protobuf payloads that pass the magic/version checks surface as a wrapped prost DecodeError rather than the more specific version/magic errors.

Source

Thrown at quickwit/quickwit-proto/src/metastore/mod.rs:75

        use prost::Message;

        if bytes.len() < SPLIT_RECOVERY_METADATA_HEADER_LEN
            || &bytes[..SPLIT_RECOVERY_METADATA_MAGIC.len()] != SPLIT_RECOVERY_METADATA_MAGIC
        {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "invalid split recovery metadata magic number",
            ));
        }
        let version = bytes[SPLIT_RECOVERY_METADATA_MAGIC.len()];
        if version != SPLIT_RECOVERY_METADATA_FORMAT_VERSION {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!("unsupported split recovery metadata format version: {version}"),
            ));
        }
        bytes = &bytes[SPLIT_RECOVERY_METADATA_HEADER_LEN..];
        Self::decode(bytes).map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))
    }
}

#[cfg(test)]
mod split_recovery_metadata_tests {
    use super::SplitRecoveryMetadata;
    use crate::types::{DocMappingUid, IndexUid};

    #[test]
    fn test_split_recovery_metadata_roundtrip_and_unknown_fields() {
        let metadata = SplitRecoveryMetadata {
            split_id: "split-a".to_string(),
            index_uid: Some(IndexUid::for_test("index-a", 1)),
            source_id: "source-a".to_string(),
            node_id: "node-a".to_string(),
            doc_mapping_uid: Some(DocMappingUid::for_test(2)),
            partition_id: 3,
            num_docs: 4,

View on GitHub (pinned to a39730c5cd)

Solutions

  1. Delete and regenerate the corrupted recovery metadata blob.
  2. Verify the byte length matches what the writer recorded (header + protobuf body).
  3. Check the writer path for partial-write bugs (missing fsync/atomic rename).
  4. Unwrap the inner prost DecodeError (it is chained as the io error source) to pinpoint the offending field.
Defensive patterns

Strategy: try-catch

Validate before calling

fn payload_plausible(bytes: &[u8]) -> bool {
    bytes.len() > SPLIT_RECOVERY_METADATA_HEADER_LEN // has protobuf body
}

Try / catch

match SplitRecoveryMetadata::deserialize(&bytes) {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {
    // inspect e.source() for the prost DecodeError to find the corrupt field
    regenerate_metadata()
}
    other => other?,
}

Prevention

When it happens

Trigger: Calling deserialize on bytes with a valid magic header and version byte but whose protobuf payload is corrupt: bit rot, truncation after the header, or garbage appended/mixed into the blob.

Common situations: Partially written recovery metadata from a crashed writer; manual file edits; a storage layer returning wrong or concatenated blobs; nondeterministic payloads (different protobuf schema) despite matching header.

Understand the failure class

Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.

Related errors


AI-assisted analysis of quickwit-oss/quickwit@a39730c5cd (2026-09-08). Data as JSON: /api/errors/f997ec98dd9b438c. Report an issue: GitHub.