quickwit-oss/quickwit · error · anyhow::Error

timed out while waiting for ingester to transition to status

Error message

timed out while waiting for ingester to transition to status {status} after {}

What it means

`wait_for_ingester_status_inner` opens an observation stream to an ingester and waits for it to report the desired status, guarded by a timeout. If the sleep branch of the select fires first, the wait failed: the ingester never reached the requested status (Ready or ReadyForCopy) within the deadline. Callers attach context about which ingester and which status was expected.

Solutions

  1. Increase the timeout passed to `wait_for_ingester_status`/`wait_for_ingester_decommission` for slow starts or large WAL replays.
  2. Inspect the ingester logs/metrics to find why it never reached the target status (WAL replay loop, missing control-plane connectivity).
  3. Verify cluster connectivity between the caller and the ingester's gRPC port.
  4. Retry the operation once the ingester is healthy; for decommission, ensure indexing tasks can be rescheduled.

Example fix

// before
wait_for_ingester_status(&mut ingester, ingester_status::Status::Ready, Duration::from_secs(5)).await?;
// after
wait_for_ingester_status(&mut ingester, ingester_status::Status::Ready, Duration::from_secs(60)).await?;
Defensive patterns

Strategy: retry

Validate before calling

// before waiting, confirm the ingester responds at all
let health = ingester.check_health().await
    .context("ingester unreachable; fix connectivity before waiting for status")?;

Try / catch

match wait_for_ingester_status(&mut ingester, Status::Ready, timeout).await {
    Err(e) if e.to_string().contains("timed out while waiting") => {
        warn!(error = %e, "ingester not ready in time; retrying with longer timeout");
        wait_for_ingester_status(&mut ingester, Status::Ready, timeout * 4).await?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling `wait_for_ingester_status` or `wait_for_ingester_decommission` while the target ingester keeps reporting a different status until `timeout_after` elapses — e.g. an ingester stuck starting up, still replaying WAL, or never reacting to a decommission request.

Common situations: Ingester pod slow to start (large WAL replay) during cluster rollout; decommissioning a node whose ingester can't drain because control-plane can't place its indexing tasks; network partition making the observation stream stale.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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

Appendix: source

Thrown at quickwit/quickwit-ingest/src/ingest_v2/helpers.rs:146

    timeout_after: Duration,
) -> Result<(), (anyhow::Error, Option<ObservationMessage>)> {
    debug_assert!(
        timeout_after > Duration::ZERO,
        "timeout_after should be greater than zero"
    );
    let mut last_observation: Option<ObservationMessage> = None;

    let sleep = tokio::time::sleep(timeout_after);
    tokio::pin!(sleep);

    let mut observation_stream = tokio::select! {
        result = ingester.open_observation_stream(OpenObservationStreamRequest {}) => {
            result
                .context("failed to open observation stream")
                .map_err(|error| (error, None))?
        }
        _ = &mut sleep => {
            let error = anyhow!(
                "timed out while waiting for ingester to transition to status {status} after {}",
                timeout_after.pretty_display(),
            );
            return Err((error, None));
        }
    };
    loop {
        tokio::select! {
            observation = observation_stream.next() => {
                match observation {
                    Some(Ok(observation_message)) => {
                        if observation_message.status() == status {
                            return Ok(());
                        }
                        last_observation = Some(observation_message);
                    }
                    Some(Err(error)) => {
                        let error = anyhow!(error).context("observation stream failed");

View on GitHub (pinned to a39730c5cd)