{"record":{"id":"9415889d4703cc59","repo":"risingwavelabs/risingwave","slug":"not-enough-vnodes-for-actors-each-actor","errorCode":null,"errorMessage":"not enough vnodes ({}) for actors ({}); each actor needs at least one vnode","messagePattern":"not enough vnodes \\((.+?)\\) for actors \\((.+?)\\); each actor needs at least one vnode","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/meta/src/stream/stream_graph/assignment.rs","lineNumber":761,"sourceCode":"    ) -> 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,\n                    self.balance_strategy,\n                )\n                .context(\"hierarchical assignment failed\");\n            }","sourceCodeStart":743,"sourceCodeEnd":779,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/meta/src/stream/stream_graph/assignment.rs#L743-L779","documentation":"The last guard in the ensure! chain (assignment.rs:761) requires `vnodes.len() >= actors.len()` because each actor must receive at least one virtual node. When there are fewer vnodes than actors, a valid per-actor vnode set cannot be constructed and the function errors with both counts.","triggerScenarios":"Calling the inner `assign_hierarchical` where `vnodes.len() < actors.len()` — e.g. 4 actors but 2 vnodes.","commonSituations":"Custom vnode-count settings lower than fragment parallelism; test/tooling calls with shrunken vnode sets; vnode count overridden by a config that ignores parallelism.","solutions":["Set vnode count to at least the actor count (RisingWave default 256 covers typical parallelism).","Lower the fragment parallelism to fit within the vnode count.","When computing vnodes programmatically, derive the size as `max(default, parallelism)`."],"exampleFix":"// before\nlet vnodes: Vec<u32> = (0..2).collect(); // 4 actors\n// after\nlet vnodes: Vec<u32> = (0..actors.len().max(DEFAULT_VNODE_COUNT)).collect();","handlingStrategy":"validation","validationCode":"// Rust, before scheduling\nif vnodes.len() < actors.len() {\n    anyhow::bail!(\"vnodes ({}) < actors ({})\", vnodes.len(), actors.len());\n}","typeGuard":null,"tryCatchPattern":"match assign_hierarchical(&workers, &actors, &vnodes, strategy) {\n    Ok(m) => m,\n    Err(e) if e.to_string().contains(\"not enough vnodes\") => {\n        Err(anyhow!(\"raise vnode count above parallelism: {e:#}\"))\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Size vnode sets as max(default_vnode_count, parallelism).","Validate vnode-count overrides against parallelism at config load time.","Add a property test: assignment succeeds whenever vnodes >= actors."],"tags":["assignment","meta","vnode","validation"],"backgroundTag":"value-out-of-range","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T21:17:11.552Z"}