risingwavelabs/risingwave · error

Backfill order strategy has a cycle

Error message

Backfill order strategy has a cycle

What it means

The user-supplied backfill_order pairs are turned into a directed graph over scanned relation ids; plan_fixed_strategy runs has_cycle on the resulting order. A cycle (e.g. A->B, B->A) makes a total backfill ordering impossible, so creation is rejected.

Source

Thrown at src/frontend/src/optimizer/backfill_order_strategy.rs:341

                    start_name
                );
            };
            let Some(end_scanned_relation_ids) = scanned_relation_ids.get(&end_relation_id) else {
                bail!(
                    "Table or source '{}' specified in backfill_order is not used in the query",
                    end_name
                );
            };

            for start_scanned_relation_id in start_scanned_relation_ids {
                order
                    .entry(*start_scanned_relation_id)
                    .or_default()
                    .extend(end_scanned_relation_ids.iter().copied());
            }
        }
        if has_cycle(&order) {
            bail!("Backfill order strategy has a cycle");
        }
        Ok(order)
    }
}

mod common {
    use std::collections::{HashMap, HashSet};

    use risingwave_pb::id::RelationId;
    use risingwave_sqlparser::ast::ObjectName;

    use crate::Binder;
    use crate::catalog::CatalogError;
    use crate::catalog::root_catalog::SchemaPath;
    use crate::catalog::schema_catalog::SchemaCatalog;
    use crate::error::Result;
    use crate::session::SessionImpl;

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Draw the dependency graph implied by your pairs and remove the contradictory edge(s).
  2. Delete duplicate/redundant pairs that imply both directions between two tables.
  3. Ensure the order is a strict topological order of the query's relations.
  4. Omit backfill_order and let RisingWave choose an order if no strict ordering is needed.

Example fix

// before
WITH (backfill_order = 'a -> b, b -> a')
// after
WITH (backfill_order = 'a -> b')
Defensive patterns

Strategy: validation

Validate before calling

-- pairs must form a DAG; check for both-direction pairs before DDL
-- bad: backfill_order = 'a -> b, b -> a'

Try / catch

match create_mv_result {
    Err(e) if e.to_string().contains("has a cycle") => {
        eprintln!("backfill_order edges form a cycle; remove contradictory pairs");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Specifying backfill_order pairs whose dependency edges form a loop, such as 'a -> b' together with 'b -> a', directly or transitively across multiple pairs.

Common situations: Conflicting ordering statements across several pairs, misunderstanding the arrow direction (pairs are not symmetric), or copy-pasting orders that contradict each other.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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