mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
db health page improvements (#8631)
* db health page improvements * Update SQLx metadata --------- Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
windmill-internal-app[bot]
parent
ef1757f5d7
commit
c41e3adcc6
+3
-3
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) as cnt FROM pg_stat_activity WHERE state = 'active'",
|
||||
"query": "SELECT setting::bigint as \"max!\" FROM pg_settings WHERE name = 'max_connections'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "cnt",
|
||||
"name": "max!",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
@@ -16,5 +16,5 @@
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "87d07998fe8373f5b89eccf6f0528c02e389bf827d935d867430ad3459104dd9"
|
||||
"hash": "07770a002a49428c4f956cfc7262d6b6792ae5b97ed90b0ee07d17480b2dffe2"
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n COUNT(*) as \"total!\",\n COUNT(*) FILTER (WHERE state = 'active') as \"active!\",\n COUNT(*) FILTER (WHERE state = 'idle') as \"idle!\"\n FROM pg_stat_activity\n WHERE backend_type = 'client backend'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "total!",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "active!",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "idle!",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "2d95191e899d60385b32f36f2e38137e4173a34c54344ee522745640d48b8813"
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n table_name as \"table_name!\",\n SUM(live_tuples)::bigint as \"live_tuples!\",\n SUM(dead_tuples)::bigint as \"dead_tuples!\",\n MAX(last_autovacuum) as \"last_autovacuum\",\n MAX(last_autoanalyze) as \"last_autoanalyze\"\n FROM (\n SELECT\n CASE\n WHEN i.inhparent IS NOT NULL THEN schemaname || '.' || p.relname\n ELSE schemaname || '.' || s.relname\n END as table_name,\n COALESCE(n_live_tup, 0) as live_tuples,\n COALESCE(n_dead_tup, 0) as dead_tuples,\n last_autovacuum,\n last_autoanalyze\n FROM pg_stat_user_tables s\n LEFT JOIN pg_class c ON c.relname = s.relname AND c.relnamespace = (\n SELECT oid FROM pg_namespace WHERE nspname = s.schemaname\n )\n LEFT JOIN pg_inherits i ON i.inhrelid = c.oid\n LEFT JOIN pg_class p ON p.oid = i.inhparent\n ) sub\n GROUP BY table_name\n ORDER BY SUM(dead_tuples) DESC",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "table_name!",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "live_tuples!",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "dead_tuples!",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "last_autovacuum",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "last_autoanalyze",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "30930bfb0513f1a70194a900011b2e890bc4146bb0419210cd76743cacda8bfa"
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
|
||||
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n schemaname || '.' || relname as \"table_name!\",\n COALESCE(n_live_tup, 0) as \"live_tuples!\",\n COALESCE(n_dead_tup, 0) as \"dead_tuples!\",\n last_autovacuum as \"last_autovacuum\",\n last_autoanalyze as \"last_autoanalyze\"\n FROM pg_stat_user_tables\n ORDER BY n_dead_tup DESC\n LIMIT 15",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "table_name!",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "live_tuples!",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "dead_tuples!",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "last_autovacuum",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "last_autoanalyze",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "bc54ea311580a0525c1f36aaa543c5798e6f7aca1e6e564330766d77038ef0e3"
|
||||
}
|
||||
@@ -12,8 +12,6 @@ use serde::{Deserialize, Serialize};
|
||||
use windmill_common::error::JsonResult;
|
||||
|
||||
use crate::db::{ApiAuthed, DB};
|
||||
use crate::health::get_pool_stats;
|
||||
use crate::health::PoolStats;
|
||||
use crate::utils::require_super_admin;
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
@@ -81,8 +79,10 @@ pub struct LargeResultRow {
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ConnectionPoolInfo {
|
||||
pub pool: PoolStats,
|
||||
pub pg_max_connections: i64,
|
||||
pub pg_total_connections: i64,
|
||||
pub pg_active_connections: i64,
|
||||
pub pg_idle_connections: i64,
|
||||
pub status: HealthLevel,
|
||||
pub message: String,
|
||||
}
|
||||
@@ -307,16 +307,30 @@ async fn fetch_large_results(
|
||||
}
|
||||
|
||||
async fn fetch_connection_pool(db: &DB) -> windmill_common::error::Result<ConnectionPoolInfo> {
|
||||
let pool = get_pool_stats(db);
|
||||
let max_row = sqlx::query_scalar!(
|
||||
r#"SELECT setting::bigint as "max!" FROM pg_settings WHERE name = 'max_connections'"#
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
|
||||
let active_row =
|
||||
sqlx::query!("SELECT COUNT(*) as cnt FROM pg_stat_activity WHERE state = 'active'")
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
let stats_row = sqlx::query!(
|
||||
r#"SELECT
|
||||
COUNT(*) as "total!",
|
||||
COUNT(*) FILTER (WHERE state = 'active') as "active!",
|
||||
COUNT(*) FILTER (WHERE state = 'idle') as "idle!"
|
||||
FROM pg_stat_activity
|
||||
WHERE backend_type = 'client backend'"#
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
|
||||
let pg_active = active_row.cnt.unwrap_or(0);
|
||||
let utilization = if pool.max_connections > 0 {
|
||||
pool.size as f64 / pool.max_connections as f64
|
||||
let pg_max = max_row;
|
||||
let pg_total = stats_row.total;
|
||||
let pg_active = stats_row.active;
|
||||
let pg_idle = stats_row.idle;
|
||||
|
||||
let utilization = if pg_max > 0 {
|
||||
pg_total as f64 / pg_max as f64
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
@@ -325,50 +339,75 @@ async fn fetch_connection_pool(db: &DB) -> windmill_common::error::Result<Connec
|
||||
(
|
||||
HealthLevel::Green,
|
||||
format!(
|
||||
"Pool utilization: {:.0}% ({}/{})",
|
||||
"Connection utilization: {:.0}% ({}/{})",
|
||||
utilization * 100.0,
|
||||
pool.size,
|
||||
pool.max_connections
|
||||
pg_total,
|
||||
pg_max
|
||||
),
|
||||
)
|
||||
} else if utilization < 0.95 {
|
||||
(
|
||||
HealthLevel::Yellow,
|
||||
format!(
|
||||
"Pool utilization is high: {:.0}% ({}/{}). Consider increasing max_connections.",
|
||||
"Connection utilization is high: {:.0}% ({}/{}). Consider increasing max_connections.",
|
||||
utilization * 100.0,
|
||||
pool.size,
|
||||
pool.max_connections
|
||||
pg_total,
|
||||
pg_max
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
HealthLevel::Red,
|
||||
format!(
|
||||
"Pool near exhaustion: {:.0}% ({}/{}). Increase max_connections urgently.",
|
||||
"Connections near exhaustion: {:.0}% ({}/{}). Increase max_connections urgently.",
|
||||
utilization * 100.0,
|
||||
pool.size,
|
||||
pool.max_connections
|
||||
pg_total,
|
||||
pg_max
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
Ok(ConnectionPoolInfo { pool, pg_active_connections: pg_active, status, message })
|
||||
Ok(ConnectionPoolInfo {
|
||||
pg_max_connections: pg_max,
|
||||
pg_total_connections: pg_total,
|
||||
pg_active_connections: pg_active,
|
||||
pg_idle_connections: pg_idle,
|
||||
status,
|
||||
message,
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_table_maintenance(
|
||||
db: &DB,
|
||||
) -> windmill_common::error::Result<Vec<TableMaintenanceInfo>> {
|
||||
// Aggregate partitioned tables (e.g. audit_YYYYMMDD -> audit_partitioned)
|
||||
// while keeping non-partitioned tables as-is
|
||||
let rows = sqlx::query!(
|
||||
r#"SELECT
|
||||
schemaname || '.' || relname as "table_name!",
|
||||
COALESCE(n_live_tup, 0) as "live_tuples!",
|
||||
COALESCE(n_dead_tup, 0) as "dead_tuples!",
|
||||
last_autovacuum as "last_autovacuum",
|
||||
last_autoanalyze as "last_autoanalyze"
|
||||
FROM pg_stat_user_tables
|
||||
ORDER BY n_dead_tup DESC
|
||||
LIMIT 15"#
|
||||
table_name as "table_name!",
|
||||
SUM(live_tuples)::bigint as "live_tuples!",
|
||||
SUM(dead_tuples)::bigint as "dead_tuples!",
|
||||
MAX(last_autovacuum) as "last_autovacuum",
|
||||
MAX(last_autoanalyze) as "last_autoanalyze"
|
||||
FROM (
|
||||
SELECT
|
||||
CASE
|
||||
WHEN i.inhparent IS NOT NULL THEN schemaname || '.' || p.relname
|
||||
ELSE schemaname || '.' || s.relname
|
||||
END as table_name,
|
||||
COALESCE(n_live_tup, 0) as live_tuples,
|
||||
COALESCE(n_dead_tup, 0) as dead_tuples,
|
||||
last_autovacuum,
|
||||
last_autoanalyze
|
||||
FROM pg_stat_user_tables s
|
||||
LEFT JOIN pg_class c ON c.relname = s.relname AND c.relnamespace = (
|
||||
SELECT oid FROM pg_namespace WHERE nspname = s.schemaname
|
||||
)
|
||||
LEFT JOIN pg_inherits i ON i.inhrelid = c.oid
|
||||
LEFT JOIN pg_class p ON p.oid = i.inhparent
|
||||
) sub
|
||||
GROUP BY table_name
|
||||
ORDER BY SUM(dead_tuples) DESC"#
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await?;
|
||||
|
||||
@@ -267,7 +267,13 @@
|
||||
<tbody>
|
||||
{#each data.large_results.top_large_results as r}
|
||||
<tr class="border-surface-secondary border-b last:border-0">
|
||||
<td class="text-primary py-1 pr-4 font-mono">{r.id.substring(0, 8)}...</td>
|
||||
<td class="text-primary py-1 pr-4 font-mono"
|
||||
><a
|
||||
href="/run/{r.id}?workspace={r.workspace_id}"
|
||||
class="text-blue-600 hover:underline dark:text-blue-400"
|
||||
>{r.id.substring(0, 8)}...</a
|
||||
></td
|
||||
>
|
||||
<td class="text-secondary py-1 pr-4">{r.workspace_id}</td>
|
||||
<td class="text-secondary py-1 pr-4 font-mono">{r.runnable_path ?? '-'}</td>
|
||||
<td class="text-secondary py-1 pr-4 text-right"
|
||||
@@ -292,7 +298,7 @@
|
||||
class="flex w-full items-center justify-between p-3 text-left hover:bg-surface-secondary/50"
|
||||
onclick={() => toggleSection('connection_pool')}
|
||||
>
|
||||
<h3 class="text-primary text-sm font-semibold">Connection Pool</h3>
|
||||
<h3 class="text-primary text-sm font-semibold">Database Connections</h3>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="rounded px-1.5 py-0.5 text-xs font-medium {statusBadge(
|
||||
@@ -311,12 +317,12 @@
|
||||
{#if expandedSections.connection_pool}
|
||||
<div class="border-surface-secondary flex flex-col gap-1 border-t p-3 text-xs">
|
||||
<p class="text-secondary">
|
||||
Pool size: <strong>{data.connection_pool.pool.size}</strong> / Max:
|
||||
<strong>{data.connection_pool.pool.max_connections}</strong>
|
||||
/ Idle: <strong>{data.connection_pool.pool.idle}</strong>
|
||||
Total connections: <strong>{data.connection_pool.pg_total_connections}</strong> / Max:
|
||||
<strong>{data.connection_pool.pg_max_connections}</strong>
|
||||
</p>
|
||||
<p class="text-secondary">
|
||||
Active PG connections: <strong>{data.connection_pool.pg_active_connections}</strong>
|
||||
Active: <strong>{data.connection_pool.pg_active_connections}</strong>
|
||||
/ Idle: <strong>{data.connection_pool.pg_idle_connections}</strong>
|
||||
</p>
|
||||
<p class="{statusColor(data.connection_pool.status)} mt-1 font-medium">
|
||||
{data.connection_pool.message}
|
||||
|
||||
Reference in New Issue
Block a user