neondatabase/neon · error

no shards provided

Error message

no shards provided

What it means

`ShardSpec::new` derives the shard count from the number of URL entries (`urls.len()`), where 1 entry means unsharded and each shard needs its own pageserver address. An empty map has no meaningful count, so construction is rejected immediately with this error.

Source

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

    ///
    /// NB: this is 0 for unsharded tenants, following `ShardIndex::unsharded()` convention.
    count: ShardCount,
    /// The stripe size for these shards.
    ///
    /// INVARIANT: None for unsharded tenants, Some for sharded.
    stripe_size: Option<ShardStripeSize>,
}

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

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Populate the map with at least one shard URL before constructing the spec (one entry = unsharded)
  2. Check upstream filtering/discovery that produced the empty shard list (tenant id, labels, config)
  3. Default to the unsharded entry (ShardIndex::unsharded()) when the tenant has no shards yet
  4. Fail early in config loading if the shard URL list is empty, with a clearer message

Example fix

// before: empty map
let spec = ShardSpec::new(HashMap::new(), None)?; // no shards provided

// after: unsharded single-shard spec
let mut urls = HashMap::new();
urls.insert(ShardIndex::unsharded(), pageserver_url.to_string());
let spec = ShardSpec::new(urls, None)?;
Defensive patterns

Strategy: validation

Validate before calling

if urls.is_empty() {
    anyhow::bail!("cannot build ShardSpec: no pageserver URLs configured for tenant");
}
let spec = ShardSpec::new(urls, stripe_size)?;

Type guard

fn has_shard_urls(urls: &HashMap<ShardIndex, String>) -> bool {
    !urls.is_empty()
}

Prevention

When it happens

Trigger: Constructing a `ShardSpec` with an empty `HashMap<ShardIndex, String>` — e.g. iterating a filtered shard list that yielded nothing, or wiring up config before any pageserver addresses are known.

Common situations: Empty configuration/stub lists in tests; discovery code that filters out all shards (wrong tenant filter); building a client before the controller has assigned pageservers.

Related errors


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