risingwavelabs/risingwave · error

the first argument (vnode count) must be in range 1..=

Error message

the first argument (vnode count) must be in range 1..={}

What it means

The `vnode(key1, key2, ..., vnode_count)` function requires its last constant argument to be an integer between 1 and `VirtualNode::MAX_COUNT` (32768). Values outside this range cannot form a valid vnode distribution, so `build_user` rejects them.

Solutions

  1. Pass a vnode count between 1 and 32768 inclusive
  2. Check the value in the DDL/query that builds the vnode expression
  3. If the count comes from a config variable, validate/clamp it before constructing the SQL

Example fix

-- before
SELECT vnode(k, 0) FROM t;
-- after
SELECT vnode(k, 128) FROM t;
Defensive patterns

Strategy: validation

Validate before calling

if (!Number.isInteger(count) || count < 1 || count > 32768) throw new Error(`vnode count must be in 1..=32768, got ${count}`);

Try / catch

try { await rw.query(`SELECT vnode(k, ${count}) FROM t`) } catch (e) { if (String(e).includes('must be in range 1..=')) { /* clamp count and retry */ } else throw e; }

Prevention

When it happens

Trigger: Building a `vnode` expression where the constant vnode-count argument is 0, negative, or greater than 32768 (`VirtualNode::MAX_COUNT` as i32).

Common situations: Configuring distribution keys or hash bucketing with an invalid bucket count; typo like `vnode(k, 0)`; copying a vnode count from a wrong config (e.g. 65536).

Related errors


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

Appendix: source

Thrown at src/expr/impl/src/scalar/vnode.rs:76

        }
        .boxed()),
    }
}

#[build_function("vnode_user(...) -> int2")]
fn build_user(_: DataType, children: Vec<BoxedExpression>) -> Result<BoxedExpression> {
    let mut children = children.into_iter();

    let vnode_count = children
        .next()
        .unwrap() // always exist, argument number enforced in binder
        .eval_const() // required to be constant
        .context("the first argument (vnode count) must be a constant")?
        .context("the first argument (vnode count) must not be NULL")?
        .into_int32(); // always int32, casted during type inference

    if !(1i32..=VirtualNode::MAX_COUNT as i32).contains(&vnode_count) {
        return Err(anyhow::anyhow!(
            "the first argument (vnode count) must be in range 1..={}",
            VirtualNode::MAX_COUNT
        )
        .into());
    }

    let children = children.collect_vec();
    let all_indices = (0..children.len()).collect();
    match try_into_sync_exprs(children) {
        Ok(children) => Ok(VnodeExpression {
            vnode_count: Some(vnode_count.try_into().unwrap()),
            all_indices,
            children,
        }
        .boxed()),
        Err(children) => Ok(VnodeExpression {
            vnode_count: Some(vnode_count.try_into().unwrap()),
            all_indices,

View on GitHub (pinned to 6469eb736d)