neondatabase/neon · error
stripe size must be given for sharded tenants
Error message
stripe size must be given for sharded tenants
What it means
ShardSpec::new derives the shard count from urls.len(): one URL means unsharded, two or more mean sharded. Sharded tenants distribute keys across shards using a ShardStripeSize, so a stripe size is mandatory. Passing stripe_size=None with two or more URLs is rejected.
Source
Thrown at pageserver/client_grpc/src/client.rs:403
impl ShardSpec {
/// Creates a new shard spec with the given URLs and stripe size. All shards must be given.
/// The stripe size must be Some for sharded tenants, or None for unsharded tenants.
pub fn new(
urls: HashMap<ShardIndex, String>,
stripe_size: Option<ShardStripeSize>,
) -> anyhow::Result<Self> {
// Compute the shard count.
let count = match urls.len() {
0 => return Err(anyhow!("no shards provided")),
1 => ShardCount::new(0), // NB: unsharded tenants use 0, like `ShardIndex::unsharded()`
n if n > u8::MAX as usize => return Err(anyhow!("too many shards: {n}")),
n => ShardCount::new(n as u8),
};
// Validate the stripe size.
if stripe_size.is_none() && !count.is_unsharded() {
return Err(anyhow!("stripe size must be given for sharded tenants"));
}
if stripe_size.is_some() && count.is_unsharded() {
return Err(anyhow!("stripe size can't be given for unsharded tenants"));
}
// Validate the shard spec.
for (shard_id, url) in &urls {
// The shard index must match the computed shard count, even for unsharded tenants.
if shard_id.shard_count != count {
return Err(anyhow!("invalid shard index {shard_id}, expected {count}"));
}
// The shard index' number and count must be consistent.
if !shard_id.is_unsharded() && shard_id.shard_number.0 >= shard_id.shard_count.0 {
return Err(anyhow!("invalid shard index {shard_id}"));
}
// The above conditions guarantee that we have all shards 0..count: len() matches count,
// shard number < count, and numbers are unique (via hashmap).
View on GitHub (pinned to 8f60b04da4)
Solutions
- Pass Some(stripe_size), for example Some(ShardStripeSize(32768)), whenever the map has more than one URL.
- Find why the stripe size was None: an omitted spec field, a serde default, or a stale cached spec.
- Keep the default stripe size in one constant and reuse it wherever sharded specs are built.
Example fix
// before let spec = ShardSpec::new(urls, None)?; // 4 shard URLs -> "stripe size must be given for sharded tenants" // after // 32768 is the usual default stripe size let spec = ShardSpec::new(urls, Some(ShardStripeSize(32768)))?;
Defensive patterns
Strategy: validation
Validate before calling
// decide stripe size from the shard count, never independently
let stripe_size = if urls.len() > 1 { Some(stripe) } else { None };
anyhow::ensure!(
!(urls.len() > 1 && stripe_size.is_none()),
"sharded tenants ({} urls) require a stripe size",
urls.len()
); Type guard
fn stripe_size_valid(url_count: usize, stripe_size: Option<ShardStripeSize>) -> bool {
match url_count {
1 => stripe_size.is_none(),
2..=255 => stripe_size.is_some(),
_ => false,
}
} Prevention
- Derive stripe size from shard count at a single place instead of passing an independent variable.
- Add a serialization test that round-trips sharded specs and asserts shard_stripe_size survives.
When it happens
Trigger: ShardSpec::new with urls.len() between 2 and 255 and stripe_size == None, even when every ShardIndex in the map is otherwise valid.
Common situations: A control-plane spec serializer drops shard_stripe_size when it is null; code written for unsharded tenants is reused for a sharded tenant; a partial shard split keeps the old None stripe size.
Related errors
- stripe size can't be given for unsharded tenants
- shard {shard_index} missing from pageserver_connection_info
- must have at least one pageserver
- only unsharded tenants are supported at this time: {}
- can't reduce shard count from {} to {}
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/1dfc203a6e550ba2.
Report an issue: GitHub.