Blocked by card to only highlight if any query is blocked longer than 10 sec (#48292)

## Context

As per PR title - we're currently showing a "danger" state for the
blocked by metric card as long as there's at least query that's blocked.
This may come off as too noisy in a real database scenario hence opting
to fine tune this behaviour a little

## Changes involved
We'll now only show the "danger" state for the blocked by metric card if
any of the blocked queries are blocked for longer than 10 seconds

<img width="356" height="256" alt="image"
src="https://github.com/user-attachments/assets/e7db5d7e-749a-4c4e-b519-9433a639b0a0"
/>

Otherwise will just be a default card

<img width="359" height="266" alt="image"
src="https://github.com/user-attachments/assets/f9aad85f-8d74-4f55-a3c5-a859d46bd8c1"
/>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added clearer blocked-query monitoring, including the longest-blocked
process and duration.
  - Added interactive selection for the longest-blocked process.

- **Bug Fixes**
- Improved activity-duration tracking across active and
idle-in-transaction states.
- Blocked-query warnings now reflect duration thresholds rather than
query count alone.
  - Prevented negative duration values in blocked-query metrics.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Joshen Lim authored and GitHub committed 2026-07-27 13:02:55 +08:00
1 parent 4822687a64
commit cbb076ddf1
3 files changed
+61 -6

No files matched your search

@@ -5,7 +5,7 @@
// - Shorter threshold as it indicates a lock
export const WARN_DURATION_ACTIVE_QUERY = 30 // seconds
export const WARN_DURATION_IDLE_TXN = 10 // seconds
export const WARN_DURATION_BLOCKED = 10 // seconds
export const WARN_TOP_BLOCKER = 3 // If the query is blocking more than 3 queries
export const QUERY_STATE_TOOLTIP = {
@@ -25,3 +25,16 @@ export const getBadgeVariant = (activity: DatabaseActivity) => {
if (state === 'idle in transaction' || state === 'idle in transaction (aborted)') return 'warning'
return 'default'
}
// Resolves the timestamp that duration should be measured from for a given state -
// query_start while actively running, transaction_start while idle in transaction.
export const getActivityStart = (activity: DatabaseActivity) => {
if (activity.state === 'active') return activity.query_start
if (
activity.state === 'idle in transaction' ||
activity.state === 'idle in transaction (aborted)'
) {
return activity.transaction_start
}
return null
}
@@ -11,9 +11,11 @@ import {
import {
WARN_DURATION_ACTIVE_QUERY,
WARN_DURATION_BLOCKED,
WARN_DURATION_IDLE_TXN,
WARN_TOP_BLOCKER,
} from './DatabaseConnections.constants'
import { getActivityStart } from './DatabaseConnections.utils'
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'
@@ -42,7 +44,23 @@ export const Overview = ({ live }: OverviewProps) => {
{ refetchOnWindowFocus: live, refetchInterval: live ? 3000 : false }
)
const activeQueries = (data ?? []).filter((x) => x.state === 'active')
const blockedQueries = (data ?? []).filter((x) => x.blocked_by.length > 0)
const warnBlockedQueries = blockedQueries.some((activity) => {
const start = getActivityStart(activity)
if (!start) return false
return dayjs().utc().diff(dayjs(start).utc(), 'second') >= WARN_DURATION_BLOCKED
})
const longestBlockedQuery = blockedQueries.reduce<{
activity: DatabaseActivity
duration: number
} | null>((longest, activity) => {
const start = getActivityStart(activity)
if (!start) return longest
const duration = Math.max(dayjs().utc().diff(dayjs(start).utc(), 'second'), 0)
return longest === null || duration > longest.duration ? { activity, duration } : longest
}, null)
const idleInTransactionQueries = (data ?? []).filter((x) => {
const isIdleInTransaction =
x.state === 'idle in transaction' || x.state === 'idle in transaction (aborted)'
@@ -53,7 +71,7 @@ export const Overview = ({ live }: OverviewProps) => {
const longestRunningQuery = (data ?? [])
.filter((x) => LONG_RUNNING_STATES.includes(x.state))
.reduce<{ activity: DatabaseActivity; duration: number } | null>((longest, activity) => {
const start = activity.state === 'active' ? activity.query_start : activity.transaction_start
const start = getActivityStart(activity)
if (!start) return longest
const duration = Math.max(dayjs().utc().diff(dayjs(start).utc(), 'second'), 0)
return longest === null || duration > longest.duration ? { activity, duration } : longest
@@ -182,11 +200,11 @@ export const Overview = ({ live }: OverviewProps) => {
<MetricCard
isLoading={isLoadingActivity}
className={cn(blockedQueries.length && 'bg-destructive-200 border-destructive-400')}
className={cn(warnBlockedQueries && 'bg-destructive-200 border-destructive-400')}
>
<MetricCardHeader>
<MetricCardLabel
className={cn(blockedQueries.length && 'text-foreground')}
className={cn(warnBlockedQueries && 'text-foreground')}
tooltip={
<>
<p>
@@ -204,8 +222,32 @@ export const Overview = ({ live }: OverviewProps) => {
</MetricCardLabel>
</MetricCardHeader>
<MetricCardContent>
<MetricCardValue className={cn(blockedQueries.length && 'text-destructive')}>
{blockedQueries.length}
<MetricCardValue
className={cn('space-x-1', warnBlockedQueries && 'text-destructive')}
>
<span>{blockedQueries.length}</span>
{warnBlockedQueries && longestBlockedQuery && (
<>
<span className="text-foreground-light text-xs">·</span>
<span
role="button"
tabIndex={0}
className="text-foreground-light text-xs cursor-pointer hover:text-foreground transition normal-nums"
onClick={() => setSelectedPid(longestBlockedQuery.activity.pid)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
setSelectedPid(longestBlockedQuery.activity.pid)
}
}}
>
PID {longestBlockedQuery.activity.pid} blocked for{' '}
<span className="tabular-nums">
{formatDuration(longestBlockedQuery.duration * 1000, 0)}
</span>
</span>
</>
)}
</MetricCardValue>
</MetricCardContent>
</MetricCard>