{"record":{"id":"85343ff0ccccee5e","repo":"risingwavelabs/risingwave","slug":"no-vnodes-to-assign-assignment-is-meaningless","errorCode":null,"errorMessage":"no vnodes to assign; assignment is meaningless","messagePattern":"no vnodes to assign; assignment is meaningless","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/meta/src/stream/stream_graph/assignment.rs","lineNumber":757,"sourceCode":"        &self,\n        workers: &BTreeMap<W, NonZeroUsize>,\n        actors: &[A],\n        vnodes: &[V],\n    ) -> Result<BTreeMap<W, BTreeMap<A, Vec<V>>>>\n    where\n        W: Ord + Hash + Eq + Clone + Debug,\n        A: Ord + Hash + Eq + Copy + Debug,\n        V: Hash + Eq + Copy + Debug,\n    {\n        ensure!(\n            !workers.is_empty(),\n            \"no workers to assign; assignment is meaningless\"\n        );\n        ensure!(\n            !actors.is_empty(),\n            \"no actors to assign; assignment is meaningless\"\n        );\n        ensure!(\n            !vnodes.is_empty(),\n            \"no vnodes to assign; assignment is meaningless\"\n        );\n        ensure!(\n            vnodes.len() >= actors.len(),\n            \"not enough vnodes ({}) for actors ({}); each actor needs at least one vnode\",\n            vnodes.len(),\n            actors.len()\n        );\n\n        let chunk_size = match self.vnode_chunking_strategy {\n            VnodeChunkingStrategy::NoChunking => {\n                return assign_hierarchical(\n                    workers,\n                    actors,\n                    vnodes,\n                    self.salt,\n                    self.actor_capacity,","sourceCodeStart":739,"sourceCodeEnd":775,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/meta/src/stream/stream_graph/assignment.rs#L739-L775","documentation":"The final `ensure!` in the guard chain (assignment.rs:757) rejects an empty vnode list; like the worker/actor checks, assignment over zero vnodes is undefined, so the function fails fast with this message.","triggerScenarios":"Calling the inner `assign_hierarchical` with `vnodes.is_empty()` — e.g. vnode set derived from a zero or unset vnode count.","commonSituations":"Zero/unknown vnode count in table properties; helper called in tests with empty slices; vnode computation skipped for some fragment type.","solutions":["Generate the full vnode set (default 256) before assignment.","Handle unset/zero `maybe_vnode_count` upstream with an explicit error instead of an empty set.","Validate inputs at the API boundary before reaching the scheduler."],"exampleFix":"// before\nlet plan = assign_hierarchical(&workers, &actors, &[], strategy)?;\n// after\nlet vnodes: Vec<u32> = (0..DEFAULT_VNODE_COUNT).collect();\nlet plan = assign_hierarchical(&workers, &actors, &vnodes, strategy)?;","handlingStrategy":"validation","validationCode":"// Rust, before scheduling\nif vnodes.is_empty() {\n    anyhow::bail!(\"vnode set not generated\");\n}","typeGuard":"fn vnodes_ready(v: &[VnodeId], actors: &[ActorId]) -> bool {\n    !v.is_empty() && v.len() >= actors.len()\n}","tryCatchPattern":"match assign_hierarchical(&workers, &actors, &vnodes, strategy) {\n    Ok(m) => m,\n    Err(e) if e.to_string().contains(\"no vnodes\") => {\n        Err(anyhow!(\"generate the vnode set (default 256) before scheduling: {e:#}\"))\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Always materialize the standard vnode range before assignment.","Guard against vnode-count values of 0 coming from config.","Cover vnode generation in fragment scheduling tests."],"tags":["assignment","meta","vnode","empty-input"],"backgroundTag":"empty-required-field","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}