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
- Set both time_range_start_inclusive and time_range_end_inclusive, or remove both
- Repair the metadata record with a script that enforces the paired-field invariant
- 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
- Always write both bounds together when constructing recovery metadata
- Use the RecoveringSplitMetadata constructor rather than hand-built structs
- Run metadata repair tools that enforce the paired-field invariant
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
- invalid recovery time range: start {start} is after end {end
- missing recovery index UID
- missing recovery doc mapping UID
- metastore factory and config backends do not match: {:?} vs.
- invalid recovery footer offsets
AI-assisted analysis of quickwit-oss/quickwit@a39730c5cd (2026-09-08).
Data as JSON: /api/errors/cbb2f7636e333b29.
Report an issue: GitHub.