From 4e68e5e2cee7c6a0c00dd9d5e78ab90087e0a2ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santiago=20Hern=C3=A1ndez?= Date: Fri, 12 Dec 2025 15:05:29 +0100 Subject: [PATCH] Alerting: Add compact model for alert rules --- pkg/services/ngalert/store/compat.go | 38 ++++++++++++++++++++--- pkg/services/ngalert/store/compat_test.go | 12 +++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/pkg/services/ngalert/store/compat.go b/pkg/services/ngalert/store/compat.go index 4f4194facc1..cb076bbd763 100644 --- a/pkg/services/ngalert/store/compat.go +++ b/pkg/services/ngalert/store/compat.go @@ -10,11 +10,38 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/models" ) +// We only care about the data source UIDs. +type compactQuery struct { + DatasourceUID string `json:"datasourceUid"` +} + func alertRuleToModelsAlertRule(ar alertRule, l log.Logger) (models.AlertRule, error) { + return transformRule(ar, l, false) +} + +// alertRuleToModelsAlertRuleCompact transforms an alertRule to a models.AlertRule +// ignoring alert queries (except for data source UIDs), notification settings, and metadata. +func alertRuleToModelsAlertRuleCompact(ar alertRule, l log.Logger) (models.AlertRule, error) { + return transformRule(ar, l, true) +} + +// transformRule creates a models.AlertRule from an alertRule. +// When 'compact' is set to 'true', it skips parsing the alert queries (except for the data source UID), notification +// settings, and metadata, thus reducing the number of JSON serializations needed. +func transformRule(ar alertRule, l log.Logger, compact bool) (models.AlertRule, error) { var data []models.AlertQuery - err := json.Unmarshal([]byte(ar.Data), &data) - if err != nil { - return models.AlertRule{}, fmt.Errorf("failed to parse data: %w", err) + if compact { + var cqs []compactQuery + if err := json.Unmarshal([]byte(ar.Data), &cqs); err != nil { + return models.AlertRule{}, fmt.Errorf("failed to parse data: %w", err) + } + for _, cq := range cqs { + data = append(data, models.AlertQuery{DatasourceUID: cq.DatasourceUID}) + } + } else { + if err := json.Unmarshal([]byte(ar.Data), &data); err != nil { + return models.AlertRule{}, fmt.Errorf("failed to parse data: %w", err) + } } result := models.AlertRule{ @@ -52,6 +79,7 @@ func alertRuleToModelsAlertRule(ar alertRule, l log.Logger) (models.AlertRule, e result.UpdatedBy = util.Pointer(models.UserUID(*ar.UpdatedBy)) } + var err error if ar.NoDataState != "" { result.NoDataState, err = models.NoDataStateFromString(ar.NoDataState) if err != nil { @@ -90,7 +118,7 @@ func alertRuleToModelsAlertRule(ar alertRule, l log.Logger) (models.AlertRule, e } } - if ar.NotificationSettings != "" { + if !compact && ar.NotificationSettings != "" { ns, err := parseNotificationSettings(ar.NotificationSettings) if err != nil { return models.AlertRule{}, fmt.Errorf("failed to parse notification settings: %w", err) @@ -98,7 +126,7 @@ func alertRuleToModelsAlertRule(ar alertRule, l log.Logger) (models.AlertRule, e result.NotificationSettings = ns } - if ar.Metadata != "" { + if !compact && ar.Metadata != "" { err = json.Unmarshal([]byte(ar.Metadata), &result.Metadata) if err != nil { return models.AlertRule{}, fmt.Errorf("failed to metadata: %w", err) diff --git a/pkg/services/ngalert/store/compat_test.go b/pkg/services/ngalert/store/compat_test.go index 990ec5dd3e1..4f7b4f41f3f 100644 --- a/pkg/services/ngalert/store/compat_test.go +++ b/pkg/services/ngalert/store/compat_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log/logtest" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/util" @@ -81,3 +82,14 @@ func TestAlertRuleVersionToAlertRule(t *testing.T) { } }) } + +func BenchmarkAlertRuleToModelsAlertRule(b *testing.B) { + r := ngmodels.RuleGen.Generate() + ar, err := alertRuleFromModelsAlertRule(r) + require.NoError(b, err) + l := log.NewNopLogger() + + for range b.N { + alertRuleToModelsAlertRuleCompact(ar, l) + } +}