databendlabs/databend · error · InvalidData

Fst data is corrupted

Error message

Fst data is corrupted: {:?}

What it means

custom_search_impl builds Fst maps from stored column data; if Fst::new fails to parse the stored FST bytes it wraps the error in an io::Error(InvalidData) reading 'Fst data is corrupted: {:?}'. This means the persisted FST (finite-state transducer) for an inverted-index field is unreadable — truncated, empty-but-marked-present, or written by an incompatible fst version. The search via search() cannot proceed for that field.

Solutions

  1. Verify the fst-{field_id} column blob is complete (compare against expected size / re-read from storage)
  2. Rebuild the inverted index for the affected fields so the FST is regenerated
  3. Check that reader and writer use compatible fst crate versions / index format versions
  4. Restore the segment/index files from snapshot or backup

Example fix

// before: assuming present blob is valid
let fst = Fst::new(fst_data).map_err(|err| io::Error::new(ErrorKind::InvalidData, format!("Fst data is corrupted: {:?}", err)))?;
// after: treat empty/corrupt as absent and fall back
let fst = Fst::new(fst_data).unwrap_or_else(|_| Fst::from_iter_str(vec![String::new()])); // empty FST fallback
Defensive patterns

Strategy: try-catch

Validate before calling

// Fst::new validates magic/version; pre-check presence and non-emptiness
if fst_blob.is_empty() { return Err("fst column is empty; index needs rebuild"); }

Type guard

fn fst_data_ok(b: &[u8]) -> bool { !b.is_empty() && b.len() >= 8 }

Try / catch

let fst = match Fst::new(fst_data) {
    Ok(f) => f,
    Err(err) => {
        log::warn!("fst-{} corrupted: {:?}; rebuilding index", field_id, err);
        rebuild_inverted_index(field_id).await?
    }
};

Prevention

When it happens

Trigger: Inverted-index search where the column file named 'fst-{field_id}' exists but its bytes fail Fst::new validation (bad magic/version/checksum, truncation, empty vector).

Common situations: Partially written index segments after a crash; corrupted snapshot files; index format version skew between writer and reader; manual file truncation during copy.

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 databendlabs/databend@288d84d76e (2026-09-11). Data as JSON: /api/errors/6e358493e528f3d6. Report an issue: GitHub.

Appendix: source

Thrown at src/query/storages/fuse/src/io/read/inverted_index/inverted_index_reader.rs:277

            if let Some(term_col_meta) = inverted_index_meta_map.remove(&term_col_name) {
                let term_range = term_col_meta.offset..(term_col_meta.offset + term_col_meta.len);
                columns.push((term_col_name, term_range));
            }
        }

        let column_files =
            legacy_load_inverted_index_files(settings, columns, index_path, &self.dal).await?;
        let mut column_files_map = column_files
            .into_iter()
            .map(|f| (f.name.clone(), f.data.clone()))
            .collect::<HashMap<_, _>>();

        let mut fst_maps = HashMap::with_capacity(field_ids.len());
        for field_id in field_ids {
            let fst_col_name = format!("fst-{}", field_id);
            let fst = if let Some(fst_data) = column_files_map.remove(&fst_col_name) {
                Fst::new(fst_data).map_err(|err| {
                    std::io::Error::new(
                        std::io::ErrorKind::InvalidData,
                        format!("Fst data is corrupted: {:?}", err),
                    )
                })?
            } else {
                // If the FST data does not exist, create an empty FST.
                // This means that the field does not have any valid terms.
                let builder = tantivy_fst::MapBuilder::memory();
                let bytes = builder.into_inner().unwrap();
                let fst_data = OwnedBytes::new(bytes);
                Fst::new(fst_data).unwrap()
            };
            let fst_map = tantivy_fst::Map::from(fst);
            fst_maps.insert(*field_id, fst_map);
        }

        // 2. check whether query is matched in the fsts.
        let mut matched_terms = HashMap::new();

View on GitHub (pinned to 288d84d76e)