CI / changes (push) Successful in 9s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 26s
CI / web (push) Successful in 33s
CI / go (push) Successful in 2m11s
CI / bird2 (push) Successful in 16s
CI / release (push) Successful in 3m27s
Added PostgreSQL monitoring and maintenance capabilities to the API, including new endpoints for instance-level metrics, maintenance operations, and job scheduling. Updated the HTTP API to support PostgreSQL monitoring routes and integrated a background scheduler for metrics collection. Enhanced the CLI with database commands for maintenance tasks. Updated documentation to reflect these changes.
105 lines
3.2 KiB
Go
105 lines
3.2 KiB
Go
package pgmonitor
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
// Recommendations builds heuristic items from live stats and snapshots.
|
|
func (s *Service) Recommendations(ctx context.Context) (RecommendationsResponse, error) {
|
|
now := time.Now().UTC()
|
|
var items []RecommendationItem
|
|
|
|
ov, err := s.Overview(ctx)
|
|
if err == nil {
|
|
if ov.Database.CacheHitPct > 0 && ov.Database.CacheHitPct < 90 {
|
|
items = append(items, RecommendationItem{
|
|
Severity: "warn",
|
|
Code: "low_cache_hit",
|
|
Title: "Низкий cache hit ratio",
|
|
Detail: "Buffer cache hit ниже 90%; проверьте shared_buffers и горячие seq scan.",
|
|
})
|
|
}
|
|
if ov.Database.Deadlocks > 0 {
|
|
items = append(items, RecommendationItem{
|
|
Severity: "warn",
|
|
Code: "deadlocks",
|
|
Title: "Зафиксированы deadlocks",
|
|
Detail: "Проверьте конкурирующие транзакции и порядок блокировок.",
|
|
})
|
|
}
|
|
if ov.Connections.MaxConnections > 0 &&
|
|
float64(ov.Connections.Total)/float64(ov.Connections.MaxConnections) > 0.8 {
|
|
items = append(items, RecommendationItem{
|
|
Severity: "critical",
|
|
Code: "connections_high",
|
|
Title: "Много подключений к PostgreSQL",
|
|
Detail: "Использование max_connections выше 80%; увеличьте pool tuning или лимит.",
|
|
})
|
|
}
|
|
}
|
|
|
|
tables, err := s.Tables(ctx, 30)
|
|
if err == nil {
|
|
for _, t := range tables {
|
|
if t.SeqScan > 1000 && t.IdxScan < t.SeqScan/10 {
|
|
items = append(items, RecommendationItem{
|
|
Severity: "warn",
|
|
Code: "missing_index",
|
|
Title: "Высокий seq_scan",
|
|
Detail: "Таблица часто сканируется последовательно; рассмотрите индекс.",
|
|
Refs: []string{t.Relname},
|
|
})
|
|
}
|
|
if t.BloatRatio > 0.2 && t.DeadTuples > 5000 {
|
|
items = append(items, RecommendationItem{
|
|
Severity: "info",
|
|
Code: "autovacuum_lag",
|
|
Title: "Возможный bloat / мёртвые строки",
|
|
Detail: "Высокая доля n_dead_tup; запланируйте VACUUM.",
|
|
Refs: []string{t.Relname},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
if snap, ok, _ := s.loadSnapshot(ctx, "unused_indexes", 30*time.Minute); ok {
|
|
type unused struct {
|
|
Index string `json:"index"`
|
|
SizeBytes int64 `json:"size_bytes"`
|
|
}
|
|
var list []unused
|
|
if decodePayload(snap.Payload, &list) == nil {
|
|
for _, u := range list {
|
|
if u.SizeBytes < 1024*1024 {
|
|
continue
|
|
}
|
|
items = append(items, RecommendationItem{
|
|
Severity: "info",
|
|
Code: "unused_index",
|
|
Title: "Неиспользуемый индекс",
|
|
Detail: "idx_scan=0; проверьте перед удалением.",
|
|
Refs: []string{u.Index},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
q, err := s.TopQueries(ctx, 5)
|
|
if err == nil {
|
|
for _, qs := range q.Items {
|
|
if qs.MeanExecMs > 500 {
|
|
items = append(items, RecommendationItem{
|
|
Severity: "warn",
|
|
Code: "slow_query",
|
|
Title: "Медленный запрос",
|
|
Detail: "Среднее время выполнения выше 500ms.",
|
|
Refs: []string{qs.Query},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return RecommendationsResponse{CollectedAt: now, Items: items}, nil
|
|
}
|