neondatabase/neon · error

invalid shard index {shard_id}

Error message

invalid shard index {shard_id}

What it means

For a sharded ShardIndex, the shard number is 0-based and must be strictly less than the shard count. A key such as shard 4 of 4 (number >= count) is rejected here; the preceding check has already ensured the count itself matches urls.len().

Source

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

        };

        // 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. Use 0-based numbers: shard numbers must span 0..count, that is 0 to count-1.
  2. Generate keys programmatically: (0..n).map(|i| ShardIndex::new(i, ShardCount::new(n))).
  3. Check the shard_id in the message; its number must be smaller than its count.

Example fix

// before: 1-based numbering -> shard 8 of 8 is invalid
for i in 1..=n {
    map.insert(ShardIndex::new(i, count), url(i));
}

// after: 0-based numbering
for i in 0..n {
    map.insert(ShardIndex::new(i, count), url(i));
}
Defensive patterns

Strategy: validation

Validate before calling

for shard_id in urls.keys() {
    if !shard_id.is_unsharded() {
        anyhow::ensure!(
            (shard_id.shard_number.0 as usize) < urls.len(),
            "shard number {} out of range for {} shards",
            shard_id.shard_number.0,
            urls.len()
        );
    }
}

Type guard

fn shard_numbers_in_range(urls: &HashMap<ShardIndex, String>) -> bool {
    urls.keys().all(|k| k.is_unsharded() || (k.shard_number.0 as usize) < urls.len())
}

Prevention

When it happens

Trigger: ShardSpec::new with any key where shard_number.0 >= shard_count.0 and the index is not the unsharded one. Most commonly produced by 1-based generation loops (for i in 1..=n).

Common situations: Generating shard indices with 1-based numbering; hand-written configs numbering shards 1..N; off-by-one after increasing the shard count.

Related errors


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