neondatabase/neon · error
could not get backends state change: {}
Error message
could not get backends state change: {} What it means
ComputeMonitor::check calls get_backends_state_change(), which SELECTs state and to_char(state_change, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') FROM pg_stat_activity for every client backend except the monitor's own pid and the cloud_admin user. This error means that query itself returned Err, so the monitor could not compute the most-recent idle state-change timestamp used in the suspend decision. As with all check() failures, the monitor logs it with downtime info, reports downtime, and reconnects for the next tick.
Source
Thrown at compute_tools/src/monitor.rs:264
// can return None or stale timestamp, so it's `compute.update_last_active()`
// responsibility to check if the new timestamp is more recent than the current one.
// This helps us to discover new sessions that have not done anything yet.
match get_backends_state_change(cli) {
Ok(last_active) => match (last_active, self.last_active) {
(Some(last_active), Some(prev_last_active)) => {
if last_active > prev_last_active {
self.last_active = Some(last_active);
return Ok(());
}
}
(Some(last_active), None) => {
self.last_active = Some(last_active);
return Ok(());
}
_ => {}
},
Err(e) => {
return Err(anyhow::anyhow!(
"could not get backends state change: {}",
e
));
}
}
// If there are existing (logical) walsenders, do not suspend.
//
// N.B. walproposer doesn't currently show up in pg_stat_replication,
// but protect if it will.
const WS_COUNT_QUERY: &str =
"select count(*) from pg_stat_replication where application_name != 'walproposer';";
match cli.query_one(WS_COUNT_QUERY, &[]) {
Ok(r) => match r.try_get::<&str, i64>("count") {
Ok(num_ws) => {
if num_ws > 0 {
self.last_active = Some(Utc::now());
return Ok(());View on GitHub (pinned to 8f60b04da4)
Solutions
- Inspect the appended source error (e) for the tokio_postgres cause and SQLSTATE
- Confirm Postgres is up and the compute is in Running state; the monitor will self-heal by reconnecting next iteration
- If errors persist every 500ms, check Postgres logs for crashes/restarts and network issues inside the compute container
- Treat consecutive errors, not single events, as actionable downtime signal
Defensive patterns
Strategy: retry
Try / catch
match get_backends_state_change(cli) {
Ok(last_active) => { /* compare and update self.last_active */ }
Err(e) => {
error!("could not get backends state change: {e}");
client = conf.connect(NoTls); // retry on next tick
}
} Prevention
- Expect single failures around suspend/resume and restarts; monitor error streaks instead
- Keep cloud_admin grants on pg_stat_activity intact
- Watch the PG_CURR_DOWNTIME_MS metric rather than parsing individual log lines
When it happens
Trigger: cli.query() over pg_stat_activity fails: broken connection, statement timeout, Postgres restarting, or the stats view being temporarily unavailable while the server changes state.
Common situations: Compute suspend/resume transitions racing the 500ms monitor poll; long lock waits on pg_stat_activity under heavy load; monitor thread surviving a Postgres crash by design and logging this each tick until Postgres returns.
Related errors
- could not query backends: {}
- could not get database statistics: {}
- failed to get list of autovacuum workers: {}
- could not query active_time: {}
- connection to postgres closed
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/22f9bf4782465dcc.
Report an issue: GitHub.