{"record":{"id":"7801269c0fe14bb3","repo":"neondatabase/neon","slug":"too-many-shards-n","errorCode":null,"errorMessage":"too many shards: {n}","messagePattern":"too many shards: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pageserver/client_grpc/src/client.rs","lineNumber":397,"sourceCode":"    count: ShardCount,\n    /// The stripe size for these shards.\n    ///\n    /// INVARIANT: None for unsharded tenants, Some for sharded.\n    stripe_size: Option<ShardStripeSize>,\n}\n\nimpl ShardSpec {\n    /// Creates a new shard spec with the given URLs and stripe size. All shards must be given.\n    /// The stripe size must be Some for sharded tenants, or None for unsharded tenants.\n    pub fn new(\n        urls: HashMap<ShardIndex, String>,\n        stripe_size: Option<ShardStripeSize>,\n    ) -> anyhow::Result<Self> {\n        // Compute the shard count.\n        let count = match urls.len() {\n            0 => return Err(anyhow!(\"no shards provided\")),\n            1 => ShardCount::new(0), // NB: unsharded tenants use 0, like `ShardIndex::unsharded()`\n            n if n > u8::MAX as usize => return Err(anyhow!(\"too many shards: {n}\")),\n            n => ShardCount::new(n as u8),\n        };\n\n        // Validate the stripe size.\n        if stripe_size.is_none() && !count.is_unsharded() {\n            return Err(anyhow!(\"stripe size must be given for sharded tenants\"));\n        }\n        if stripe_size.is_some() && count.is_unsharded() {\n            return Err(anyhow!(\"stripe size can't be given for unsharded tenants\"));\n        }\n\n        // Validate the shard spec.\n        for (shard_id, url) in &urls {\n            // The shard index must match the computed shard count, even for unsharded tenants.\n            if shard_id.shard_count != count {\n                return Err(anyhow!(\"invalid shard index {shard_id}, expected {count}\"));\n            }\n            // The shard index' number and count must be consistent.","sourceCodeStart":379,"sourceCodeEnd":415,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/pageserver/client_grpc/src/client.rs#L379-L415","documentation":"ShardSpec::new in pageserver/client_grpc builds a tenant shard specification from a HashMap of ShardIndex to URL. The shard count is stored in a u8 (ShardCount), and 0 is reserved for unsharded tenants, so at most 255 shard URLs are accepted. A map with more than 255 entries is rejected with this error before any other validation runs.","triggerScenarios":"Calling ShardSpec::new(urls, stripe_size), or any control-plane spec parser that feeds it, with urls.len() greater than 255 (u8::MAX). The count check runs first, so stripe size and shard index validation never execute.","commonSituations":"A test harness or control plane generates N shards from a config value without clamping N; a script duplicates shard URLs into an oversized map; an operator tries to scale a single tenant beyond the current 255-shard ceiling.","solutions":["Reduce the shard set to 255 or fewer URLs; ShardCount is u8-backed with 0 reserved for unsharded, so 255 is a hard maximum.","Inspect the code that builds the HashMap for duplicated entries or generation loops that run twice (e.g. 1..=N appended two times).","If the tenant genuinely needs more than 255 shards, split the tenant or raise the limit upstream in ShardCount; never truncate the map to sneak past the check."],"exampleFix":"// before\nlet spec = ShardSpec::new(all_shard_urls, Some(stripe))?; // 300 entries -> \"too many shards: 300\"\n\n// after\nassert!(all_shard_urls.len() <= u8::MAX as usize, \"max 255 shards\");\nlet spec = ShardSpec::new(all_shard_urls, Some(stripe))?;","handlingStrategy":"validation","validationCode":"// before calling ShardSpec::new\nconst MAX_SHARDS: usize = u8::MAX as usize; // 0 reserved for unsharded\nanyhow::ensure!(\n    (1..=MAX_SHARDS).contains(&urls.len()),\n    \"shard map has {} entries; 1..=255 required\",\n    urls.len()\n);","typeGuard":"fn shard_count_representable(urls: &HashMap<ShardIndex, String>) -> bool {\n    (1..=u8::MAX as usize).contains(&urls.len())\n}","tryCatchPattern":"match ShardSpec::new(urls, stripe_size) {\n    Ok(spec) => spec,\n    Err(e) if e.to_string().starts_with(\"too many shards\") => {\n        // reduce the shard set, then rebuild the spec with regenerated indices\n        return Err(e.context(\"shard set exceeds u8 ShardCount; split the tenant\"));\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Clamp or assert shard counts at the configuration boundary before building the map.","Treat 255 as a documented product limit next to every shard-count knob.","Generate shard maps programmatically instead of assembling them by hand."],"tags":["rust","neon","pageserver","sharding","input-validation"],"backgroundTag":"shard-count-limit-exceeded","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}