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

  1. 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)
  2. Validate the parsed info after deserialization: every shard in 0..shard_count exists and has a non-empty pageservers list
  3. 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

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


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/c952e667dc70c041. Report an issue: GitHub.