{"record":{"id":"4f7494fba39a2c53","repo":"neondatabase/neon","slug":"can-t-reduce-shard-count-from-to","errorCode":null,"errorMessage":"can't reduce shard count from {} to {}","messagePattern":"can't reduce shard count from (.+?) to (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pageserver/client_grpc/src/client.rs","lineNumber":134,"sourceCode":"            compression,\n            shards: ArcSwap::new(Arc::new(shards)),\n        })\n    }\n\n    /// Updates the shards from the given shard spec. In-flight requests will complete using the\n    /// existing shards, but may retry with the new shards if they fail.\n    ///\n    /// TODO: verify that in-flight requests are allowed to complete, and that the old pools are\n    /// properly spun down and dropped afterwards.\n    pub fn update_shards(&self, shard_spec: ShardSpec) -> anyhow::Result<()> {\n        // Validate the shard spec. We should really use `ArcSwap::rcu` for this, to avoid races\n        // with concurrent updates, but that involves creating a new `Shards` on every attempt,\n        // which spins up a bunch of Tokio tasks and such. These should already be checked elsewhere\n        // in the stack, and if they're violated then we already have problems elsewhere, so a\n        // best-effort but possibly-racy check is okay here.\n        let old = self.shards.load_full();\n        if shard_spec.count < old.count {\n            return Err(anyhow!(\n                \"can't reduce shard count from {} to {}\",\n                old.count,\n                shard_spec.count\n            ));\n        }\n        if !old.count.is_unsharded() && shard_spec.stripe_size != old.stripe_size {\n            return Err(anyhow!(\n                \"can't change stripe size from {} to {}\",\n                old.stripe_size.expect(\"always Some when sharded\"),\n                shard_spec.stripe_size.expect(\"always Some when sharded\")\n            ));\n        }\n\n        let shards = Shards::new(\n            self.tenant_id,\n            self.timeline_id,\n            shard_spec,\n            self.auth_token.clone(),","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/pageserver/client_grpc/src/client.rs#L116-L152","documentation":"`ShardedPageserverClient::update_shards` (gRPC pageserver client) only supports growing the shard set, because Neon sharding uses splits — existing key→shard mappings stay valid when count increases but cannot be retracted. Calling it with a `ShardSpec` whose count is lower than the currently installed one returns this error without changing state.","triggerScenarios":"Invoking `update_shards(shard_spec)` where `shard_spec.count <` the client's current shard count — e.g. feeding a stale/smaller shard map from the controller after shards were already split.","commonSituations":"Controller reconciliation bugs replaying an old shard map; mixing up shard-count semantics (0 = unsharded vs. actual counts) when building the spec; tests attempting to 'unsplit' shards.","solutions":["Don't reduce shard counts — Neon shards only split; construct the client fresh with the smaller spec instead of updating in place","Fix the caller (controller/config) to always pass the current-or-larger shard count","Check for stale shard-map caches feeding outdated ShardSpecs into update_shards","Verify the spec's count derivation (urls.len()) matches the intended topology"],"exampleFix":"// before: attempts to shrink from 8 shards to 4\nclient.update_shards(ShardSpec::new(four_shard_urls, stripe)?)?; // can't reduce shard count\n\n// after: only grow, or rebuild the client for a different topology\nif new_spec.count >= current.count {\n    client.update_shards(new_spec)?;\n} else {\n    let client = ShardedPageserverClient::new(tenant_id, timeline_id, new_spec, ...)?;\n}","handlingStrategy":"validation","validationCode":"let old = client.shards();\nif shard_spec.count < old.count {\n    anyhow::bail!(\n        \"shard count can only grow (current {}, requested {}); rebuild the client instead\",\n        old.count, shard_spec.count\n    );\n}\nclient.update_shards(shard_spec)?;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never attempt to un-split shards in place; create a fresh client for a different topology","Validate controller shard maps against the client's current count before reconciling","Unit-test update_shards with grow/equal/shrink specs to pin the contract"],"tags":["rust","neon","sharding","pageserver","grpc-client"],"backgroundTag":"invalid-shard-configuration","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}