neondatabase/neon · error

could not get total_sessions: {}

Error message

could not get total_sessions: {}

What it means

The final column conversion in get_database_stats(): stats.try_get("total_sessions") to i64 failed because the 'total_sessions' column is not the BIGINT/INT8 the Rust side expects. The query intentionally casts to ::pg_catalog.bigint, so this error signals query/type drift rather than a bad value. It propagates as a check() error (downtime + reconnect).

Source

Thrown at compute_tools/src/monitor.rs:434

                'template1'
            );",
        &[],
    );
    let stats = match stats {
        Ok(stats) => stats,
        Err(e) => {
            return Err(anyhow::anyhow!("could not query active_time: {}", e));
        }
    };

    let active_time: f64 = match stats.try_get("total_active_time") {
        Ok(active_time) => active_time,
        Err(e) => return Err(anyhow::anyhow!("could not get total_active_time: {}", e)),
    };

    let sessions: i64 = match stats.try_get("total_sessions") {
        Ok(sessions) => sessions,
        Err(e) => return Err(anyhow::anyhow!("could not get total_sessions: {}", e)),
    };

    Ok((active_time, sessions))
}

// Figure out the most recent state change time across all client backends.
// If there is currently active backend, timestamp will be `Utc::now()`.
// It can return `None`, which means no client backends exist or we were
// unable to parse the timestamp.
fn get_backends_state_change(cli: &mut Client) -> anyhow::Result<Option<DateTime<Utc>>> {
    let mut last_active: Option<DateTime<Utc>> = None;
    // Get all running client backends except ourself, use RFC3339 DateTime format.
    let backends = cli.query(
        "SELECT state, pg_catalog.to_char(state_change, 'YYYY-MM-DD\"T\"HH24:MI:SS.US\"Z\"'::pg_catalog.text) AS state_change
                FROM pg_stat_activity
                    WHERE backend_type OPERATOR(pg_catalog.=) 'client backend'::pg_catalog.text
                    AND pid OPERATOR(pg_catalog.!=) pg_catalog.pg_backend_pid()
                    AND usename OPERATOR(pg_catalog.!=) 'cloud_admin'::pg_catalog.name;", // XXX: find a better way to filter other monitors?

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Restore the explicit cast: coalesce(sum(sessions), 0)::pg_catalog.bigint AS total_sessions
  2. Read the inner FromSql error for the concrete type mismatch
  3. Confirm the 'total_sessions' alias still exists in the query
  4. Add a regression test asserting the (f64, i64) tuple round-trips

Example fix

// before
"pg_catalog.coalesce(pg_catalog.sum(sessions), 0) AS total_sessions"

// after
"pg_catalog.coalesce(pg_catalog.sum(sessions), 0)::pg_catalog.bigint AS total_sessions"
Defensive patterns

Strategy: validation

Validate before calling

// keep the explicit cast so the column is always BIGINT on the wire
const GET_DB_STATS_SQL: &str = "... pg_catalog.coalesce(pg_catalog.sum(sessions), 0)::pg_catalog.bigint AS total_sessions ...";

Type guard

fn col_is_int8(row: &postgres::Row, col: &str) -> bool {
    row.columns()
        .iter()
        .any(|c| c.name() == col && *c.type_() == postgres::types::Type::INT8)
}

Try / catch

let sessions: i64 = match stats.try_get("total_sessions") {
    Ok(v) => v,
    Err(e) => {
        warn!("total_sessions unreadable, skipping stats check: {e}");
        return Ok(());
    }
};

Prevention

When it happens

Trigger: Column 'total_sessions' not of SQL type INT8: cast removed in a query edit, alias renamed, or a server returning a different type for sum(sessions).

Common situations: Editing monitor SQL without updating conversions; Postgres version changes in type inference for sum() over pg_stat_database columns.

Related errors


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