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
- Wait until the upstream table has committed changelogs, then retry the backfill creation
- Retry after the next barrier checkpoints
- 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
- Ensure upstream has committed changelogs before since-timestamp backfill
- Avoid trimming changelogs to empty
- Retry after next checkpoint
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
- no table changelog found for upstream table {} when resolvin
- since_timestamp is earlier than the retained changelog of up
- since_timestamp is later than the latest changelog of upstre
- since_timestamp epoch has not been resolved for snapshot bac
- cannot create batch refresh job while database barrier is pa
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/e6ca0e02e875e40b.
Report an issue: GitHub.