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
<img width="977" height="256" alt="image"
src="https://github.com/user-attachments/assets/3d5129a8-f0d7-40a6-808e-0d902889d997"
/>

^ 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

<img width="965" height="262" alt="image"
src="https://github.com/user-attachments/assets/f3fda77a-8d52-4e5d-8717-66a26f511a3c"
/>

## 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



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## 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.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Joshen Lim authored and GitHub committed 2026-07-27 11:08:57 +08:00
1 parent 2b27ed0ab1
commit fb7debec25
2 files changed
+169 -91

No files matched your search

@@ -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.',
@@ -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<Map<number, number>>((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) => {
</div>
<div className="flex flex-col gap-y-2">
<div className="grid grid-cols-2 gap-2">
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
<MetricCard isLoading={isLoadingRoles || isLoadingMaxConnections}>
<MetricCardHeader>
<MetricCardLabel
@@ -126,57 +138,6 @@ export const Overview = ({ live }: OverviewProps) => {
</MetricCardContent>
</MetricCard>
<MetricCard
isLoading={isLoadingActivity}
className={cn(warnLongestRunningQuery && 'bg-warning-200 border-warning-400')}
>
<MetricCardHeader>
<MetricCardLabel
className={cn(warnLongestRunningQuery && 'text-foreground')}
tooltip="Only considers active or idle-in-transaction queries"
>
Longest running
</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue
className={cn(
'space-x-2',
longestRunningQuery === null
? 'text-foreground-lighter'
: warnLongestRunningQuery
? 'text-warning'
: 'text-foreground'
)}
>
{longestRunningQuery === null ? (
'-'
) : (
<>
<span>{formatDuration(longestRunningQuery.duration * 1000, 0)}</span>
<span className="text-foreground-lighter text-sm">·</span>
<span
role="button"
tabIndex={0}
className="text-foreground-lighter text-sm hover:text-foreground transition cursor-pointer"
onClick={() => setSelectedPid(longestRunningQuery.activity.pid)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
setSelectedPid(longestRunningQuery.activity.pid)
}
}}
>
PID: {longestRunningQuery.activity.pid}
</span>
</>
)}
</MetricCardValue>
</MetricCardContent>
</MetricCard>
</div>
<div className="grid grid-cols-2 gap-2 lg:grid-cols-3">
<MetricCard isLoading={isLoadingActivity}>
<MetricCardHeader>
<MetricCardLabel tooltip="Queries currently executing on the database.">
@@ -188,36 +149,6 @@ export const Overview = ({ live }: OverviewProps) => {
</MetricCardContent>
</MetricCard>
<MetricCard
isLoading={isLoadingActivity}
className={cn(blockedQueries.length && 'bg-destructive-200 border-destructive-400')}
>
<MetricCardHeader>
<MetricCardLabel
className={cn(blockedQueries.length && 'text-foreground')}
tooltip={
<>
<p>
Queries waiting on a lock held by another session - stalls everything queued
behind it.
</p>
<p className="mt-2">
Typically caused by an uncommitted transaction, a long-running migration, or a
stuck idle-in-transaction session.
</p>
</>
}
>
Blocked queries
</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue className={cn(blockedQueries.length && 'text-destructive')}>
{blockedQueries.length}
</MetricCardValue>
</MetricCardContent>
</MetricCard>
<MetricCard
isLoading={isLoadingActivity}
className={cn(idleInTransactionQueries.length && 'bg-warning-200 border-warning-400')}
@@ -248,6 +179,151 @@ export const Overview = ({ live }: OverviewProps) => {
</MetricCardValue>
</MetricCardContent>
</MetricCard>
<MetricCard
isLoading={isLoadingActivity}
className={cn(blockedQueries.length && 'bg-destructive-200 border-destructive-400')}
>
<MetricCardHeader>
<MetricCardLabel
className={cn(blockedQueries.length && 'text-foreground')}
tooltip={
<>
<p>
Queries waiting on a lock held by another session - stalls everything queued
behind it.
</p>
<p className="mt-2">
Typically caused by a slow transaction, a long-running migration, or a stuck
idle-in-transaction session.
</p>
</>
}
>
Blocked queries
</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue className={cn(blockedQueries.length && 'text-destructive')}>
{blockedQueries.length}
</MetricCardValue>
</MetricCardContent>
</MetricCard>
<MetricCard
isLoading={isLoadingActivity}
className={cn(warnTopBlocker && 'bg-destructive-200 border-destructive-400')}
>
<MetricCardHeader>
<MetricCardLabel
className={cn(warnTopBlocker && 'text-foreground')}
tooltip="The query blocking the most other queries"
>
Top blocker
</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue
className={cn(
'space-x-1',
queryBlockingTheMostQueries === null
? 'text-foreground-lighter'
: warnTopBlocker
? 'text-destructive'
: 'text-foreground'
)}
>
{queryBlockingTheMostQueries === null ? (
'-'
) : (
<>
<span
role="button"
tabIndex={0}
className="normal-nums cursor-pointer hover:underline"
onClick={() => setSelectedPid(queryBlockingTheMostQueries.activity.pid)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
setSelectedPid(queryBlockingTheMostQueries.activity.pid)
}
}}
>
PID: {queryBlockingTheMostQueries?.activity.pid}
</span>
<span className="text-foreground-light text-xs">·</span>
<span
className={cn(
'text-xs',
warnTopBlocker ? 'text-foreground-light' : 'text-foreground-lighter'
)}
>
Blocking {queryBlockingTheMostQueries?.count} other{' '}
{queryBlockingTheMostQueries?.count > 1 ? 'queries' : 'query'}
</span>
</>
)}
</MetricCardValue>
</MetricCardContent>
</MetricCard>
<MetricCard
isLoading={isLoadingActivity}
className={cn(warnLongestRunningQuery && 'bg-warning-200 border-warning-400')}
>
<MetricCardHeader>
<MetricCardLabel
className={cn(warnLongestRunningQuery && 'text-foreground')}
tooltip="Only considers active or idle-in-transaction queries"
>
Longest running
</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue
className={cn(
'space-x-1',
longestRunningQuery === null
? 'text-foreground-lighter'
: warnLongestRunningQuery
? 'text-warning'
: 'text-foreground'
)}
>
{longestRunningQuery === null ? (
'-'
) : (
<>
<span
role="button"
tabIndex={0}
className="normal-nums hover:underline cursor-pointer"
onClick={() => setSelectedPid(longestRunningQuery.activity.pid)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
setSelectedPid(longestRunningQuery.activity.pid)
}
}}
>
PID: {longestRunningQuery.activity.pid}
</span>
<span className="text-foreground-lighter text-sm">·</span>
<span
className={cn(
'text-sm',
warnLongestRunningQuery
? 'text-foreground-light'
: 'text-foreground-lighter'
)}
>
{formatDuration(longestRunningQuery.duration * 1000, 0)}
</span>
</>
)}
</MetricCardValue>
</MetricCardContent>
</MetricCard>
</div>
</div>
</div>