neondatabase/neon · error
must have at least one pageserver
Error message
must have at least one pageserver
What it means
After shard_url finds the shard's entry, it takes the first pageserver in that shard's pageservers list as the connection target. If the list is empty there is nothing to connect to, and this error fires. The docs note multi-pageserver shards are rare today, so in practice the list is built with exactly one element from the legacy connstr path.
Source
Thrown at libs/compute_api/src/spec.rs:342
shard_number: ShardNumber,
protocol: PageserverProtocol,
) -> anyhow::Result<&str> {
let shard_index = ShardIndex {
shard_number,
shard_count: self.shard_count,
};
let shard = self.shards.get(&shard_index).ok_or_else(|| {
anyhow::anyhow!("shard connection info missing for shard {}", shard_index)
})?;
// Just use the first pageserver in the list. That's good enough for this
// convenience routine; if you need more control, like round robin policy or
// failover support, roll your own. (As of this writing, we never have more than
// one pageserver per shard anyway, but that will change in the future.)
let pageserver = shard
.pageservers
.first()
.ok_or(anyhow::anyhow!("must have at least one pageserver"))?;
let result = match protocol {
PageserverProtocol::Grpc => pageserver
.grpc_url
.as_ref()
.ok_or(anyhow::anyhow!("no grpc_url for shard {shard_index}"))?,
PageserverProtocol::Libpq => pageserver
.libpq_url
.as_ref()
.ok_or(anyhow::anyhow!("no libpq_url for shard {shard_index}"))?,
};
Ok(result)
}
}
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct PageserverShardInfo {
pub pageservers: Vec<PageserverShardConnectionInfo>,View on GitHub (pinned to 8f60b04da4)
Solutions
- Fix the source spec so every shard in pageserver_connection_info has at least one pageserver entry (with libpq_url and/or grpc_url set)
- Validate the parsed info after deserialization: every shard in 0..shard_count exists and has a non-empty pageservers list
- If generating specs programmatically, assert on construction that pageservers is never empty
Example fix
// before
shards: { shard_idx: PageserverShardInfo { pageservers: vec![], ... } }
// after
shards: { shard_idx: PageserverShardInfo { pageservers: vec![PageserverShardConnectionInfo { id: None, libpq_url: Some(url), grpc_url: None }], ... } } Defensive patterns
Strategy: validation
Validate before calling
fn validate(info: &PageserverConnectionInfo) -> anyhow::Result<()> {
for (idx, shard) in &info.shards {
anyhow::ensure!(!shard.pageservers.is_empty(),
"shard {idx} has no pageservers");
for ps in &shard.pageservers {
anyhow::ensure!(ps.libpq_url.is_some() || ps.grpc_url.is_some(),
"shard {idx} pageserver has no URL");
}
}
Ok(())
} Type guard
fn shard_has_pageserver(info: &PageserverConnectionInfo, idx: ShardIndex) -> bool {
info.shards.get(&idx).map(|s| !s.pageservers.is_empty()).unwrap_or(false)
} Prevention
- Validate deserialized pageserver_connection_info in one place before use
- In spec generators, push at least one connection entry per shard unconditionally
- Prefer libpq_url plus grpc_url both set so protocol preference never starves
When it happens
Trigger: A PageserverShardInfo whose pageservers Vec is empty — possible when pageserver_connection_info JSON is hand-authored or generated with an empty pageservers array for some shard — and shard_url is called for that shard.
Common situations: Manually editing or generating ComputeSpec pageserver_connection_info and leaving a shard's pageservers empty; programmatic builders that skip pushing connection info under a branch; partial JSON merge dropping shard entries' contents.
Related errors
- shard {shard_index} missing from pageserver_connection_info
- stripe size must be given for sharded tenants
- stripe size can't be given for unsharded tenants
- pageserver connection information should be provided
- {n} shards but no stripe_size
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/c952e667dc70c041.
Report an issue: GitHub.