risingwavelabs/risingwave · error

current epoch {} has exceeded the epoch {} of the stream tha

Error message

current epoch {} has exceeded the epoch {} of the stream that has not started

What it means

Thrown while draining `not_started_streams` during barrier validation: the incoming epoch has skipped past the epoch recorded for a stream that had not yet started, so its bookkeeping entry can never be matched. The log store tracks streams whose first epoch is in the future; if a barrier arrives with an epoch greater than such a stream's first epoch, the stream's state is inconsistent and cannot be reconciled.

Source

Thrown at src/stream/src/common/log_store_impl/kv_log_store/serde.rs:972

            }
        };

        if prev_epoch >= epoch {
            return Err(anyhow!(
                "epoch {} should be greater than prev epoch {}",
                epoch,
                prev_epoch
            ));
        }

        while let Some((stream_epoch, _)) = self.not_started_streams.last() {
            if *stream_epoch > epoch {
                // Current epoch has not reached the first epoch of
                // the stream. Later streams must also have greater epoch, so break here.
                break;
            }
            if *stream_epoch < epoch {
                return Err(anyhow!(
                    "current epoch {} has exceeded the epoch {} of the stream that has not started",
                    epoch,
                    stream_epoch
                ));
            }
            let (_, stream) = self.not_started_streams.pop().expect("should not be empty");
            self.row_streams.push(stream.into_future());
        }

        self.stream_state = StreamState::AllConsumingRow { curr_epoch: epoch };
        Ok(())
    }

    async fn next_op(&mut self) -> LogStoreResult<Option<AlignedLogStoreRow>> {
        while let (Some(result), stream) = self
            .row_streams
            .next()
            .await

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Verify that a stream's registered first epoch matches the epoch of the first barrier it actually receives.
  2. Check barrier flow so streams that have not started are not skipped by later epochs; resume/start them before the epoch advances past their registration.
  3. Confirm meta node stream-building completion ordering: a stream must start before barriers with greater epochs are validated.
  4. Log `not_started_streams` contents when the error fires to identify which stream's epoch was skipped.

Example fix

// before: barrier epoch advanced past not-started stream
stream_epoch = 100; barrier_epoch = 200; // 100 < 200 -> error
// after: ensure stream starts at/before barrier epoch
stream_epoch = 200; barrier_epoch = 200; // equal -> popped and matched
Defensive patterns

Strategy: validation

Validate before calling

// Rust: before validating a barrier, ensure no not-started stream would be skipped
fn would_skip_epoch(not_started_streams: &[u64], epoch: u64) -> bool {
    not_started_streams.iter().any(|s| s < &epoch)
}

Prevention

When it happens

Trigger: In `validate_epoch`, when popping `not_started_streams` and finding `*stream_epoch < epoch` — a barrier with epoch E arrives for a stream whose recorded first epoch is smaller than E (epoch skipped over it).

Common situations: A stream was registered with an expected start epoch but its data/barriers were processed under a later epoch (actor creation delayed, backfill altered epochs), or meta node updated the stream epoch list inconsistently with barrier flow.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/52c962b5b3a1100e. Report an issue: GitHub.