risingwavelabs/risingwave · error
epoch {} should be greater than prev epoch {}
Error message
epoch {} should be greater than prev epoch {} What it means
Thrown by the same epoch validation routine when the incoming epoch is not strictly greater than the previous epoch recorded in `StreamState::BarrierEmitted { prev_epoch }`. Epochs must monotonically increase across barriers; reusing or decreasing an epoch would corrupt the ordering guarantees of the log store and downstream Hummock snapshots.
Source
Thrown at src/stream/src/common/log_store_impl/kv_log_store/serde.rs:958
let prev_epoch = match &self.stream_state {
StreamState::Uninitialized => unreachable!("should have init"),
StreamState::BarrierEmitted { prev_epoch } => *prev_epoch,
StreamState::AllConsumingRow { curr_epoch }
| StreamState::BarrierAligning { curr_epoch, .. } => {
return if *curr_epoch != epoch {
Err(anyhow!(
"epoch {} does not match with current epoch {}",
epoch,
curr_epoch
))
} else {
Ok(())
};
}
};
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
));View on GitHub (pinned to 6469eb736d)
Solutions
- Ensure every barrier carries a strictly increasing epoch from the epoch provider (meta service); never reuse an epoch after emitting it.
- Check for duplicate barrier sends in the barrier manager / actor graph.
- During failover, confirm the new leader continues epoch allocation above all previously emitted epochs (e.g. persisted high-water mark).
- Add an assertion/log where epochs are generated to catch non-monotonic allocation early.
Example fix
// before let epoch = last_emitted_epoch; // reused epoch // after let epoch = last_emitted_epoch + 1; // strictly greater
Defensive patterns
Strategy: validation
Validate before calling
// Rust: assert strictly increasing epochs before emitting a barrier
fn ensure_monotonic(prev: u64, next: u64) -> Result<(), String> {
if next <= prev { return Err(format!("epoch {} must be > prev {}", next, prev)); }
Ok(())
} Prevention
- Always allocate epochs from the centralized epoch provider; never cache and reuse.
- On failover, persist and reload the high-water epoch before allocating new ones.
- Deduplicate barriers before appending so the same epoch is not emitted twice.
When it happens
Trigger: Issuing a barrier (via the log store append path) whose `epoch <= prev_epoch`, i.e. after a barrier has already been emitted with an equal or larger epoch.
Common situations: Duplicate barrier injection (same barrier sent twice), a recovery path re-emitting an old epoch, meta node reassigning an epoch after failover without bumping it, or clock/epoch-generation logic producing repeated values.
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
- epoch {} does not match with current epoch {}
- current epoch {} has exceeded the epoch {} of the stream tha
- Exchange executor should not have children!
- missing lagging barriers for direct log-store start from sna
- get different response epoch to commit epoch: {} {}
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/93e18e009632c15d.
Report an issue: GitHub.