2021-03-08 14:19:21 -06:00
|
|
|
package store
|
2021-01-18 12:57:17 -06:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-10-06 01:22:58 -05:00
|
|
|
"fmt"
|
|
|
|
"sort"
|
2021-01-18 12:57:17 -06:00
|
|
|
"strings"
|
|
|
|
|
2022-10-19 08:02:15 -05:00
|
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
2022-10-06 01:22:58 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
2021-03-08 14:19:21 -06:00
|
|
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
2021-01-18 12:57:17 -06:00
|
|
|
)
|
|
|
|
|
2021-03-08 14:19:21 -06:00
|
|
|
// ListAlertInstances is a handler for retrieving alert instances within specific organisation
|
2021-01-18 12:57:17 -06:00
|
|
|
// based on various filters.
|
2023-03-28 03:34:35 -05:00
|
|
|
func (st DBstore) ListAlertInstances(ctx context.Context, cmd *models.ListAlertInstancesQuery) (result []*models.AlertInstance, err error) {
|
|
|
|
err = st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
|
2022-05-23 03:49:49 -05:00
|
|
|
alertInstances := make([]*models.AlertInstance, 0)
|
2021-01-18 12:57:17 -06:00
|
|
|
|
|
|
|
s := strings.Builder{}
|
2023-08-30 10:46:47 -05:00
|
|
|
params := make([]any, 0)
|
2021-01-18 12:57:17 -06:00
|
|
|
|
2023-08-30 10:46:47 -05:00
|
|
|
addToQuery := func(stmt string, p ...any) {
|
2021-01-18 12:57:17 -06:00
|
|
|
s.WriteString(stmt)
|
|
|
|
params = append(params, p...)
|
|
|
|
}
|
|
|
|
|
2022-10-06 01:22:58 -05:00
|
|
|
addToQuery("SELECT * FROM alert_instance WHERE rule_org_id = ?", cmd.RuleOrgID)
|
2021-01-18 12:57:17 -06:00
|
|
|
|
2021-05-03 06:19:15 -05:00
|
|
|
if cmd.RuleUID != "" {
|
2021-05-12 06:17:43 -05:00
|
|
|
addToQuery(` AND rule_uid = ?`, cmd.RuleUID)
|
2021-01-18 12:57:17 -06:00
|
|
|
}
|
2023-11-14 14:50:27 -06:00
|
|
|
if st.FeatureToggles.IsEnabled(ctx, featuremgmt.FlagAlertingNoNormalState) {
|
2023-01-13 17:29:29 -06:00
|
|
|
s.WriteString(fmt.Sprintf(" AND NOT (current_state = '%s' AND current_reason = '')", models.InstanceStateNormal))
|
2022-05-23 03:49:49 -05:00
|
|
|
}
|
2021-01-18 12:57:17 -06:00
|
|
|
if err := sess.SQL(s.String(), params...).Find(&alertInstances); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-03-28 03:34:35 -05:00
|
|
|
result = alertInstances
|
2021-01-18 12:57:17 -06:00
|
|
|
return nil
|
|
|
|
})
|
2023-03-28 03:34:35 -05:00
|
|
|
return result, err
|
2021-01-18 12:57:17 -06:00
|
|
|
}
|
|
|
|
|
2022-10-06 01:22:58 -05:00
|
|
|
// SaveAlertInstance is a handler for saving a new alert instance.
|
|
|
|
func (st DBstore) SaveAlertInstance(ctx context.Context, alertInstance models.AlertInstance) error {
|
2022-10-19 08:02:15 -05:00
|
|
|
return st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
|
2022-09-09 10:44:06 -05:00
|
|
|
if err := models.ValidateAlertInstance(alertInstance); err != nil {
|
|
|
|
return err
|
2021-01-18 12:57:17 -06:00
|
|
|
}
|
|
|
|
|
2022-10-06 01:22:58 -05:00
|
|
|
labelTupleJSON, err := alertInstance.Labels.StringKey()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-08-30 10:46:47 -05:00
|
|
|
params := append(make([]any, 0), alertInstance.RuleOrgID, alertInstance.RuleUID, labelTupleJSON, alertInstance.LabelsHash, alertInstance.CurrentState, alertInstance.CurrentReason, alertInstance.CurrentStateSince.Unix(), alertInstance.CurrentStateEnd.Unix(), alertInstance.LastEvalTime.Unix())
|
Alerting: Write and Delete multiple alert instances. (#54072)
Prior to this change, all alert instance writes and deletes happened
individually, in their own database transaction. This change batches up
writes or deletes for a given rule's evaluation loop into a single
transaction before applying it.
Before:
```
goos: darwin
goarch: arm64
pkg: github.com/grafana/grafana/pkg/services/ngalert/store
BenchmarkAlertInstanceOperations-8 398 2991381 ns/op 1133537 B/op 27703 allocs/op
--- BENCH: BenchmarkAlertInstanceOperations-8
util.go:127: alert definition: {orgID: 1, UID: FovKXiRVzm} with title: "an alert definition FTvFXmRVkz" interval: 60 created
util.go:127: alert definition: {orgID: 1, UID: foDFXmRVkm} with title: "an alert definition fovFXmRVkz" interval: 60 created
util.go:127: alert definition: {orgID: 1, UID: VQvFuigVkm} with title: "an alert definition VwDKXmR4kz" interval: 60 created
PASS
ok github.com/grafana/grafana/pkg/services/ngalert/store 1.619s
```
After:
```
goos: darwin
goarch: arm64
pkg: github.com/grafana/grafana/pkg/services/ngalert/store
BenchmarkAlertInstanceOperations-8 1440 816484 ns/op 352297 B/op 6529 allocs/op
--- BENCH: BenchmarkAlertInstanceOperations-8
util.go:127: alert definition: {orgID: 1, UID: 302r_igVzm} with title: "an alert definition q0h9lmR4zz" interval: 60 created
util.go:127: alert definition: {orgID: 1, UID: 71hrlmR4km} with title: "an alert definition nJ29_mR4zz" interval: 60 created
util.go:127: alert definition: {orgID: 1, UID: Cahr_mR4zm} with title: "an alert definition ja2rlmg4zz" interval: 60 created
PASS
ok github.com/grafana/grafana/pkg/services/ngalert/store 1.383s
```
So we cut time by about 75% and memory allocations by about 60% when
storing and deleting 100 instances.
This change also updates some of our tests so that they run successfully against postgreSQL - we were using random Int64s, but postgres integers, which our tables use, max out at 2^31-1
2022-09-01 22:17:20 -05:00
|
|
|
|
2022-10-14 14:33:06 -05:00
|
|
|
upsertSQL := st.SQLStore.GetDialect().UpsertSQL(
|
2022-09-09 10:44:06 -05:00
|
|
|
"alert_instance",
|
|
|
|
[]string{"rule_org_id", "rule_uid", "labels_hash"},
|
|
|
|
[]string{"rule_org_id", "rule_uid", "labels", "labels_hash", "current_state", "current_reason", "current_state_since", "current_state_end", "last_eval_time"})
|
|
|
|
_, err = sess.SQL(upsertSQL, params...).Query()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2021-01-18 12:57:17 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
2021-04-02 10:11:33 -05:00
|
|
|
|
2022-02-08 07:49:04 -06:00
|
|
|
func (st DBstore) FetchOrgIds(ctx context.Context) ([]int64, error) {
|
2021-05-12 06:17:43 -05:00
|
|
|
orgIds := []int64{}
|
2021-04-02 10:11:33 -05:00
|
|
|
|
2022-10-19 08:02:15 -05:00
|
|
|
err := st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
|
2021-04-02 10:11:33 -05:00
|
|
|
s := strings.Builder{}
|
2023-08-30 10:46:47 -05:00
|
|
|
params := make([]any, 0)
|
2021-04-02 10:11:33 -05:00
|
|
|
|
2023-08-30 10:46:47 -05:00
|
|
|
addToQuery := func(stmt string, p ...any) {
|
2021-04-02 10:11:33 -05:00
|
|
|
s.WriteString(stmt)
|
|
|
|
params = append(params, p...)
|
|
|
|
}
|
|
|
|
|
2021-05-12 06:17:43 -05:00
|
|
|
addToQuery("SELECT DISTINCT rule_org_id FROM alert_instance")
|
2021-04-02 10:11:33 -05:00
|
|
|
|
|
|
|
if err := sess.SQL(s.String(), params...).Find(&orgIds); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
2021-05-12 06:17:43 -05:00
|
|
|
|
|
|
|
return orgIds, err
|
2021-04-02 10:11:33 -05:00
|
|
|
}
|
2021-07-26 11:12:04 -05:00
|
|
|
|
2022-10-06 01:22:58 -05:00
|
|
|
// DeleteAlertInstances deletes instances with the provided keys in a single transaction.
|
|
|
|
func (st DBstore) DeleteAlertInstances(ctx context.Context, keys ...models.AlertInstanceKey) error {
|
|
|
|
if len(keys) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type data struct {
|
|
|
|
ruleOrgID int64
|
|
|
|
ruleUID string
|
2023-08-30 10:46:47 -05:00
|
|
|
labelHashes []any
|
2022-10-06 01:22:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Sort by org and rule UID. Most callers will have grouped already, but it's
|
|
|
|
// cheap to verify and leads to more compact transactions.
|
|
|
|
sort.Slice(keys, func(i, j int) bool {
|
|
|
|
aye := keys[i]
|
|
|
|
jay := keys[j]
|
|
|
|
|
|
|
|
if aye.RuleOrgID < jay.RuleOrgID {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if aye.RuleOrgID == jay.RuleOrgID && aye.RuleUID < jay.RuleUID {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
})
|
|
|
|
|
|
|
|
maxRows := 200
|
|
|
|
rowData := data{
|
2023-08-30 10:46:47 -05:00
|
|
|
0, "", make([]any, 0, maxRows),
|
2022-10-06 01:22:58 -05:00
|
|
|
}
|
|
|
|
placeholdersBuilder := strings.Builder{}
|
|
|
|
placeholdersBuilder.WriteString("(")
|
|
|
|
|
2022-10-19 08:02:15 -05:00
|
|
|
execQuery := func(s *db.Session, rd data, placeholders string) error {
|
2022-10-06 01:22:58 -05:00
|
|
|
if len(rd.labelHashes) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
placeholders = strings.TrimRight(placeholders, ", ")
|
|
|
|
placeholders = placeholders + ")"
|
|
|
|
|
|
|
|
queryString := fmt.Sprintf(
|
|
|
|
"DELETE FROM alert_instance WHERE rule_org_id = ? AND rule_uid = ? AND labels_hash IN %s;",
|
|
|
|
placeholders,
|
|
|
|
)
|
|
|
|
|
2023-08-30 10:46:47 -05:00
|
|
|
execArgs := make([]any, 0, 3+len(rd.labelHashes))
|
2022-10-06 01:22:58 -05:00
|
|
|
execArgs = append(execArgs, queryString, rd.ruleOrgID, rd.ruleUID)
|
|
|
|
execArgs = append(execArgs, rd.labelHashes...)
|
|
|
|
_, err := s.Exec(execArgs...)
|
2021-07-26 11:12:04 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-10-06 01:22:58 -05:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-10-19 08:02:15 -05:00
|
|
|
err := st.SQLStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
2022-10-06 01:22:58 -05:00
|
|
|
counter := 0
|
|
|
|
|
|
|
|
// Create batches of up to 200 items and execute a new delete statement for each batch.
|
|
|
|
for _, k := range keys {
|
|
|
|
counter++
|
|
|
|
// When a rule ID changes or we hit 200 hashes, issue a statement.
|
|
|
|
if rowData.ruleOrgID != k.RuleOrgID || rowData.ruleUID != k.RuleUID || len(rowData.labelHashes) >= 200 {
|
|
|
|
err := execQuery(sess, rowData, placeholdersBuilder.String())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset our reused data.
|
|
|
|
rowData.ruleOrgID = k.RuleOrgID
|
|
|
|
rowData.ruleUID = k.RuleUID
|
|
|
|
rowData.labelHashes = rowData.labelHashes[:0]
|
|
|
|
placeholdersBuilder.Reset()
|
|
|
|
placeholdersBuilder.WriteString("(")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Accumulate new values.
|
|
|
|
rowData.labelHashes = append(rowData.labelHashes, k.LabelsHash)
|
|
|
|
placeholdersBuilder.WriteString("?, ")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete any remaining rows.
|
|
|
|
if len(rowData.labelHashes) != 0 {
|
|
|
|
err := execQuery(sess, rowData, placeholdersBuilder.String())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-26 11:12:04 -05:00
|
|
|
return nil
|
|
|
|
})
|
2022-10-06 01:22:58 -05:00
|
|
|
|
|
|
|
return err
|
2021-07-26 11:12:04 -05:00
|
|
|
}
|
2022-08-25 13:12:22 -05:00
|
|
|
|
|
|
|
func (st DBstore) DeleteAlertInstancesByRule(ctx context.Context, key models.AlertRuleKey) error {
|
2022-10-19 08:02:15 -05:00
|
|
|
return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
2022-08-25 13:12:22 -05:00
|
|
|
_, err := sess.Exec("DELETE FROM alert_instance WHERE rule_org_id = ? AND rule_uid = ?", key.OrgID, key.UID)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
}
|