risingwavelabs/risingwave · error

batch refresh materialized views must not depend on sources

Error message

batch refresh materialized views must not depend on sources directly; fragment {} has source/source-backfill nodes

What it means

Batch refresh materialized views must not read from sources directly. The meta node scans each fragment's FragmentTypeFlag and, if any fragment contains Source or SourceScan nodes, it bails, because split assignment resolution is skipped for batch refresh jobs and direct source reads would be left unresolved.

Source

Thrown at src/meta/src/stream/stream_manager.rs:685

        };

        let job_type = if let Some(refresh_interval_sec) = refresh_interval_sec {
            if since_timestamp_epoch.is_some() {
                bail!("since_timestamp should not be specified when no snapshot backfill");
            }
            let snapshot_backfill_info = snapshot_backfill_info.ok_or_else(|| {
                anyhow::anyhow!(
                    "batch refresh materialized view must have snapshot backfill upstream"
                )
            })?;
            // Batch refresh jobs must not contain source or source-backfill nodes,
            // because we skip split assignment resolution for them.
            for fragment in info.stream_job_fragments.inner.fragments.values() {
                let mask = fragment.fragment_type_mask;
                if mask.contains(FragmentTypeFlag::Source)
                    || mask.contains(FragmentTypeFlag::SourceScan)
                {
                    bail!(
                        "batch refresh materialized views must not depend on sources directly; \
                         fragment {} has source/source-backfill nodes",
                        fragment.fragment_id
                    );
                }
            }
            tracing::debug!(
                ?snapshot_backfill_info,
                refresh_interval_sec,
                "sending Command::CreateBatchRefreshStreamingJob"
            );
            CreateStreamingJobType::BatchRefresh(BatchRefreshInfo {
                snapshot_backfill_info,
                refresh_interval_sec,
            })
        } else if let Some(snapshot_backfill_info) = snapshot_backfill_info {
            tracing::debug!(
                ?snapshot_backfill_info,

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Insert an intermediate regular (non-batch-refresh) MV between the source and the batch refresh MV, then build the refresh MV on top of that MV.
  2. Replace the direct source read with a table/MV as the upstream of the batch refresh job.
  3. If reading the source directly is required, use a normal streaming MV instead of batch refresh.

Example fix

// before
CREATE MATERIALIZED VIEW mv WITH (refresh = '10 minutes') AS SELECT * FROM src;
// after
CREATE MATERIALIZED VIEW base_mv AS SELECT * FROM src;
CREATE MATERIALIZED VIEW mv WITH (refresh = '10 minutes') AS SELECT * FROM base_mv;
Defensive patterns

Strategy: validation

Validate before calling

-- ensure the batch refresh MV does not read a source directly
-- build on MVs/tables only:
CREATE MATERIALIZED VIEW base_mv AS SELECT * FROM src;
CREATE MATERIALIZED VIEW mv WITH (refresh = '10 minutes') AS SELECT * FROM base_mv;

Try / catch

match result {
    Err(e) if e.to_string().contains("must not depend on sources directly") =>
        return Err("insert an intermediate MV between the source and the batch refresh MV".into()),
    other => other,
}

Prevention

When it happens

Trigger: Creating a batch refresh MV (refresh_interval_sec set, snapshot_backfill_info present) whose plan contains a fragment with fragment_type_mask containing FragmentTypeFlag::Source or SourceScan — i.e. the MV reads a source (or a source-backfill node) directly in its dependency chain.

Common situations: `CREATE MATERIALIZED VIEW ... WITH (refresh=...) AS SELECT ... FROM <source>`; building a refresh MV on top of a chain of MVs where one still reads a source directly; users migrating pipelines that previously used regular streaming MVs on sources.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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