{"record":{"id":"45a97c9909194305","repo":"risingwavelabs/risingwave","slug":"no-vnodes-to-assign","errorCode":null,"errorMessage":"no vnodes to assign","messagePattern":"no vnodes to assign","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/meta/src/stream/stream_graph/assignment.rs","lineNumber":401,"sourceCode":"    workers: &BTreeMap<W, NonZeroUsize>,\n    actors: &[A],\n    virtual_nodes: &[V],\n    salt: S,\n    actor_capacity_mode: CapacityMode,\n    balanced_by: BalancedBy,\n) -> anyhow::Result<BTreeMap<W, BTreeMap<A, Vec<V>>>>\nwhere\n    W: Ord + Hash + Eq + Clone + Debug,\n    A: Ord + Hash + Eq + Copy + Clone + Debug,\n    V: Hash + Eq + Copy + Clone + Debug,\n    S: Hash + Copy,\n{\n    if actors.is_empty() {\n        return Err(anyhow!(\"no actors to assign\"));\n    }\n\n    if virtual_nodes.is_empty() {\n        return Err(anyhow!(\"no vnodes to assign\"));\n    }\n\n    // Validate input: ensure vnode count can cover all actors\n    if actors.len() > virtual_nodes.len() {\n        return Err(anyhow!(\n            \"actor count ({}) exceeds vnode count ({})\",\n            actors.len(),\n            virtual_nodes.len()\n        ));\n    }\n\n    let actor_capacity_fn = match actor_capacity_mode {\n        CapacityMode::Weighted => weighted_scale,\n        CapacityMode::Unbounded => unbounded_scale,\n    };\n\n    // Distribute actors across workers based on their weight\n    let actor_to_worker: BTreeMap<W, Vec<A>> =","sourceCodeStart":383,"sourceCodeEnd":419,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/meta/src/stream/stream_graph/assignment.rs#L383-L419","documentation":"`assign_hierarchical` requires a non-empty virtual node list to distribute actors over; an empty vnode list makes balanced assignment impossible. It is a fast-fail precondition at the top of the function.","triggerScenarios":"Calling `assign_hierarchical` (assignment.rs:401) with `virtual_nodes.is_empty() == true`; test `error_on_empty_vnodes` triggers it.","commonSituations":"Passing a vnode set computed from a zero vnode-count table; misconfigured parallelism/vnode-count of 0; calling the assignment helper directly in tests with empty vnode slices.","solutions":["Ensure vnode count is >= 1 for the fragment (default is 256 in RisingWave) before assignment.","Guard the caller to reject fragments with empty vnode sets earlier with a clearer error.","Fix any code that computes vnodes from `maybe_vnode_count` so zero/unknown counts are handled before assignment."],"exampleFix":"// before\nlet mapping = assign_hierarchical(&workers, &actors, &[], strategy)?;\n// after\nassert!(!vnodes.is_empty(), \"vnodes must be generated before assignment\");\nlet mapping = assign_hierarchical(&workers, &actors, &vnodes, strategy)?;","handlingStrategy":"validation","validationCode":"// Rust, before calling assignment\nif vnodes.is_empty() {\n    anyhow::bail!(\"refusing to assign: vnode list is empty\");\n}","typeGuard":"fn has_vnodes(v: &[VnodeId]) -> bool { !v.is_empty() }","tryCatchPattern":"match assign_hierarchical(&workers, &actors, &vnodes, strategy) {\n    Ok(m) => m,\n    Err(e) if e.to_string().contains(\"no vnodes to assign\") => {\n        Err(anyhow!(\"vnode set missing; check vnode count config: {e:#}\"))\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Use the default vnode count (256) unless there is a specific reason not to.","Handle unset `maybe_vnode_count` before computing vnode sets.","Add unit tests for zero-vnode-count table configs."],"tags":["assignment","meta","validation","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"}