mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
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:
1 parent
2b27ed0ab1
commit
fb7debec25
2 files changed
+169
-91
No files matched your search
+2
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user