mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
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:
1 parent
4822687a64
commit
cbb076ddf1
3 files changed
+61
-6
No files matched your search
+1
-1
@@ -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 = {
|
||||
|
||||
+13
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user