quickwit-oss/quickwit · error

recovery time range must contain both start and end

Error message

recovery time range must contain both start and end

What it means

Thrown by try_from_recovery_metadata when a recovery time range specifies only one of the two bounds: exactly one of time_range_start_inclusive / time_range_end_inclusive is present. The invariant is both-or-neither.

Source

Thrown at quickwit/quickwit-metastore/src/split_metadata.rs:216

            partition_id,
            num_docs,
            uncompressed_docs_size_bytes,
            time_range_start_inclusive,
            time_range_end_inclusive,
            create_timestamp,
            tags,
            delete_opstamp,
            num_merge_ops,
            parent_split_ids,
            maturation_period_millis,
        } = recovery_metadata;
        let time_range = match (time_range_start_inclusive, time_range_end_inclusive) {
            (Some(start), Some(end)) if start <= end => Some(start..=end),
            (None, None) => None,
            (Some(start), Some(end)) => {
                bail!("invalid recovery time range: start {start} is after end {end}")
            }
            _ => bail!("recovery time range must contain both start and end"),
        };
        ensure!(
            !footer_offsets.is_empty(),
            "invalid recovery footer offsets"
        );
        let maturity = match maturation_period_millis {
            Some(maturation_period_millis) => SplitMaturity::Immature {
                maturation_period: Duration::from_millis(maturation_period_millis),
            },
            None => SplitMaturity::Mature,
        };
        let split_metadata = Self {
            split_id: split_id.into(),
            index_uid: index_uid.ok_or_else(|| anyhow::anyhow!("missing recovery index UID"))?,
            partition_id,
            source_id,
            node_id,
            num_docs: num_docs.try_into()?,

View on GitHub (pinned to a39730c5cd)

Solutions

  1. Set both time_range_start_inclusive and time_range_end_inclusive, or remove both
  2. Repair the metadata record with a script that enforces the paired-field invariant
  3. Restore the affected metadata from backup

Example fix

// before
{"time_range_start_inclusive": 1600, "time_range_end_inclusive": null}
// after
{"time_range_start_inclusive": 1600, "time_range_end_inclusive": 1700}
Defensive patterns

Strategy: validation

Validate before calling

fn bounds_paired(start: Option<i64>, end: Option<i64>) -> bool {
    matches!((start, end), (Some(_), Some(_)) | (None, None))
}

Type guard

fn has_paired_bounds(m: &RecoveryMetadata) -> bool {
    m.time_range_start_inclusive.is_some() == m.time_range_end_inclusive.is_some()
}

Try / catch

if let Err(e) = SplitMetadata::try_from(meta) {
    if e.to_string().contains("must contain both start and end") {
        error!("partial time range in metadata: {e}");
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Deserializing split metadata whose recovery record sets one timestamp bound but not the other, e.g. partial JSON written by an interrupted tool or hand-edit.

Common situations: Manually patching split metadata and filling only start; a metadata writer bug that omitted the paired field.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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