risingwavelabs/risingwave · error

Different number of internal tables. New: {}, Old: {}

Error message

Different number of internal tables. New: {}, Old: {}

What it means

`fit_internal_tables_trivial` maps the new graph's internal tables onto the old job's internal tables by position after sorting by id. The trivial algorithm requires the ALTER to preserve the number of internal tables exactly; if the new graph needs a different count (e.g. a new column changed the plan and added/removed a state table), it refuses to fit them and fails the ALTER.

Source

Thrown at src/meta/src/stream/stream_graph/fragment.rs:1083

            );
        }
    }

    /// Use a trivial algorithm to match the internal tables of the new graph for
    /// `ALTER TABLE` or `ALTER SOURCE`.
    pub fn fit_internal_tables_trivial(
        &mut self,
        mut old_internal_tables: Vec<Table>,
    ) -> MetaResult<()> {
        let mut new_internal_table_ids = Vec::new();
        for fragment in self.fragments.values() {
            for table in &fragment.extract_internal_tables() {
                new_internal_table_ids.push(table.id);
            }
        }

        if new_internal_table_ids.len() != old_internal_tables.len() {
            bail!(
                "Different number of internal tables. New: {}, Old: {}",
                new_internal_table_ids.len(),
                old_internal_tables.len()
            );
        }
        old_internal_tables.sort_by_key(|t| t.id);
        new_internal_table_ids.sort();

        let internal_table_id_map = new_internal_table_ids
            .into_iter()
            .zip_eq_fast(old_internal_tables.into_iter())
            .collect::<HashMap<_, _>>();

        // TODO(alter-mv): unify this with `fit_internal_table_ids_with_mapping` after we
        // confirm the behavior is the same.
        for fragment in self.fragments.values_mut() {
            stream_graph_visitor::visit_internal_tables(
                &mut fragment.inner,

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Drop and recreate the MV after altering the table instead of relying on in-place schema change.
  2. Check whether the ALTER changes the plan shape (e.g. new column used in a join/agg key) and adjust the statement.
  3. Upgrade RisingWave: newer versions may use mapping-based fitting that tolerates plan changes.

Example fix

-- before
ALTER TABLE t ADD COLUMN c INT; -- MV plan gains an extra internal table -> error
-- after
DROP MATERIALIZED VIEW mv;
ALTER TABLE t ADD COLUMN c INT;
CREATE MV mv AS ...;
Defensive patterns

Strategy: fallback

Try / catch

// On this error, fall back to drop-and-recreate of the MV:
// DROP MATERIALIZED VIEW mv; ALTER TABLE ...; CREATE MATERIALIZED VIEW mv AS ...;

Prevention

When it happens

Trigger: ALTER TABLE/ALTER SOURCE (via build_replace_job) where the rewritten graph's `extract_internal_tables()` count differs from the old job's internal table list — e.g. adding a column changes the plan such that an extra hash-agg/join state table is required.

Common situations: Schema change on MVs containing aggregations or joins where the new plan's state tables differ from the old; RisingWave intentionally rejects rather than migrating state tables.

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/dace6974422b2dbf. Report an issue: GitHub.