risingwavelabs/risingwave · error

empty table changelog found for upstream table {} when resol

Error message

empty table changelog found for upstream table {} when resolving since_timestamp

What it means

The changelog map has an entry for the upstream table but it is empty, so there is no first log entry from which to derive the earliest retained checkpoint epoch. The resolver treats this as invalid because nothing has been retained.

Source

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

use crate::model::FragmentDownstreamRelation;
use crate::serving::{fetch_serving_infos, sync_serving_table_vnode_mappings_to_hummock};
use crate::stream::{SourceChange, cleanup_dropped_streaming_jobs};
use crate::{MetaError, MetaResult};

fn resolve_since_timestamp_log_store_epoch(
    table_id: TableId,
    since_epoch: u64,
    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 {}",

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Wait until the upstream table has committed changelogs, then retry the backfill creation
  2. Retry after the next barrier checkpoints
  3. Check log-store retention configuration so changelogs are not trimmed to empty
Defensive patterns

Strategy: validation

Validate before calling

if table_change_log.get(&table_id).map_or(true, |l| l.is_empty()) {
    return Err("upstream has no retained changelogs yet");
}

Type guard

fn has_nonempty_changelog(logs: &TableChangeLogs, id: &TableId) -> bool {
    logs.get(id).map_or(false, |l| !l.is_empty())
}

Try / catch

match create_snapshot_backfill(...).await {
    Err(e) if e.contains("empty table changelog") => retry_after_upstream_commits(),
    other => other,
}

Prevention

When it happens

Trigger: Calling resolve_since_timestamp_log_store_epoch with a change_log vec of length zero for the table (e.g. logs trimmed or not yet populated).

Common situations: Changelog entry created but logs never appended; aggressive log trimming removed all entries; newly created upstream table with no committed changelogs yet.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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