risingwavelabs/risingwave · error
since_timestamp is later than the latest changelog of upstre
Error message
since_timestamp is later than the latest changelog of upstream table {}: requested epoch {}, latest changelog epoch {} What it means
Binary search over the changelog failed to find a change-log entry at the target epoch, so the resolved snapshot epoch index is out of bounds. This indicates the requested epoch is beyond the latest retained changelog entry.
Source
Thrown at src/meta/src/barrier/context/context_impl.rs:120
)
.into());
}
// `binary_search_by_checkpoint_epoch` searches only by the checkpoint epoch
// of each changelog entry. An entry may still cover earlier non-checkpoint
// epochs, e.g. `{ non_checkpoint_epochs: [30, 35], checkpoint_epoch: 40 }`
// covers `(20, 40]` if the previous checkpoint is 20. For `since_epoch =
// 35`, the search returns `Err(index_of_40)`, and 40 is the snapshot epoch.
// For `since_epoch = 40`, it returns `Ok(index_of_40)`. In both cases, the
// resolved snapshot epoch is the least checkpoint epoch >= `since_epoch`.
let snapshot_epoch_index = match change_log.binary_search_by_checkpoint_epoch(since_epoch) {
Ok(index) => index,
Err(index) => index,
};
let snapshot_epoch = change_log
.get(snapshot_epoch_index)
.ok_or_else(|| {
anyhow::anyhow!(
"since_timestamp is later than the latest changelog of upstream table {}: requested epoch {}, latest changelog epoch {}",
table_id,
since_epoch,
upstream_committed_epoch,
)
})?
.checkpoint_epoch;
// A request inside the latest changelog entry may be smaller than
// `upstream_committed_epoch`, but still resolve to the latest checkpoint.
// That end-of-log case is not a usable historical snapshot epoch.
if snapshot_epoch >= upstream_committed_epoch {
return Err(anyhow::anyhow!(
"since_timestamp is too new for upstream table {}: requested epoch {}, resolved snapshot epoch {}, latest changelog epoch {}",
table_id,
since_epoch,
snapshot_epoch,
upstream_committed_epoch,
)View on GitHub (pinned to 6469eb736d)
Solutions
- Use a since_timestamp within the retained changelog range
- Retry after the next barrier refreshes the changelog
- Check retention configuration and upstream progress
- Report a bug if the epoch was validated as within range beforehand
Defensive patterns
Strategy: validation
Validate before calling
let idx = search_index(change_log, since_epoch);
if idx >= change_log.len() {
return Err("since_timestamp beyond latest retained changelog entry");
} Type guard
fn index_in_bounds(log: &TableChangeLog, idx: usize) -> bool { idx < log.len() } Try / catch
match create_snapshot_backfill(...).await {
Err(e) if e.contains("later than the latest changelog") => retry_after_next_barrier(),
other => other,
} Prevention
- Keep since_epoch within [first, last] changelog checkpoints
- Refresh barrier state before resolution
- Watch for clock skew making timestamps too new
When it happens
Trigger: resolve_since_timestamp_log_store_epoch when the computed snapshot_epoch_index is >= change_log.len() (since_epoch later than all retained changelog entries), making change_log.get(index) return None.
Common situations: Since timestamp newer than the latest changelog entry due to clock skew; changelog trimmed; race where the barrier state changelog is stale relative to the caller's epoch.
Related errors
- no table changelog found for upstream table {} when resolvin
- since_timestamp is earlier than the retained changelog of up
- empty table changelog found for upstream table {} when resol
- since_timestamp is not before the committed epoch of upstrea
- since_timestamp is too new for upstream table {}: requested
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/fe7de6cc0eb770ff.
Report an issue: GitHub.