risingwavelabs/risingwave · error

no vnodes to assign

Error message

no vnodes to assign

What it means

`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.

Source

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

    workers: &BTreeMap<W, NonZeroUsize>,
    actors: &[A],
    virtual_nodes: &[V],
    salt: S,
    actor_capacity_mode: CapacityMode,
    balanced_by: BalancedBy,
) -> anyhow::Result<BTreeMap<W, BTreeMap<A, Vec<V>>>>
where
    W: Ord + Hash + Eq + Clone + Debug,
    A: Ord + Hash + Eq + Copy + Clone + Debug,
    V: Hash + Eq + Copy + Clone + Debug,
    S: Hash + Copy,
{
    if actors.is_empty() {
        return Err(anyhow!("no actors to assign"));
    }

    if virtual_nodes.is_empty() {
        return Err(anyhow!("no vnodes to assign"));
    }

    // Validate input: ensure vnode count can cover all actors
    if actors.len() > virtual_nodes.len() {
        return Err(anyhow!(
            "actor count ({}) exceeds vnode count ({})",
            actors.len(),
            virtual_nodes.len()
        ));
    }

    let actor_capacity_fn = match actor_capacity_mode {
        CapacityMode::Weighted => weighted_scale,
        CapacityMode::Unbounded => unbounded_scale,
    };

    // Distribute actors across workers based on their weight
    let actor_to_worker: BTreeMap<W, Vec<A>> =

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Ensure vnode count is >= 1 for the fragment (default is 256 in RisingWave) before assignment.
  2. Guard the caller to reject fragments with empty vnode sets earlier with a clearer error.
  3. Fix any code that computes vnodes from `maybe_vnode_count` so zero/unknown counts are handled before assignment.

Example fix

// before
let mapping = assign_hierarchical(&workers, &actors, &[], strategy)?;
// after
assert!(!vnodes.is_empty(), "vnodes must be generated before assignment");
let mapping = assign_hierarchical(&workers, &actors, &vnodes, strategy)?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust, before calling assignment
if vnodes.is_empty() {
    anyhow::bail!("refusing to assign: vnode list is empty");
}

Type guard

fn has_vnodes(v: &[VnodeId]) -> bool { !v.is_empty() }

Try / catch

match assign_hierarchical(&workers, &actors, &vnodes, strategy) {
    Ok(m) => m,
    Err(e) if e.to_string().contains("no vnodes to assign") => {
        Err(anyhow!("vnode set missing; check vnode count config: {e:#}"))
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Calling `assign_hierarchical` (assignment.rs:401) with `virtual_nodes.is_empty() == true`; test `error_on_empty_vnodes` triggers it.

Common situations: 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.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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