{"record":{"id":"1dfc203a6e550ba2","repo":"neondatabase/neon","slug":"stripe-size-must-be-given-for-sharded-tenants","errorCode":null,"errorMessage":"stripe size must be given for sharded tenants","messagePattern":"stripe size must be given for sharded tenants","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pageserver/client_grpc/src/client.rs","lineNumber":403,"sourceCode":"\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.\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","sourceCodeStart":385,"sourceCodeEnd":421,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/pageserver/client_grpc/src/client.rs#L385-L421","documentation":"ShardSpec::new derives the shard count from urls.len(): one URL means unsharded, two or more mean sharded. Sharded tenants distribute keys across shards using a ShardStripeSize, so a stripe size is mandatory. Passing stripe_size=None with two or more URLs is rejected.","triggerScenarios":"ShardSpec::new with urls.len() between 2 and 255 and stripe_size == None, even when every ShardIndex in the map is otherwise valid.","commonSituations":"A control-plane spec serializer drops shard_stripe_size when it is null; code written for unsharded tenants is reused for a sharded tenant; a partial shard split keeps the old None stripe size.","solutions":["Pass Some(stripe_size), for example Some(ShardStripeSize(32768)), whenever the map has more than one URL.","Find why the stripe size was None: an omitted spec field, a serde default, or a stale cached spec.","Keep the default stripe size in one constant and reuse it wherever sharded specs are built."],"exampleFix":"// before\nlet spec = ShardSpec::new(urls, None)?; // 4 shard URLs -> \"stripe size must be given for sharded tenants\"\n\n// after\n// 32768 is the usual default stripe size\nlet spec = ShardSpec::new(urls, Some(ShardStripeSize(32768)))?;","handlingStrategy":"validation","validationCode":"// decide stripe size from the shard count, never independently\nlet stripe_size = if urls.len() > 1 { Some(stripe) } else { None };\nanyhow::ensure!(\n    !(urls.len() > 1 && stripe_size.is_none()),\n    \"sharded tenants ({} urls) require a stripe size\",\n    urls.len()\n);","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":["Derive stripe size from shard count at a single place instead of passing an independent variable.","Add a serialization test that round-trips sharded specs and asserts shard_stripe_size survives."],"tags":["rust","neon","pageserver","sharding","configuration"],"backgroundTag":"missing-required-config-field","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}