risingwavelabs/risingwave · error

Creating such a sink will result in circular dependency.

Error message

Creating such a sink will result in circular dependency.

What it means

Creating a sink whose target table would create a cycle of dependencies among relations (the sink depends on a table that transitively depends on the sink) is forbidden. The catalog dependency check found a cycle before inserting the sink catalog.

Solutions

  1. Redesign the pipeline to break the cycle (split into separate tables or an external sink).
  2. Drop one of the intermediate relations creating the cycle.
  3. Create the sink into a fresh table not read by its own upstream chain.
  4. Validate the dependency graph manually (rw_dependencies / upstream queries) before creating the sink.
Defensive patterns

Strategy: validation

Validate before calling

// before creating the sink, ensure the target table is not read
// (transitively) by the sink's source query
let cycle = check_relation_cycle(target_table_id, dependencies, &txn).await?;
if cycle { /* abort creation */ }

Try / catch

match create_sink_catalog(..).await {
    Err(e) if e.to_string().contains("circular dependency") => /* redesign pipeline; drop cycle link */,
    other => other?,
}

Prevention

When it happens

Trigger: create_job_catalog for a sink whose target_table participates in an existing dependency cycle with the sink's upstream relations, detected by the dependency check over the txn.

Common situations: Users wiring sinks into tables that feed other MVs which the sink's source query reads (A -> B -> A pipelines), often from incremental schema evolution.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at src/meta/src/controller/streaming_job.rs:623

                    backfill_adaptive_parallelism_strategy,
                    refresh_interval_sec,
                )
                .await?;
                table.id = streaming_job_model.job_id.as_mv_table_id();
                let table_model: table::ActiveModel = table.clone().into();
                Table::insert(table_model).exec(&txn).await?;
                streaming_job_model
            }
            StreamingJob::Sink(sink, belong_to_table_id) => {
                if let Some(target_table_id) = sink.target_table
                    && check_sink_into_table_cycle(
                        target_table_id.into(),
                        dependencies.iter().cloned().collect(),
                        &txn,
                    )
                    .await?
                {
                    bail!("Creating such a sink will result in circular dependency.");
                }

                let streaming_job_model = Self::create_streaming_job_obj(
                    &txn,
                    ObjectType::Sink,
                    sink.owner as _,
                    Some(
                        belong_to_table_id
                            .map(|table_id| table_id.as_object_id())
                            .unwrap_or(sink.schema_id.as_object_id()),
                    ),
                    create_type,
                    ctx.clone(),
                    adaptive_parallelism_strategy,
                    streaming_parallelism,
                    max_parallelism,
                    resource_type.clone(),
                    backfill_parallelism.clone(),

View on GitHub (pinned to 6469eb736d)