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

  1. Use a since_timestamp within the retained changelog range
  2. Retry after the next barrier refreshes the changelog
  3. Check retention configuration and upstream progress
  4. 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

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


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