neondatabase/neon · error

stripe size can't be given for unsharded tenants

Error message

stripe size can't be given for unsharded tenants

What it means

With exactly one URL, ShardSpec::new treats the tenant as unsharded (ShardCount 0). Unsharded tenants do not stripe keys, so no stripe size may be supplied. stripe_size=Some(_) together with a single URL is rejected.

Source

Thrown at pageserver/client_grpc/src/client.rs:406

    /// 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).

            // Validate the URL.
            if PageserverProtocol::from_connstring(url)? != PageserverProtocol::Grpc {
                return Err(anyhow!("invalid shard URL {url}: must use gRPC"));

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Pass stripe_size=None when the map contains exactly one URL.
  2. Derive the stripe size from the shard count instead of an independent variable: None for one URL, Some(_) for more.
  3. Review spec deserialization for a serde default that materializes a stripe size where None was intended.

Example fix

// before
let spec = ShardSpec::new(single_url_map, Some(ShardStripeSize(32768)))?; // -> "stripe size can't be given for unsharded tenants"

// after
let spec = ShardSpec::new(single_url_map, None)?;
Defensive patterns

Strategy: validation

Validate before calling

// unsharded (single URL) specs must pass None
let stripe_size = if urls.len() == 1 { None } else { Some(stripe) };

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

When it happens

Trigger: ShardSpec::new with exactly one URL and stripe_size == Some(_).

Common situations: Applying a default stripe size unconditionally when building specs; scaling a sharded tenant down to one shard without clearing the stripe size; copy-paste between sharded and unsharded spec builders.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/ae4cefb57bbf7787. Report an issue: GitHub.