risingwavelabs/risingwave · error · Error

time-travel version expired: table {table_id}, epoch {epoch}

Error message

time-travel version expired: table {table_id}, epoch {epoch}

What it means

HummockMetaError::TimeTravelVersionExpired signals that a time-travel read requested a table version at an epoch that has already been marked expired and pruned. Hummock retains historical versions only up to the configured retention window; beyond it the version is no longer readable.

Source

Thrown at src/meta/src/hummock/error.rs:52

        anyhow::Error,
    ),
    #[error(transparent)]
    ObjectStore(
        #[from]
        #[backtrace]
        ObjectError,
    ),
    #[error("compactor {0} is disconnected")]
    CompactorUnreachable(HummockContextId),
    #[error("compaction group error: {0}")]
    CompactionGroup(String),
    #[error("SST {0} is invalid")]
    InvalidSst(HummockSstableObjectId),
    #[error("invalid manual compaction option: {0}")]
    InvalidManualCompactionOption(String),
    #[error("invalid epoch range: {start_epoch}..={end_epoch}")]
    InvalidEpochRange { start_epoch: u64, end_epoch: u64 },
    #[error("time-travel version expired: table {table_id}, epoch {epoch}")]
    TimeTravelVersionExpired { table_id: TableId, epoch: u64 },
    #[error("time travel")]
    TimeTravel(
        #[source]
        #[backtrace]
        anyhow::Error,
    ),
    #[error(transparent)]
    Internal(
        #[from]
        #[backtrace]
        anyhow::Error,
    ),
}

impl Error {
    pub fn retryable(&self) -> bool {
        matches!(self, Error::MetaStore(_))

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Increase the time-travel version retention/TTV retention config on the meta node and retry
  2. Pick an epoch within the available retention window (check hummock version timelines via risectl)
  3. Refresh the epoch to a recent snapshot instead of a stale one
  4. Restore from backup/object store snapshot if the historical data is genuinely required
Defensive patterns

Strategy: fallback

Validate before calling

if let Some(available) = meta_client.time_travel_version(table_id, epoch)? {
    if available.is_expired() { /* pick a newer epoch instead */ }
}

Type guard

fn version_expired(err: &HummockMetaError) -> Option<(TableId, u64)> {
    if let HummockMetaError::TimeTravelVersionExpired { table_id, epoch } = err { Some((*table_id, *epoch)) } else { None }
}

Try / catch

match read_as_of(table_id, epoch).await {
    Err(HummockMetaError::TimeTravelVersionExpired { .. }) => {
        eprintln!("version expired; reading latest snapshot instead");
        read_latest(table_id).await
    }
    other => other,
}

Prevention

When it happens

Trigger: Querying `AS OF` / time travel for (table_id, epoch) where epoch is older than the retention period (hummock time-travel version retention), or after the version was expired and cleaned by GC.

Common situations: Backfill or audit queries against old snapshots; retention period shortened in config so previously readable epochs expire; long-running query reusing a stale epoch handle; Iceberg-export/time-travel consumers reading beyond TTL.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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