risingwavelabs/risingwave · error · HummockError

bounded compaction head sequence {} is older than boundary {

Error message

bounded compaction head sequence {} is older than boundary {}

What it means

During bounded compaction validation, the head snapshot's sequence number is compared with the requested max_file_sequence_number boundary. If head.sequence_number() < boundary, the boundary exceeds anything the table has ever written, so it is provably meaningless and the runner rejects the task rather than producing an empty/incorrect bounded plan.

Source

Thrown at src/storage/src/hummock/compactor/iceberg_compaction/iceberg_compactor_runner.rs:972

        .load_table(&table_ident)
        .await
        .map_err(|e| HummockError::compaction_executor(e.as_report()))?;

    if let Some(boundary) = max_file_sequence_number {
        // An empty bounded plan is reported as `Drained`, so fail closed unless
        // the loaded branch can prove that this fixed boundary is meaningful.
        if table.metadata().format_version() < FormatVersion::V2 {
            return Err(HummockError::compaction_executor(anyhow::anyhow!(
                "bounded compaction requires Iceberg format V2 or V3"
            )));
        }
        let head = table.metadata().snapshot_for_ref(&branch).ok_or_else(|| {
            HummockError::compaction_executor(anyhow::anyhow!(
                "bounded compaction branch {branch} has no snapshot"
            ))
        })?;
        if head.sequence_number() < boundary {
            return Err(HummockError::compaction_executor(anyhow::anyhow!(
                "bounded compaction head sequence {} is older than boundary {}",
                head.sequence_number(),
                boundary
            )));
        }
    }

    let planning_config = build_task_planning_config(
        compaction_kind,
        &iceberg_config,
        &config,
        max_file_sequence_number,
    )?;

    let compaction_plans = CompactionPlanner::new(planning_config)
        .plan_compaction_with_branch(&table, &branch)
        .await
        .map_err(|e| HummockError::compaction_executor(e.as_report()))?;

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Re-read the current head snapshot's sequence number for the branch and send a boundary <= that value (or omit max_file_sequence_number entirely).
  2. If the table was recreated, refresh any persisted boundary state in the coordinating system to match the new table's sequence space.
  3. Use an unbounded compaction task when you just want a full/normal compaction instead of tracking sequence numbers.
  4. Confirm the request targets the intended table/branch — boundaries from a different branch can exceed this branch's sequence numbers.

Example fix

// before
// max_file_sequence_number = 1000 while head.sequence_number() = 42

// after: fetch head sequence and clamp, or omit
// max_file_sequence_number = null  // or <= head sequence
Defensive patterns

Strategy: validation

Validate before calling

fn boundary_is_valid(table: &Table, branch: &str, boundary: i64) -> bool {
    table.metadata().snapshot_for_ref(branch)
        .map_or(false, |head| head.sequence_number() >= boundary)
}

Prevention

When it happens

Trigger: create_task_execution with max_file_sequence_number > head snapshot's sequence_number for the target branch — the caller supplied a stale or fabricated boundary larger than any committed sequence number.

Common situations: A client cached sequence numbers from a different table/branch and sent them to this one; a table was recreated (sequence numbers reset) while the coordinator still held old boundaries; a manual/ops script hardcodes a boundary value.

Related errors


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