risingwavelabs/risingwave · error · SinkError::LanceDb

LanceDB pre-commit epoch {} does not match commit epoch {}

Error message

LanceDB pre-commit epoch {} does not match commit epoch {}

What it means

During commit_data, the LanceDB sink deserializes the opaque pre-commit state written in pre_commit and compares its recorded epoch to the epoch the coordinator asks to commit. A mismatch means the commit belongs to a different (typically older) epoch than the one prepared — usually the consequence of a recovery/restart where a stale prepared transaction is replayed against a new epoch.

Source

Thrown at src/connector/src/sink/lancedb.rs:750

            LanceDbPreCommitMetadata {
                sink_id: self.sink_id.clone(),
                epoch,
                fragments,
            }
            .try_into_bytes()?,
        ))
    }

    async fn commit_data(&mut self, epoch: u64, commit_metadata: Vec<u8>) -> Result<()> {
        tracing::debug!("Starting LanceDB two-phase commit in epoch {epoch}.");

        if commit_metadata.is_empty() {
            return Ok(());
        }

        let pre_commit_metadata = LanceDbPreCommitMetadata::try_from_bytes(&commit_metadata)?;
        if pre_commit_metadata.epoch != epoch {
            return Err(SinkError::LanceDb(anyhow!(
                "LanceDB pre-commit epoch {} does not match commit epoch {}",
                pre_commit_metadata.epoch,
                epoch
            )));
        }
        if pre_commit_metadata.sink_id != self.sink_id {
            return Err(SinkError::LanceDb(anyhow!(
                "LanceDB pre-commit sink id {} does not match coordinator sink id {}",
                pre_commit_metadata.sink_id,
                self.sink_id
            )));
        }

        let transaction_properties = pre_commit_metadata.transaction_properties();
        self.commit_fragments(
            epoch,
            pre_commit_metadata.fragments,
            Some(transaction_properties),

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Verify whether the epoch's data already landed in LanceDB before the error — if so, the stale prepared state can be safely discarded
  2. Restart/recover the sink so pre_commit state is re-created at the current epoch
  3. Ensure only one coordinator commits to the sink at a time (check for duplicate sink worker assignments)
  4. If persistent after a single clean restart, report — it may indicate lost/incorrect pre-commit metadata
Defensive patterns

Strategy: retry

Validate before calling

// before committing, verify metadata epoch matches
let meta = LanceDbPreCommitMetadata::try_from_bytes(&commit_metadata)?;
if meta.epoch != epoch { /* stale prepared txn: verify data landed, then discard */ }

Type guard

fn is_epoch_mismatch(err: &SinkError) -> bool {
    matches!(err, SinkError::LanceDb(e) if e.to_string().contains("does not match commit epoch"))
}

Try / catch

match commit_result {
    Err(e) if e.to_string().contains("does not match commit epoch") => {
        // stale prepared state from before recovery: verify data, then proceed with new epoch
    }
    other => other,
}

Prevention

When it happens

Trigger: pre_commit stored epoch E1 in LanceDbPreCommitMetadata; the coordinator later calls commit_data with epoch E2 != E1, e.g. after a failover/restart, or a replayed commit for an epoch that was already committed/aborted.

Common situations: Recovery after a meta-node failure replaying old commit epochs; duplicated barrier delivery; retries of an already-committed epoch.

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