DatabaseInfrastructureSection.tsx268 lines · main
1import { useParams } from 'common'
2import dayjs from 'dayjs'
3import Link from 'next/link'
4import { useMemo } from 'react'
5import {
6 MetricCard,
7 MetricCardContent,
8 MetricCardHeader,
9 MetricCardLabel,
10 MetricCardValue,
11} from 'ui-patterns/MetricCard'
12
13import {
14 parseConnectionsData,
15 parseInfrastructureMetrics,
16} from './DatabaseInfrastructureSection.utils'
17import { useInfraMonitoringAttributesQuery } from '@/data/analytics/infra-monitoring-query'
18import { useMaxConnectionsQuery } from '@/data/database/max-connections-query'
19import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
20
21type DatabaseInfrastructureSectionProps = {
22 interval: '1hr' | '1day' | '7day'
23 refreshKey: number
24 dbErrorRate: number
25 isLoading: boolean
26 slowQueriesCount?: number
27 slowQueriesLoading?: boolean
28}
29
30export const DatabaseInfrastructureSection = ({
31 interval,
32 refreshKey,
33 dbErrorRate: _dbErrorRate,
34 isLoading: _dbLoading,
35 slowQueriesCount = 0,
36 slowQueriesLoading = false,
37}: DatabaseInfrastructureSectionProps) => {
38 const { ref: projectRef } = useParams()
39 const { data: project } = useSelectedProjectQuery()
40
41 // refreshKey forces date recalculation when user clicks refresh button
42 // eslint-disable-next-line react-hooks/exhaustive-deps
43 const { startDate, endDate, infraInterval } = useMemo(() => {
44 const now = dayjs()
45 const end = now.toISOString()
46 let start: string
47 let infraInterval: '1h' | '1d'
48
49 switch (interval) {
50 case '1hr':
51 start = now.subtract(1, 'hour').toISOString()
52 infraInterval = '1h'
53 break
54 case '1day':
55 start = now.subtract(1, 'day').toISOString()
56 infraInterval = '1h'
57 break
58 case '7day':
59 start = now.subtract(7, 'day').toISOString()
60 infraInterval = '1d'
61 break
62 default:
63 start = now.subtract(1, 'hour').toISOString()
64 infraInterval = '1h'
65 }
66
67 return { startDate: start, endDate: end, infraInterval }
68 }, [interval, refreshKey])
69
70 const {
71 data: infraData,
72 isLoading: infraLoading,
73 error: infraError,
74 } = useInfraMonitoringAttributesQuery({
75 projectRef,
76 attributes: [
77 'avg_cpu_usage',
78 'ram_usage',
79 'disk_fs_used_system',
80 'disk_fs_used_wal',
81 'pg_database_size',
82 'disk_fs_size',
83 'disk_io_consumption',
84 'pg_stat_database_num_backends',
85 ],
86 startDate,
87 endDate,
88 interval: infraInterval,
89 })
90
91 const { data: maxConnectionsData } = useMaxConnectionsQuery({
92 projectRef,
93 connectionString: project?.connectionString,
94 })
95
96 const metrics = useMemo(() => parseInfrastructureMetrics(infraData), [infraData])
97
98 const connections = useMemo(
99 () => parseConnectionsData(infraData, maxConnectionsData),
100 [infraData, maxConnectionsData]
101 )
102
103 const errorMessage =
104 infraError && typeof infraError === 'object' && 'message' in infraError
105 ? String(infraError.message)
106 : 'Error loading data'
107
108 // Generate database report URL with time range parameters
109 const getDatabaseReportUrl = () => {
110 const now = dayjs()
111 let its: string
112 let helperText: string
113
114 switch (interval) {
115 case '1hr':
116 its = now.subtract(1, 'hour').toISOString()
117 helperText = 'Last 60 minutes'
118 break
119 case '1day':
120 its = now.subtract(24, 'hour').toISOString()
121 helperText = 'Last 24 hours'
122 break
123 case '7day':
124 its = now.subtract(7, 'day').toISOString()
125 helperText = 'Last 7 days'
126 break
127 default:
128 its = now.subtract(24, 'hour').toISOString()
129 helperText = 'Last 24 hours'
130 }
131
132 const ite = now.toISOString()
133 const params = new URLSearchParams({
134 its,
135 ite,
136 isHelper: 'true',
137 helperText,
138 })
139
140 return `/project/${projectRef}/observability/database?${params.toString()}`
141 }
142
143 const databaseReportUrl = getDatabaseReportUrl()
144
145 return (
146 <div>
147 <h2 className="mb-4">Database</h2>
148 {/* First row: Metrics */}
149 <div className="grid grid-cols-3 gap-2">
150 <Link
151 href={`/project/${projectRef}/observability/query-performance?totalTimeFilter=${encodeURIComponent(JSON.stringify({ operator: '>', value: 1000 }))}`}
152 className="block group"
153 >
154 <MetricCard isLoading={slowQueriesLoading}>
155 <MetricCardHeader
156 href={`/project/${projectRef}/observability/query-performance?totalTimeFilter=${encodeURIComponent(JSON.stringify({ operator: '>', value: 1000 }))}`}
157 linkTooltip="Go to query performance"
158 >
159 <MetricCardLabel tooltip="Queries with total execution time (execution time + planning time) greater than 1000ms. High values may indicate query optimization opportunities">
160 Slow Queries
161 </MetricCardLabel>
162 </MetricCardHeader>
163 <MetricCardContent>
164 <MetricCardValue>{slowQueriesCount}</MetricCardValue>
165 </MetricCardContent>
166 </MetricCard>
167 </Link>
168
169 <Link href={databaseReportUrl} className="block group">
170 <MetricCard isLoading={infraLoading}>
171 <MetricCardHeader href={databaseReportUrl} linkTooltip="Go to database report">
172 <MetricCardLabel tooltip="Active database connections (current/max). Monitor to avoid connection exhaustion">
173 Connections
174 </MetricCardLabel>
175 </MetricCardHeader>
176 <MetricCardContent>
177 {infraError ? (
178 <div className="text-xs text-destructive wrap-break-word">{errorMessage}</div>
179 ) : connections.max > 0 ? (
180 <MetricCardValue>
181 {connections.current}/{connections.max}
182 </MetricCardValue>
183 ) : (
184 <MetricCardValue>--</MetricCardValue>
185 )}
186 </MetricCardContent>
187 </MetricCard>
188 </Link>
189
190 <Link href={databaseReportUrl} className="block group">
191 <MetricCard isLoading={infraLoading}>
192 <MetricCardHeader href={databaseReportUrl} linkTooltip="Go to database report">
193 <MetricCardLabel tooltip="Disk usage percentage of total disk space used">
194 Disk Usage
195 </MetricCardLabel>
196 </MetricCardHeader>
197 <MetricCardContent>
198 {infraError ? (
199 <div className="text-xs text-destructive wrap-break-word">{errorMessage}</div>
200 ) : metrics ? (
201 <MetricCardValue>{metrics.disk.current.toFixed(0)}%</MetricCardValue>
202 ) : (
203 <MetricCardValue>--</MetricCardValue>
204 )}
205 </MetricCardContent>
206 </MetricCard>
207 </Link>
208
209 <Link href={databaseReportUrl} className="block group">
210 <MetricCard isLoading={infraLoading}>
211 <MetricCardHeader href={databaseReportUrl} linkTooltip="Go to database report">
212 <MetricCardLabel tooltip="Disk I/O consumption percentage. High values may indicate disk bottlenecks">
213 Disk IO
214 </MetricCardLabel>
215 </MetricCardHeader>
216 <MetricCardContent>
217 {infraError ? (
218 <div className="text-xs text-destructive wrap-break-word">{errorMessage}</div>
219 ) : metrics ? (
220 <MetricCardValue>{metrics.diskIo.current.toFixed(0)}%</MetricCardValue>
221 ) : (
222 <MetricCardValue>--</MetricCardValue>
223 )}
224 </MetricCardContent>
225 </MetricCard>
226 </Link>
227
228 <Link href={databaseReportUrl} className="block group">
229 <MetricCard isLoading={infraLoading}>
230 <MetricCardHeader href={databaseReportUrl} linkTooltip="Go to database report">
231 <MetricCardLabel tooltip="RAM usage percentage. Sustained high usage may indicate memory pressure">
232 Memory
233 </MetricCardLabel>
234 </MetricCardHeader>
235 <MetricCardContent>
236 {infraError ? (
237 <div className="text-xs text-destructive wrap-break-word">{errorMessage}</div>
238 ) : metrics ? (
239 <MetricCardValue>{metrics.ram.current.toFixed(0)}%</MetricCardValue>
240 ) : (
241 <MetricCardValue>--</MetricCardValue>
242 )}
243 </MetricCardContent>
244 </MetricCard>
245 </Link>
246
247 <Link href={databaseReportUrl} className="block group">
248 <MetricCard isLoading={infraLoading}>
249 <MetricCardHeader href={databaseReportUrl} linkTooltip="Go to database report">
250 <MetricCardLabel tooltip="CPU usage percentage. High values may suggest CPU-intensive queries or workloads">
251 CPU
252 </MetricCardLabel>
253 </MetricCardHeader>
254 <MetricCardContent>
255 {infraError ? (
256 <div className="text-xs text-destructive wrap-break-word">{errorMessage}</div>
257 ) : metrics ? (
258 <MetricCardValue>{metrics.cpu.current.toFixed(0)}%</MetricCardValue>
259 ) : (
260 <MetricCardValue>--</MetricCardValue>
261 )}
262 </MetricCardContent>
263 </MetricCard>
264 </Link>
265 </div>
266 </div>
267 )
268}