From fb7debec2585a42fc5c3785928801011e310975e Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 27 Jul 2026 11:08:57 +0800 Subject: [PATCH] Add top blocker overview card (#48290) ## Context Adds a "Top blocker" overview card for Database Connections This should provide a better signal if there's any process that's behaving as a bottleneck for multiple blocked queries image ^ We only highlight the card in red if the query is blocking more than 3 other queries to account - otherwise the signal might be too noisy image ## Other changes involved - Am swapping the card positions around a little - Longest running query card shows the PID as the primary information, followed by the duration of the run ## Summary by CodeRabbit * **New Features** * Added a **Top blocker** metric to the Database Connections Overview to highlight the PID/account blocking the most other queries. * Warning styling now appears when a query blocks more than **3** other queries. * Reorganized the metrics layout and ordering for improved visibility (active, idle-in-transaction, blocked, top blocker, and longest running). * **Bug Fixes** * Updated tooltip and guidance text for clearer explanations of blocked and idle-in-transaction states. --- .../DatabaseConnections.constants.ts | 2 + .../DatabaseConnections/Overview.tsx | 258 ++++++++++++------ 2 files changed, 169 insertions(+), 91 deletions(-) diff --git a/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.constants.ts b/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.constants.ts index be3a075cdc1..ab07cd26b47 100644 --- a/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.constants.ts +++ b/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.constants.ts @@ -6,6 +6,8 @@ export const WARN_DURATION_ACTIVE_QUERY = 30 // seconds export const WARN_DURATION_IDLE_TXN = 10 // seconds +export const WARN_TOP_BLOCKER = 3 // If the query is blocking more than 3 queries + export const QUERY_STATE_TOOLTIP = { ['active']: 'Currently executing a query.', ['idle']: 'Connected, but not currently running a query.', diff --git a/apps/studio/components/interfaces/Observability/DatabaseConnections/Overview.tsx b/apps/studio/components/interfaces/Observability/DatabaseConnections/Overview.tsx index d03ee79e09f..c7c64e60576 100644 --- a/apps/studio/components/interfaces/Observability/DatabaseConnections/Overview.tsx +++ b/apps/studio/components/interfaces/Observability/DatabaseConnections/Overview.tsx @@ -9,7 +9,11 @@ import { MetricCardValue, } from 'ui-patterns/MetricCard' -import { WARN_DURATION_ACTIVE_QUERY, WARN_DURATION_IDLE_TXN } from './DatabaseConnections.constants' +import { + WARN_DURATION_ACTIVE_QUERY, + WARN_DURATION_IDLE_TXN, + WARN_TOP_BLOCKER, +} from './DatabaseConnections.constants' import { formatDuration } from '@/components/interfaces/QueryPerformance/QueryPerformance.utils' import { useDatabaseRolesQuery } from '@/data/database-roles/database-roles-query' import { useDatabaseActivityQuery, type DatabaseActivity } from '@/data/database/activity-query' @@ -26,14 +30,6 @@ interface OverviewProps { live?: boolean } -/** - * [Joshen] Couple of nuances worth calling out to provide better signals for the user - * - Idle in transaction: - * - Only considers queries in that state, but running for longer than 10 seconds - * - Could otherwise be a query in mid-flight - * - Longest running: - * - Only considers queries that are active or idle in transaction - */ export const Overview = ({ live }: OverviewProps) => { const { data: project } = useSelectedProjectQuery() const [, setSelectedPid] = useQueryState('pid', parseAsInteger) @@ -69,6 +65,22 @@ export const Overview = ({ live }: OverviewProps) => { longestRunningQuery?.activity.state === 'idle in transaction (aborted)') && longestRunningQuery.duration >= WARN_DURATION_IDLE_TXN) + const blockingCounts = (data ?? []).reduce>((counts, activity) => { + activity.blocked_by.forEach((pid) => counts.set(pid, (counts.get(pid) ?? 0) + 1)) + return counts + }, new Map()) + + const queryBlockingTheMostQueries = [...blockingCounts].reduce<{ + activity: DatabaseActivity + count: number + } | null>((mostBlocking, [pid, count]) => { + if (mostBlocking && count <= mostBlocking.count) return mostBlocking + const activity = (data ?? []).find((x) => x.pid === pid) + return activity ? { activity, count } : mostBlocking + }, null) + + const warnTopBlocker = (queryBlockingTheMostQueries?.count ?? 0) >= WARN_TOP_BLOCKER + const { data: roles, isPending: isLoadingRoles } = useDatabaseRolesQuery( { projectRef: project?.ref, @@ -99,7 +111,7 @@ export const Overview = ({ live }: OverviewProps) => {
-
+
{ - - - - Longest running - - - - - {longestRunningQuery === null ? ( - '-' - ) : ( - <> - {formatDuration(longestRunningQuery.duration * 1000, 0)} - · - setSelectedPid(longestRunningQuery.activity.pid)} - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault() - setSelectedPid(longestRunningQuery.activity.pid) - } - }} - > - PID: {longestRunningQuery.activity.pid} - - - )} - - - -
- -
@@ -188,36 +149,6 @@ export const Overview = ({ live }: OverviewProps) => { - - - -

- Queries waiting on a lock held by another session - stalls everything queued - behind it. -

-

- Typically caused by an uncommitted transaction, a long-running migration, or a - stuck idle-in-transaction session. -

- - } - > - Blocked queries -
-
- - - {blockedQueries.length} - - -
- { + + + + +

+ Queries waiting on a lock held by another session - stalls everything queued + behind it. +

+

+ Typically caused by a slow transaction, a long-running migration, or a stuck + idle-in-transaction session. +

+ + } + > + Blocked queries +
+
+ + + {blockedQueries.length} + + +
+ + + + + Top blocker + + + + + {queryBlockingTheMostQueries === null ? ( + '-' + ) : ( + <> + setSelectedPid(queryBlockingTheMostQueries.activity.pid)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + setSelectedPid(queryBlockingTheMostQueries.activity.pid) + } + }} + > + PID: {queryBlockingTheMostQueries?.activity.pid} + + · + + Blocking {queryBlockingTheMostQueries?.count} other{' '} + {queryBlockingTheMostQueries?.count > 1 ? 'queries' : 'query'} + + + )} + + + + + + + + Longest running + + + + + {longestRunningQuery === null ? ( + '-' + ) : ( + <> + setSelectedPid(longestRunningQuery.activity.pid)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + setSelectedPid(longestRunningQuery.activity.pid) + } + }} + > + PID: {longestRunningQuery.activity.pid} + + · + + {formatDuration(longestRunningQuery.duration * 1000, 0)} + + + )} + + +