rustfs/rustfs · error · io::Error

decoded stripe is missing a data shard

Error message

decoded stripe is missing a data shard

What it means

While emitting a decoded block on the streaming read path, every one of the first data_shards stripe slots must hold bytes. A None slot after reconstruction means the erasure decode could not rebuild that data shard: too few healthy shards were available (read quorum lost) or bitrot verification dropped shards during this stripe's fetch.

Source

Thrown at crates/ecstore/src/erasure/coding/decode_reader.rs:806

    };
    rustfs_io_metrics::record_get_object_reconstruct_outcome(metrics_path, engine.engine_name(), reconstruct_outcome);
    record_get_stage_duration_if_enabled(metrics_path, GET_STAGE_RECONSTRUCT, reconstruct_stage_start);

    if state.shards_mut().len() < engine.data_shards() {
        return Err(io::Error::new(
            ErrorKind::UnexpectedEof,
            "decoded stripe has fewer shards than data shard count",
        ));
    }

    let emit_stage_start = get_stage_timer_if_enabled(stage_metrics_enabled);
    reserve_output_capacity(output, engine.block_size().min(remaining));
    for shard in state.shards_mut().iter().take(engine.data_shards()) {
        if output.len() >= remaining {
            break;
        }
        let Some(shard) = shard else {
            return Err(io::Error::new(ErrorKind::UnexpectedEof, "decoded stripe is missing a data shard"));
        };
        let copy_len = shard.len().min(remaining - output.len());
        output.extend_from_slice(&shard[..copy_len]);
    }
    record_get_stage_duration_if_enabled(metrics_path, GET_STAGE_EMIT, emit_stage_start);

    Ok(true)
}

#[allow(dead_code, reason = "shard emission asserted by this file's tests (backlog#1823)")]
fn emit_data_shards(state: &StripeReadState, data_shards: usize, block_size: usize, remaining: usize) -> io::Result<Vec<u8>> {
    let mut output = Vec::new();
    emit_data_shards_into(state, data_shards, block_size, remaining, &mut output)?;
    Ok(output)
}

fn reserve_output_capacity(output: &mut Vec<u8>, target_capacity: usize) {
    if output.capacity() < target_capacity {

View on GitHub (pinned to 9e6e02ea09)

Solutions

  1. Check set health and bring offline drives back (remount disks, fix network)
  2. Inspect disks for errors (dmesg, SMART) and replace failing ones
  3. Run rustfs-admin heal for the affected bucket/object
  4. Confirm the read quorum: online and healthy drives must be at least data_shards before reads can succeed
Defensive patterns

Strategy: try-catch

Validate before calling

// preflight: quorum must be readable before starting the GET
let healthy = set.disks().iter().filter(|d| d.is_online()).count();
if healthy < set.data_shards() {
    return Err(read_quorum_lost(healthy, set.data_shards()));
}

Try / catch

match res {
    Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof && e.to_string().contains("missing a data shard") => {
        // respond 503/500, trigger heal for the object, surface drive health
    }
    other => other,
}

Prevention

When it happens

Trigger: GET when the number of failed, unreachable, or corrupt drives exceeds parity_shards; a data-shard read failed bitrot or IO verification during this stripe, leaving a None slot reconstruction cannot fill.

Common situations: Multiple dead disks in one erasure set; network flap making remote endpoints unreachable; bitrot on data parts detected mid-read.

Related errors


AI-assisted analysis of rustfs/rustfs@9e6e02ea09 (2026-08-16). Data as JSON: /api/errors/4a9529d70bb756ac. Report an issue: GitHub.