risingwavelabs/risingwave · error

since_timestamp is earlier than the retained changelog of up

Error message

since_timestamp is earlier than the retained changelog of upstream table {}: requested epoch {}, first retained checkpoint epoch {}

What it means

The requested since_timestamp epoch is older than the earliest checkpoint epoch still retained in the upstream table's changelog. The required history has been trimmed, so a snapshot at that epoch cannot be produced.

Source

Thrown at src/meta/src/barrier/context/context_impl.rs:78

    upstream_committed_epoch: u64,
    table_change_log: &TableChangeLogs,
) -> MetaResult<SinceTimestampResolvedEpoch> {
    let change_log = table_change_log.get(&table_id).ok_or_else(|| {
        anyhow::anyhow!(
            "no table changelog found for upstream table {} when resolving since_timestamp",
            table_id
        )
    })?;
    let Some(first_log) = change_log.first() else {
        return Err(anyhow::anyhow!(
            "empty table changelog found for upstream table {} when resolving since_timestamp",
            table_id
        )
        .into());
    };
    let first_checkpoint_epoch = first_log.checkpoint_epoch;
    if since_epoch < first_checkpoint_epoch {
        return Err(anyhow::anyhow!(
            "since_timestamp is earlier than the retained changelog of upstream table {}: requested epoch {}, first retained checkpoint epoch {}",
            table_id,
            since_epoch,
            first_checkpoint_epoch,
        )
        .into());
    }
    if since_epoch >= upstream_committed_epoch {
        return Err(anyhow::anyhow!(
            "since_timestamp is not before the committed epoch of upstream table {}: requested epoch {}, committed epoch {}",
            table_id,
            since_epoch,
            upstream_committed_epoch,
        )
        .into());
    }
    let latest_log = change_log.last().expect("checked non-empty");
    if upstream_committed_epoch != latest_log.checkpoint_epoch {

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Re-create the snapshot backfill with a more recent since_timestamp
  2. Increase upstream changelog/log-store retention
  3. If full history is needed, rebuild the upstream MV or use a full refresh instead of timestamp backfill

Example fix

-- before
CREATE ... FROM mv WITH (SNC_TIMESTAMP = 'very old timestamp');
-- after: use a recent timestamp within retained changelog
CREATE ... FROM mv WITH (SNC_TIMESTAMP = now - interval '1 hour');
Defensive patterns

Strategy: validation

Validate before calling

if since_epoch < first_retained_checkpoint_epoch {
    return Err("since_timestamp older than retained changelog; increase retention or use newer timestamp");
}

Type guard

fn within_retained_range(since: u64, first: u64, committed: u64) -> bool {
    since >= first && since < committed
}

Try / catch

match create_snapshot_backfill(...).await {
    Err(e) if e.contains("earlier than the retained changelog") => pick_newer_since_timestamp(),
    other => other,
}

Prevention

When it happens

Trigger: resolve_since_timestamp_log_store_epoch with since_epoch < first_log.checkpoint_epoch — e.g. backfilling from an MV whose old changelogs have been trimmed by retention policies.

Common situations: Slow snapshot backfill created long after upstream data history aged out; short changelog retention in log store; replaying an old DDL script against aged data.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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