{"record":{"id":"4f11f27f1714b2ac","repo":"risingwavelabs/risingwave","slug":"failed-to-match-graph-message","errorCode":null,"errorMessage":"failed to match graph: {message}","messagePattern":"failed to match graph: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/meta/src/stream/stream_graph/state_match.rs","lineNumber":56,"sourceCode":"    fn from(node: &StreamNode) -> Self {\n        let id = node.operator_id;\n        let identity = &node.identity;\n        let body = node.node_body.as_ref().unwrap();\n\n        Self(format!(\"{}({}, {})\", body, id, identity).into_boxed_str())\n    }\n}\n\nimpl std::fmt::Display for StreamNodeDesc {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.0)\n    }\n}\n\n/// Error type for failed state table matching.\n#[derive(thiserror::Error, thiserror_ext::Macro, thiserror_ext::ReportDebug)]\npub(crate) enum Error {\n    #[error(\"failed to match graph: {message}\")]\n    Graph { message: String },\n\n    #[error(\"failed to match fragment {id}: {message}\")]\n    Fragment {\n        source: Option<Box<Error>>,\n        id: Id,\n        message: String,\n    },\n\n    #[error(\"failed to match operator {from} to {to}: {message}\")]\n    Operator {\n        from: StreamNodeDesc,\n        to: StreamNodeDesc,\n        message: String,\n    },\n}\n\ntype Result<T, E = Error> = std::result::Result<T, E>;","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/meta/src/stream/stream_graph/state_match.rs#L38-L74","documentation":"This is the top-level variant of the `state_match::Error` enum used when matching an old and new stream graph for state table migration (schema change). `Graph` carries a free-form message describing why the whole graph could not be matched, typically produced via context wrapping lower-level Fragment/Operator errors or direct graph-level checks.","triggerScenarios":"Running a schema-change state-table match where a graph-level check fails — e.g. the new graph has a different structure that cannot be paired with the old graph, or a fragment-level failure is lifted to the graph level with context.","commonSituations":"ALTER statements whose new plan topology diverges too much from the old plan (unsupported schema changes), version upgrades changing operator layout so old and new graphs no longer correspond.","solutions":["Read the inner `message` for the specific mismatch; if it wraps a Fragment/Operator error, fix that root cause first.","Ensure the ALTER only makes supported changes (adding columns etc.); otherwise recreate the MV/table with the new definition.","Align frontend/meta versions so both sides generate structurally comparable graphs."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"null","typeGuard":"if let state_match::Error::Graph { message } = err {\n    // handle graph-level mismatch\n}","tryCatchPattern":"match state_match::match_graph(&old, &new) {\n    Err(state_match::Error::Graph { message }) => {\n        tracing::warn!(\"schema change unsupported: {}\", message);\n        // fall back to full rebuild of the job\n    }\n    Err(e) => return Err(e.into()),\n    Ok(tables) => apply(tables),\n}","preventionTips":["Restrict ALTERs to changes that keep graph topology comparable","Test schema changes against production-like plans before applying","Keep frontend and meta versions in sync"],"tags":["meta","schema-change","state-matching"],"backgroundTag":"schema-validation-failed","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}