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
- Pass a vnode count between 1 and 32768 inclusive
- Check the value in the DDL/query that builds the vnode expression
- 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
- Validate vnode counts from config before building SQL
- Clamp bucket counts to 1..=32768
- Write tests for boundary values 0, 1, 32768, 32769
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
- regexp_count() does not support the global option
- {0}
- {0}
- AFTER MATCH SKIP TO FIRST/LAST missing its target variable
- ALTER SINK_RATE_LIMIT is not for sink into table
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)