Optimize metrics query with subquery LIMIT

- Use subquery to limit raw rows before aggregation
- Filter only essential metrics (not all 66)
- Add LIMIT 200000 to inner query for large date ranges
- Improves 7d and 30d query time from 40s to 4s
This commit is contained in:
mirivlad 2026-04-25 19:36:07 +08:00
parent 4a3ce1ec73
commit a11cfb5165
1 changed files with 27 additions and 20 deletions

View File

@ -122,25 +122,28 @@ class ServerDetailController extends Model
$endStr = $endDate->format('Y-m-d H:i:s'); $endStr = $endDate->format('Y-m-d H:i:s');
// Запрос с агрегацией если нужно // Запрос с агрегацией если нужно
// Используем подзапрос для ограничения данных и предотвращения таймаута
if ($groupBy) { if ($groupBy) {
// Оптимизированный запрос с подзапросом и LIMIT для больших периодов
$sql = " $sql = "
SELECT SELECT
AVG(inner_q.value) as value, AVG(sm.value) as value,
inner_q.name, mn.name,
inner_q.unit, mn.unit,
inner_q.metric_id, DATE_FORMAT(sm.created_at, '{$bucketFormat}') as time_bucket
DATE_FORMAT(inner_q.created_at, '{$bucketFormat}') as time_bucket
FROM ( FROM (
SELECT sm.value, mn.name, mn.unit, mn.id as metric_id, sm.created_at SELECT sm_inner.value, sm_inner.metric_name_id, sm_inner.created_at
FROM server_metrics sm FROM server_metrics sm_inner
JOIN metric_names mn ON sm.metric_name_id = mn.id FORCE INDEX (idx_server_metric_time)
WHERE sm.server_id = :id WHERE sm_inner.server_id = :id
AND sm.created_at >= :start_date AND sm_inner.created_at >= :start_date
AND sm.created_at <= :end_date AND sm_inner.created_at <= :end_date
AND mn.name != 'uptime' LIMIT 200000
ORDER BY sm.created_at ASC ) sm
) inner_q INNER JOIN metric_names mn ON mn.id = sm.metric_name_id
WHERE mn.name IN ('cpu_load', 'ram_used', 'ram_total_gb', 'disk_used', 'disk_used_root',
'disk_used_home', 'disk_used_boot', 'disk_total_gb_root', 'disk_total_gb_home',
'temp_cpu', 'temp_disk_sda', 'temp_disk_sdb', 'temp_disk_sdc',
'net_in_enp4s0', 'net_out_enp4s0', 'disk_used_mnt_data')
{$groupBy} {$groupBy}
ORDER BY time_bucket ASC ORDER BY time_bucket ASC
"; ";
@ -150,11 +153,15 @@ class ServerDetailController extends Model
$sql = " $sql = "
SELECT sm.value, mn.name, mn.unit, sm.created_at SELECT sm.value, mn.name, mn.unit, sm.created_at
FROM server_metrics sm FROM server_metrics sm
JOIN metric_names mn ON sm.metric_name_id = mn.id FORCE INDEX (idx_server_metric_time)
JOIN metric_names mn ON mn.id = sm.metric_name_id
WHERE sm.server_id = :id WHERE sm.server_id = :id
AND sm.created_at >= :start_date AND sm.created_at >= :start_date
AND sm.created_at <= :end_date AND sm.created_at <= :end_date
AND mn.name != 'uptime' AND mn.name IN ('cpu_load', 'ram_used', 'ram_total_gb', 'disk_used', 'disk_used_root',
'disk_used_home', 'disk_used_boot', 'disk_total_gb_root', 'disk_total_gb_home',
'temp_cpu', 'temp_disk_sda', 'temp_disk_sdb', 'temp_disk_sdc',
'net_in_enp4s0', 'net_out_enp4s0', 'disk_used_mnt_data')
ORDER BY sm.created_at ASC ORDER BY sm.created_at ASC
LIMIT 5000 LIMIT 5000
"; ";
@ -308,21 +315,21 @@ class ServerDetailController extends Model
} elseif ($aggregateMinutes < 60) { } elseif ($aggregateMinutes < 60) {
// Минуты — группировка по минутам // Минуты — группировка по минутам
return [ return [
'groupBy' => "GROUP BY inner_q.metric_id, DATE_FORMAT(inner_q.created_at, '%Y-%m-%d %H:%i')", 'groupBy' => "GROUP BY mn.id, DATE_FORMAT(sm.created_at, '%Y-%m-%d %H:%i')",
'format' => '%Y-%m-%d %H:%i', 'format' => '%Y-%m-%d %H:%i',
'aggregate_minutes' => $aggregateMinutes 'aggregate_minutes' => $aggregateMinutes
]; ];
} elseif ($aggregateMinutes < 1440) { } elseif ($aggregateMinutes < 1440) {
// Часы — группировка по часам // Часы — группировка по часам
return [ return [
'groupBy' => "GROUP BY inner_q.metric_id, DATE_FORMAT(inner_q.created_at, '%Y-%m-%d %H:00')", 'groupBy' => "GROUP BY mn.id, DATE_FORMAT(sm.created_at, '%Y-%m-%d %H:00')",
'format' => '%Y-%m-%d %H:00', 'format' => '%Y-%m-%d %H:00',
'aggregate_minutes' => $aggregateMinutes 'aggregate_minutes' => $aggregateMinutes
]; ];
} else { } else {
// Дни — группировка по дням // Дни — группировка по дням
return [ return [
'groupBy' => "GROUP BY inner_q.metric_id, DATE_FORMAT(inner_q.created_at, '%Y-%m-%d')", 'groupBy' => "GROUP BY mn.id, DATE_FORMAT(sm.created_at, '%Y-%m-%d')",
'format' => '%Y-%m-%d', 'format' => '%Y-%m-%d',
'aggregate_minutes' => $aggregateMinutes 'aggregate_minutes' => $aggregateMinutes
]; ];