{"record":{"id":"d7ed94e3f4435fa5","repo":"neondatabase/neon","slug":"shard-shard-index-missing-from-pageserver-connec","errorCode":null,"errorMessage":"shard {shard_index} missing from pageserver_connection_info shard map","messagePattern":"shard (.+?) missing from pageserver_connection_info shard map","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"compute_tools/src/config.rs","lineNumber":94,"sourceCode":"                \"# from compute spec's pageserver_connection_info.stripe_size field\"\n            )?;\n            writeln!(file, \"neon.stripe_size={stripe_size}\")?;\n        }\n\n        let mut libpq_urls: Option<Vec<String>> = Some(Vec::new());\n        let num_shards = if conninfo.shard_count.0 == 0 {\n            1 // unsharded, treat it as a single shard\n        } else {\n            conninfo.shard_count.0\n        };\n\n        for shard_number in 0..num_shards {\n            let shard_index = ShardIndex {\n                shard_number: ShardNumber(shard_number),\n                shard_count: conninfo.shard_count,\n            };\n            let info = conninfo.shards.get(&shard_index).ok_or_else(|| {\n                anyhow::anyhow!(\n                    \"shard {shard_index} missing from pageserver_connection_info shard map\"\n                )\n            })?;\n\n            let first_pageserver = info\n                .pageservers\n                .first()\n                .expect(\"must have at least one pageserver\");\n\n            // Add the libpq URL to the array, or if the URL is missing, reset the array\n            // forgetting any previous entries. All servers must have a libpq URL, or none\n            // at all.\n            if let Some(url) = &first_pageserver.libpq_url {\n                if let Some(ref mut urls) = libpq_urls {\n                    urls.push(url.clone());\n                }\n            } else {\n                libpq_urls = None","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/compute_tools/src/config.rs#L76-L112","documentation":"When expanding a sharded pageserver connstring, compute_tools iterates shard numbers 0..shard_count and looks each up in conninfo.shards. For some shard index the map had no entry, so the per-shard pageserver list could not be built. The connstring's shard_count disagrees with the shard map it carries - malformed or truncated control-plane input. (The next line also has a hard expect on a non-empty pageservers list, a sibling failure mode.)","triggerScenarios":"PageserverConnectionInfo parsed with shard_count > 0 but whose shards map lacks an entry for one of the shard indices in 0..shard_count; e.g. a connstring built for N shards but embedding only N-1 shard entries.","commonSituations":"Control-plane bugs during shard resharding (shard_count bumped before shard map updated); hand-edited or templated connstrings; version skew in how shard maps are serialized into the GUC; partially migrated sharded tenants.","solutions":["Inspect the neon.pageserver_connstring GUC: the shard_count number must match the number of shard entries in the map","Regenerate the connstring from the control plane after resharding completes so count and map are consistent","For unsharded tenants keep shard_count at 0, which the code treats as a single shard and skips the map"],"exampleFix":"# before: shard_count=2 with only shard 0 mapped -> error\n# after (GUC payload shape)\nshard_count=2;shards={ 0: 'host=ps-0', 1: 'host=ps-1' }  # every index 0..count-1 present","handlingStrategy":"validation","validationCode":"// Validate shard-map completeness before expanding the connstring\nlet n = if conninfo.shard_count.0 == 0 { 1 } else { conninfo.shard_count.0 };\nfor i in 0..n {\n    let idx = ShardIndex { shard_number: ShardNumber(i), shard_count: conninfo.shard_count };\n    let info = conninfo.shards.get(&idx).ok_or(anyhow!(\"shard {idx} missing\"))?;\n    if info.pageservers.is_empty() { anyhow::bail!(\"shard {idx} has no pageservers\"); }\n}","typeGuard":"fn shard_map_complete(conninfo: &PageserverConnectionInfo) -> bool {\n    let n = if conninfo.shard_count.0 == 0 { 1 } else { conninfo.shard_count.0 };\n    (0..n).all(|i| {\n        let idx = ShardIndex { shard_number: ShardNumber(i), shard_count: conninfo.shard_count };\n        conninfo.shards.get(&idx).map(|s| !s.pageservers.is_empty()).unwrap_or(false)\n    })\n}","tryCatchPattern":"// Config error at startup: surface which shard is missing instead of retrying\nif let Some(missing) = (0..n).map(|i| /* build idx */).find(|idx| !conninfo.shards.contains_key(idx)) {\n    return Err(anyhow!(\"incomplete shard map: missing shard {missing} of {}\", conninfo.shard_count));\n}","preventionTips":["Have the control plane construct shard maps and shard_count in one function so they cannot diverge","After resharding, verify 0..shard_count all present before publishing the connstring GUC","Replace the sibling .expect(\"must have at least one pageserver\") with a checked error too - it panics on the same malformed input"],"tags":["rust","compute-ctl","pageserver","sharding","configuration","spec","startup"],"backgroundTag":"configuration-mismatch","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}