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

  1. Pass Some(stripe_size), for example Some(ShardStripeSize(32768)), whenever the map has more than one URL.
  2. Find why the stripe size was None: an omitted spec field, a serde default, or a stale cached spec.
  3. 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

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


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