neondatabase/neon · error

invalid shard index {shard_id}, expected {count}

Error message

invalid shard index {shard_id}, expected {count}

What it means

Every ShardIndex key in the map must carry a shard_count equal to the number of URLs supplied (0 when there is a single, unsharded URL). A key whose count differs, for example 8 URLs each labelled shard_count=4, cannot describe a coherent shard set and is rejected. The expected count is printed in the message.

Source

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

            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"));
            }
        }

        Ok(Self {
            urls,
            count,
            stripe_size,

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Rebuild the map so every ShardIndex uses ShardCount::new(urls.len() as u8), or 0 for a single URL.
  2. After any change to the shard set, regenerate all ShardIndex keys instead of patching individual entries.
  3. Read the error output: it shows the offending shard_id and the expected count, so compare number and count directly.

Example fix

// before: 8 URLs, but each ShardIndex still labelled with the old count of 4
let spec = ShardSpec::new(urls, stripe)?; // -> "invalid shard index <4:4>, expected 8"

// after: relabel keys for the new layout
let count = ShardCount::new(urls.len() as u8);
let urls = urls.into_iter()
    .map(|(idx, url)| (ShardIndex::new(idx.shard_number.0 % count.0, count), url))
    .collect::<HashMap<_, _>>();
let spec = ShardSpec::new(urls, stripe)?;
Defensive patterns

Strategy: validation

Validate before calling

// every key's shard_count must equal the map size (0 for a single URL)
let expected = if urls.len() == 1 { ShardCount::new(0) } else { ShardCount::new(urls.len() as u8) };
for shard_id in urls.keys() {
    anyhow::ensure!(
        shard_id.shard_count == expected,
        "shard index {shard_id} does not match expected count {expected}"
    );
}

Type guard

fn shard_indices_consistent(urls: &HashMap<ShardIndex, String>) -> bool {
    let expected = if urls.len() == 1 { ShardCount::new(0) } else { ShardCount::new(urls.len() as u8) };
    urls.keys().all(|k| k.shard_count == expected)
}

Prevention

When it happens

Trigger: ShardSpec::new where any key's shard_id.shard_count differs from urls.len() (with 1 mapping to 0). Typical cause: adding or removing URLs without relabelling the indices, or mixing shards from two different shard layouts.

Common situations: A partially applied shard split where half the URLs still use the old count; constructing ShardIndex with the default unsharded count and forgetting to set shard_count.

Related errors


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