risingwavelabs/risingwave · error

since_timestamp requires at least one upstream table

Error message

since_timestamp requires at least one upstream table

What it means

resolve_log_store_epoch resolves the unified change-log epoch for a streaming job's upstream tables. It rejects the call up-front when the caller supplies zero upstream table IDs, because computing a 'since' timestamp is meaningless without any table to read change logs from. This is a defensive validation against calling the API with an empty table set.

Source

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

    }

    fn mark_ready(&self, options: MarkReadyOptions) {
        let is_global = matches!(&options, MarkReadyOptions::Global { .. });
        self.scheduled_barriers.mark_ready(options);
        if is_global {
            self.set_status(BarrierManagerStatus::Running);
        }
    }

    async fn resolve_log_store_epoch<'a>(
        &'a self,
        upstream_table_ids: impl Iterator<Item = TableId> + Send + 'a,
        since_epoch: u64,
    ) -> MetaResult<SinceTimestampResolvedEpoch> {
        let upstream_table_ids = upstream_table_ids.collect::<Vec<_>>();
        if upstream_table_ids.is_empty() {
            return Err(
                anyhow::anyhow!("since_timestamp requires at least one upstream table").into(),
            );
        }

        self.hummock_manager
            .on_current_version_and_table_change_log(|version, table_change_log| {
                let mut unified_log_epochs = None;
                for &upstream_table_id in &upstream_table_ids {
                    let upstream_committed_epoch = version
                        .state_table_info
                        .info()
                        .get(&upstream_table_id)
                        .map(|info| info.committed_epoch)
                        .ok_or_else(|| {
                            anyhow::anyhow!(
                                "cannot get committed epoch for upstream table {}",
                                upstream_table_id
                            )
                        })?;

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Inspect the job whose upstream tables resolved to empty; verify it actually has upstream materialized views/sources in the catalog
  2. Fix the caller so it only invokes resolve_log_store_epoch when the upstream table list is non-empty (guard before collecting)
  3. If the job legitimately has no upstream table, change its design (e.g. use a source or datagen) — log-store resolution cannot work tableless
  4. If it's a recovery-time failure, restart recovery after fixing the catalog so upstream mappings are correct

Example fix

// before
let epochs = manager.resolve_log_store_epoch(upstream_table_ids, since_epoch).await?;
// after
let tables: Vec<_> = upstream_table_ids.collect();
anyhow::ensure!(!tables.is_empty(), "job has no upstream tables; skip since_timestamp resolution");
let epochs = manager.resolve_log_store_epoch(tables.into_iter(), since_epoch).await?;
Defensive patterns

Strategy: validation

Validate before calling

let tables: Vec<TableId> = upstream_table_ids.collect();
if tables.is_empty() {
    return Err(anyhow::anyhow!("no upstream tables to resolve since_timestamp"));
}
let resolved = manager.resolve_log_store_epoch(tables.into_iter(), since_epoch).await?;

Try / catch

match manager.resolve_log_store_epoch(ids, epoch).await {
    Ok(resolved) => use(resolved),
    Err(e) if e.to_string().contains("requires at least one upstream table") => log::warn!("job has no upstream tables, skipping"),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling GlobalBarrierManager::resolve_log_store_epoch (via the barrier manager context) with an upstream_table_ids iterator that yields no elements — e.g. a streaming job with no upstream materialized-source/table dependencies, or an upstream ID collection bug upstream of the call.

Common situations: Creating or recovering a sink/job whose upstream resolution returned an empty set; a planner or catalog bug mapping a job to its upstream tables; races where upstream tables were dropped before this call.

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