neondatabase/neon · error

invalid connstring: {err}

Error message

invalid connstring: {err}

What it means

pagebench's basebackup command parses --page-service-connstring with Url::parse. The case RelativeUrlWithoutBase (a bare string with no scheme) is tolerated and defaults to the postgresql transport, but any other parse failure returns 'invalid connstring: {err}' with the url crate's reason (invalid port number, invalid IPv6 literal, invalid domain character, empty host). The string never reaches the pageserver, so this is purely client-side argument validation.

Source

Thrown at pageserver/pagebench/src/cmd/basebackup.rs:162

            loop {
                let start = std::time::Instant::now();
                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
                let completed_requests = stats.completed_requests.swap(0, Ordering::Relaxed);
                let elapsed = start.elapsed();
                info!(
                    "RPS: {:.0}",
                    completed_requests as f64 / elapsed.as_secs_f64()
                );
            }
        }
    });

    let mut work_senders = HashMap::new();
    let mut tasks = Vec::new();
    let scheme = match Url::parse(&args.page_service_connstring) {
        Ok(url) => url.scheme().to_lowercase().to_string(),
        Err(url::ParseError::RelativeUrlWithoutBase) => "postgresql".to_string(),
        Err(err) => return Err(anyhow!("invalid connstring: {err}")),
    };
    for &tl in &timelines {
        let (sender, receiver) = tokio::sync::mpsc::channel(1); // TODO: not sure what the implications of this are
        work_senders.insert(tl, sender);

        let client: Box<dyn Client> = match scheme.as_str() {
            "postgresql" | "postgres" => Box::new(
                LibpqClient::new(&args.page_service_connstring, tl, !args.no_compression).await?,
            ),
            "grpc" => Box::new(
                GrpcClient::new(&args.page_service_connstring, tl, !args.no_compression).await?,
            ),
            scheme => return Err(anyhow!("invalid scheme {scheme}")),
        };

        tasks.push(tokio::spawn(run_worker(
            client,
            Arc::clone(&start_work_barrier),

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Rewrite the connstring in full URL form: postgresql://user@host:port for the libpq page protocol or grpc://host:port for the gRPC transport
  2. Check that the port is numeric and within 0-65535 and the host contains no spaces or placeholder characters
  3. Shell-quote the argument and echo it back to spot interpolation problems
  4. If you meant a bare host:port, note that only RelativeUrlWithoutBase falls back to postgresql; most partial strings fail parsing or hit the separate 'invalid scheme' error, so always pass the explicit scheme

Example fix

# before
pagebench basebackup --page-service-connstring 'postgres://localhost:notaport'
# after
pagebench basebackup --page-service-connstring 'postgres://localhost:6400'
Defensive patterns

Strategy: validation

Validate before calling

fn validate_connstring(s: &str) -> anyhow::Result<String> {
    match url::Url::parse(s) {
        Ok(u) => Ok(u.scheme().to_lowercase()),
        Err(url::ParseError::RelativeUrlWithoutBase) => Ok("postgresql".to_string()),
        Err(e) => anyhow::bail!("invalid connstring: {e}"),
    }
}

// run before starting the bench
let scheme = validate_connstring(&args.page_service_connstring)?;

Type guard

fn is_parseable_connstring(s: &str) -> bool {
    matches!(
        url::Url::parse(s),
        Ok(_) | Err(url::ParseError::RelativeUrlWithoutBase)
    )
}

Try / catch

match url::Url::parse(&args.page_service_connstring) {
    Ok(url) => url.scheme().to_lowercase(),
    Err(url::ParseError::RelativeUrlWithoutBase) => "postgresql".to_string(),
    Err(err) => {
        eprintln!("fix the --page-service-connstring value: {err}");
        return Err(anyhow!("invalid connstring: {err}"));
    }
}

Prevention

When it happens

Trigger: Running pagebench basebackup with a connstring containing URL syntax errors: non-numeric or out-of-range port (postgres://host:99999), an IPv6 literal without brackets (postgres://::1:6400), spaces or control characters, bad percent-encoding, or a truncated URL such as 'postgres://' with no host.

Common situations: Typos when copying the page_service endpoint out of configs or logs; shell scripts interpolating empty or unquoted variables; documentation placeholders like postgresql://user@<host>:6400 left in the string.

Related errors


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