risingwavelabs/risingwave · error

new item epoch {} does not exceed barrier offset epoch {}

Error message

new item epoch {} does not exceed barrier offset epoch {}

What it means

`check_next_item_epoch` on a `TruncateOffset::Barrier` requires the new item's epoch to be strictly greater than the barrier's epoch, because everything after a barrier belongs to a later epoch. An equal-or-lesser epoch means pre-barrier data is arriving post-barrier.

Source

Thrown at src/connector/src/sink/log_store.rs:107

    pub fn check_next_item_epoch(&self, epoch: u64) -> LogStoreResult<()> {
        match self {
            TruncateOffset::Chunk {
                epoch: offset_epoch,
                ..
            } => {
                if epoch != *offset_epoch {
                    bail!(
                        "new item epoch {} does not match current chunk offset epoch {}",
                        epoch,
                        offset_epoch
                    );
                }
            }
            TruncateOffset::Barrier {
                epoch: offset_epoch,
            } => {
                if epoch <= *offset_epoch {
                    bail!(
                        "new item epoch {} does not exceed barrier offset epoch {}",
                        epoch,
                        offset_epoch
                    );
                }
            }
        }
        Ok(())
    }
}

#[derive(Debug)]
pub enum LogStoreReadItem {
    StreamChunk {
        chunk: StreamChunk,
        chunk_id: ChunkId,
    },
    Barrier {

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Drop or flush all pre-barrier items before recording the barrier offset
  2. Ensure items are sorted/filtered so post-barrier writes have epoch > barrier epoch
  3. Audit recovery paths for stale cursors replaying old epochs

Example fix

// before
store.check_next_item_epoch(item.epoch); // item.epoch <= barrier_epoch
// after
if item.epoch > barrier_epoch {
    store.check_next_item_epoch(item.epoch);
} else {
    item.drop_stale();
}
Defensive patterns

Strategy: validation

Validate before calling

assert!(item_epoch > barrier_epoch, "post-barrier items must have epoch > barrier epoch");

Type guard

fn exceeds_barrier(epoch: u64, offset: &TruncateOffset) -> bool {
    match offset {
        TruncateOffset::Barrier { epoch: e } => epoch > *e,
        _ => true,
    }
}

Try / catch

match offset.check_next_item_epoch(epoch) {
    Err(_) => { /* drop stale pre-barrier item or replay from correct cursor */ }
    Ok(()) => { /* write item */ }
}

Prevention

When it happens

Trigger: Calling `check_next_item_epoch(epoch)` while the current offset is `TruncateOffset::Barrier { epoch: offset_epoch }` with `epoch <= offset_epoch`; e.g. buffered items from before a barrier being written after the barrier was recorded.

Common situations: Out-of-order buffering in sink writers; replaying a queue from a stale cursor after recovery; barrier arrives while older-epoch items remain pending.

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/80761b332a5d629f. Report an issue: GitHub.