{"record":{"id":"ae4cefb57bbf7787","repo":"neondatabase/neon","slug":"stripe-size-can-t-be-given-for-unsharded-tenants","errorCode":null,"errorMessage":"stripe size can't be given for unsharded tenants","messagePattern":"stripe size can't be given for unsharded tenants","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pageserver/client_grpc/src/client.rs","lineNumber":406,"sourceCode":"    /// 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.\n            if !shard_id.is_unsharded() && shard_id.shard_number.0 >= shard_id.shard_count.0 {\n                return Err(anyhow!(\"invalid shard index {shard_id}\"));\n            }\n            // The above conditions guarantee that we have all shards 0..count: len() matches count,\n            // shard number < count, and numbers are unique (via hashmap).\n\n            // Validate the URL.\n            if PageserverProtocol::from_connstring(url)? != PageserverProtocol::Grpc {\n                return Err(anyhow!(\"invalid shard URL {url}: must use gRPC\"));","sourceCodeStart":388,"sourceCodeEnd":424,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/pageserver/client_grpc/src/client.rs#L388-L424","documentation":"With exactly one URL, ShardSpec::new treats the tenant as unsharded (ShardCount 0). Unsharded tenants do not stripe keys, so no stripe size may be supplied. stripe_size=Some(_) together with a single URL is rejected.","triggerScenarios":"ShardSpec::new with exactly one URL and stripe_size == Some(_).","commonSituations":"Applying a default stripe size unconditionally when building specs; scaling a sharded tenant down to one shard without clearing the stripe size; copy-paste between sharded and unsharded spec builders.","solutions":["Pass stripe_size=None when the map contains exactly one URL.","Derive the stripe size from the shard count instead of an independent variable: None for one URL, Some(_) for more.","Review spec deserialization for a serde default that materializes a stripe size where None was intended."],"exampleFix":"// before\nlet spec = ShardSpec::new(single_url_map, Some(ShardStripeSize(32768)))?; // -> \"stripe size can't be given for unsharded tenants\"\n\n// after\nlet spec = ShardSpec::new(single_url_map, None)?;","handlingStrategy":"validation","validationCode":"// unsharded (single URL) specs must pass None\nlet stripe_size = if urls.len() == 1 { None } else { Some(stripe) };","typeGuard":"fn stripe_size_valid(url_count: usize, stripe_size: Option<ShardStripeSize>) -> bool {\n    match url_count {\n        1 => stripe_size.is_none(),\n        2..=255 => stripe_size.is_some(),\n        _ => false,\n    }\n}","tryCatchPattern":null,"preventionTips":["Normalize the stripe size to None whenever scaling a spec down to one shard.","Avoid serde defaults that materialize a stripe size; make the field explicitly optional."],"tags":["rust","neon","pageserver","sharding","configuration"],"backgroundTag":"invalid-config-combination","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}