mirror of
https://github.com/neondatabase/neon.git
synced 2026-05-16 12:40:36 +00:00
## Problem There are several parts of `compute_ctl` with a very low visibility of errors: 1. DB migrations that run async in the background after compute start. 2. Requests made to control plane (currently only `GetSpec`). 3. Requests made to the remote extensions server. ## Summary of changes Add new counters to quickly evaluate the amount of errors among the fleet. Part of neondatabase/cloud#17590
102 lines
3.4 KiB
Rust
102 lines
3.4 KiB
Rust
use compute_api::responses::{InstalledExtension, InstalledExtensions};
|
|
use std::collections::HashMap;
|
|
|
|
use anyhow::Result;
|
|
use postgres::{Client, NoTls};
|
|
|
|
use crate::metrics::INSTALLED_EXTENSIONS;
|
|
|
|
/// We don't reuse get_existing_dbs() just for code clarity
|
|
/// and to make database listing query here more explicit.
|
|
///
|
|
/// Limit the number of databases to 500 to avoid excessive load.
|
|
fn list_dbs(client: &mut Client) -> Result<Vec<String>> {
|
|
// `pg_database.datconnlimit = -2` means that the database is in the
|
|
// invalid state
|
|
let databases = client
|
|
.query(
|
|
"SELECT datname FROM pg_catalog.pg_database
|
|
WHERE datallowconn
|
|
AND datconnlimit <> - 2
|
|
LIMIT 500",
|
|
&[],
|
|
)?
|
|
.iter()
|
|
.map(|row| {
|
|
let db: String = row.get("datname");
|
|
db
|
|
})
|
|
.collect();
|
|
|
|
Ok(databases)
|
|
}
|
|
|
|
/// Connect to every database (see list_dbs above) and get the list of installed extensions.
|
|
///
|
|
/// Same extension can be installed in multiple databases with different versions,
|
|
/// so we report a separate metric (number of databases where it is installed)
|
|
/// for each extension version.
|
|
pub fn get_installed_extensions(mut conf: postgres::config::Config) -> Result<InstalledExtensions> {
|
|
conf.application_name("compute_ctl:get_installed_extensions");
|
|
let mut client = conf.connect(NoTls)?;
|
|
let databases: Vec<String> = list_dbs(&mut client)?;
|
|
|
|
let mut extensions_map: HashMap<(String, String, String), InstalledExtension> = HashMap::new();
|
|
for db in databases.iter() {
|
|
conf.dbname(db);
|
|
let mut db_client = conf.connect(NoTls)?;
|
|
let extensions: Vec<(String, String, i32)> = db_client
|
|
.query(
|
|
"SELECT extname, extversion, extowner::integer FROM pg_catalog.pg_extension",
|
|
&[],
|
|
)?
|
|
.iter()
|
|
.map(|row| {
|
|
(
|
|
row.get("extname"),
|
|
row.get("extversion"),
|
|
row.get("extowner"),
|
|
)
|
|
})
|
|
.collect();
|
|
|
|
for (extname, v, extowner) in extensions.iter() {
|
|
let version = v.to_string();
|
|
|
|
// check if the extension is owned by superuser
|
|
// 10 is the oid of superuser
|
|
let owned_by_superuser = if *extowner == 10 { "1" } else { "0" };
|
|
|
|
extensions_map
|
|
.entry((
|
|
extname.to_string(),
|
|
version.clone(),
|
|
owned_by_superuser.to_string(),
|
|
))
|
|
.and_modify(|e| {
|
|
// count the number of databases where the extension is installed
|
|
e.n_databases += 1;
|
|
})
|
|
.or_insert(InstalledExtension {
|
|
extname: extname.to_string(),
|
|
version: version.clone(),
|
|
n_databases: 1,
|
|
owned_by_superuser: owned_by_superuser.to_string(),
|
|
});
|
|
}
|
|
}
|
|
|
|
for (key, ext) in extensions_map.iter() {
|
|
let (extname, version, owned_by_superuser) = key;
|
|
let n_databases = ext.n_databases as u64;
|
|
|
|
INSTALLED_EXTENSIONS
|
|
.with_label_values(&[extname, version, owned_by_superuser])
|
|
.set(n_databases);
|
|
}
|
|
|
|
Ok(InstalledExtensions {
|
|
extensions: extensions_map.into_values().collect(),
|
|
})
|
|
}
|