import dayjs from 'dayjs' import { parseAsInteger, useQueryState } from 'nuqs' import { cn } from 'ui' import { MetricCard, MetricCardContent, MetricCardHeader, MetricCardLabel, MetricCardValue, } from 'ui-patterns/MetricCard' import { WARN_DURATION_ACTIVE_QUERY, WARN_DURATION_IDLE_TXN } 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' import { useMaxConnectionsQuery } from '@/data/database/max-connections-query' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' const LONG_RUNNING_STATES: (DatabaseActivity['state'] | undefined)[] = [ 'active', 'idle in transaction', 'idle in transaction (aborted)', ] 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) const { data, isPending: isLoadingActivity } = useDatabaseActivityQuery( { projectRef: project?.ref, connectionString: project?.connectionString, }, { 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 idleInTransactionQueries = (data ?? []).filter((x) => { const isIdleInTransaction = x.state === 'idle in transaction' || x.state === 'idle in transaction (aborted)' if (!isIdleInTransaction || !x.transaction_start) return false return dayjs().utc().diff(dayjs(x.transaction_start).utc(), 'second') > WARN_DURATION_IDLE_TXN }) 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 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 warnLongestRunningQuery = (longestRunningQuery?.activity.state === 'active' && longestRunningQuery.duration >= WARN_DURATION_ACTIVE_QUERY) || ((longestRunningQuery?.activity.state === 'idle in transaction' || longestRunningQuery?.activity.state === 'idle in transaction (aborted)') && longestRunningQuery.duration >= WARN_DURATION_IDLE_TXN) const { data: roles, isPending: isLoadingRoles } = useDatabaseRolesQuery( { projectRef: project?.ref, connectionString: project?.connectionString, }, { refetchOnWindowFocus: live, refetchInterval: live ? 3000 : false } ) const rolesWithActiveConnections = (roles ?? []).filter((role) => role.activeConnections) const totalActiveConnections = (roles ?? []) .map((role) => role.activeConnections) .reduce((a, b) => a + b, 0) const { data: maxConnectionLimit, isPending: isLoadingMaxConnections } = useMaxConnectionsQuery( { projectRef: project?.ref, connectionString: project?.connectionString, }, { select: (data) => data.maxConnections, refetchInterval: live ? 3000 : false, } ) return (
Connections by roles:
{rolesWithActiveConnections.map((role) => ({role.name}:
{role.activeConnections}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 queriesTransactions left open without running a query, which can hold locks and block table cleanup for as long as it stays open
Typically indicates an app issue, such as a forgotten COMMIT or ROLLBACK.
> } > Idle in transaction