risingwavelabs/risingwave · error

not enough vnodes ({}) for actors ({}); each actor needs at

Error message

not enough vnodes ({}) for actors ({}); each actor needs at least one vnode

What it means

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.

Source

Thrown at src/meta/src/stream/stream_graph/assignment.rs:761

    ) -> Result<BTreeMap<W, BTreeMap<A, Vec<V>>>>
    where
        W: Ord + Hash + Eq + Clone + Debug,
        A: Ord + Hash + Eq + Copy + Debug,
        V: Hash + Eq + Copy + Debug,
    {
        ensure!(
            !workers.is_empty(),
            "no workers to assign; assignment is meaningless"
        );
        ensure!(
            !actors.is_empty(),
            "no actors to assign; assignment is meaningless"
        );
        ensure!(
            !vnodes.is_empty(),
            "no vnodes to assign; assignment is meaningless"
        );
        ensure!(
            vnodes.len() >= actors.len(),
            "not enough vnodes ({}) for actors ({}); each actor needs at least one vnode",
            vnodes.len(),
            actors.len()
        );

        let chunk_size = match self.vnode_chunking_strategy {
            VnodeChunkingStrategy::NoChunking => {
                return assign_hierarchical(
                    workers,
                    actors,
                    vnodes,
                    self.salt,
                    self.actor_capacity,
                    self.balance_strategy,
                )
                .context("hierarchical assignment failed");
            }

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Set vnode count to at least the actor count (RisingWave default 256 covers typical parallelism).
  2. Lower the fragment parallelism to fit within the vnode count.
  3. When computing vnodes programmatically, derive the size as `max(default, parallelism)`.

Example fix

// before
let vnodes: Vec<u32> = (0..2).collect(); // 4 actors
// after
let vnodes: Vec<u32> = (0..actors.len().max(DEFAULT_VNODE_COUNT)).collect();
Defensive patterns

Strategy: validation

Validate before calling

// Rust, before scheduling
if vnodes.len() < actors.len() {
    anyhow::bail!("vnodes ({}) < actors ({})", vnodes.len(), actors.len());
}

Try / catch

match assign_hierarchical(&workers, &actors, &vnodes, strategy) {
    Ok(m) => m,
    Err(e) if e.to_string().contains("not enough vnodes") => {
        Err(anyhow!("raise vnode count above parallelism: {e:#}"))
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Calling the inner `assign_hierarchical` where `vnodes.len() < actors.len()` — e.g. 4 actors but 2 vnodes.

Common situations: Custom vnode-count settings lower than fragment parallelism; test/tooling calls with shrunken vnode sets; vnode count overridden by a config that ignores parallelism.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/9415889d4703cc59. Report an issue: GitHub.