Alerting: Add state_periodic_save_batch_size config option (#98019)

* Alerting: Add state_periodic_save_batch_size config option

---------

Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com>
This commit is contained in:
Alexander Akhmetov
2024-12-16 15:30:38 +01:00
committed by GitHub
co-authored by brendamuir
parent 22a9770ec5
commit 1f8f9a45d7
11 changed files with 243 additions and 37 deletions
+83 -26
View File
@@ -224,51 +224,108 @@ func (st DBstore) DeleteAlertInstancesByRule(ctx context.Context, key models.Ale
})
}
func (st DBstore) FullSync(ctx context.Context, instances []models.AlertInstance) error {
// FullSync performs a full synchronization of the given alert instances to the database.
//
// This method will delete all existing alert instances and insert the given instances in a single transaction.
//
// The batchSize parameter controls how many instances are inserted per batch. Increasing batchSize can improve
// performance for large datasets, but can also increase load on the database.
func (st DBstore) FullSync(ctx context.Context, instances []models.AlertInstance, batchSize int) error {
if len(instances) == 0 {
return nil
}
if batchSize <= 0 {
batchSize = 1
}
return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
// First we delete all records from the table
if _, err := sess.Exec("DELETE FROM alert_instance"); err != nil {
return fmt.Errorf("failed to delete alert_instance table: %w", err)
}
for _, alertInstance := range instances {
if err := models.ValidateAlertInstance(alertInstance); err != nil {
st.Logger.Warn("Failed to validate alert instance, skipping", "err", err, "rule_uid", alertInstance.RuleUID)
continue
}
labelTupleJSON, err := alertInstance.Labels.StringKey()
if err != nil {
st.Logger.Warn("Failed to generate alert instance labels key, skipping", "err", err, "rule_uid", alertInstance.RuleUID)
continue
total := len(instances)
for start := 0; start < total; start += batchSize {
end := start + batchSize
if end > total {
end = total
}
_, err = sess.Exec(
"INSERT INTO alert_instance (rule_org_id, rule_uid, labels, labels_hash, current_state, current_reason, current_state_since, current_state_end, last_eval_time, resolved_at, last_sent_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)",
alertInstance.RuleOrgID,
alertInstance.RuleUID,
labelTupleJSON,
alertInstance.LabelsHash,
alertInstance.CurrentState,
alertInstance.CurrentReason,
alertInstance.CurrentStateSince.Unix(),
alertInstance.CurrentStateEnd.Unix(),
alertInstance.LastEvalTime.Unix(),
nullableTimeToUnix(alertInstance.ResolvedAt),
nullableTimeToUnix(alertInstance.LastSentAt),
)
if err != nil {
return fmt.Errorf("failed to insert into alert_instance table: %w", err)
batch := instances[start:end]
if err := st.insertInstancesBatch(sess, batch); err != nil {
return fmt.Errorf("failed to insert batch [%d:%d]: %w", start, end, err)
}
}
if err := sess.Commit(); err != nil {
return fmt.Errorf("failed to commit alert_instance table: %w", err)
}
return nil
})
}
func (st DBstore) insertInstancesBatch(sess *sqlstore.DBSession, batch []models.AlertInstance) error {
// If the batch is empty, nothing to insert.
if len(batch) == 0 {
return nil
}
query := strings.Builder{}
placeholders := make([]string, 0, len(batch))
args := make([]any, 0, len(batch)*11)
query.WriteString("INSERT INTO alert_instance ")
query.WriteString("(rule_org_id, rule_uid, labels, labels_hash, current_state, current_reason, current_state_since, current_state_end, last_eval_time, resolved_at, last_sent_at) VALUES ")
for _, instance := range batch {
if err := models.ValidateAlertInstance(instance); err != nil {
st.Logger.Warn("Skipping invalid alert instance", "err", err, "rule_uid", instance.RuleUID)
continue
}
labelTupleJSON, err := instance.Labels.StringKey()
if err != nil {
st.Logger.Warn("Skipping instance with invalid labels key", "err", err, "rule_uid", instance.RuleUID)
continue
}
placeholders = append(placeholders, "(?,?,?,?,?,?,?,?,?,?,?)")
args = append(args,
instance.RuleOrgID,
instance.RuleUID,
labelTupleJSON,
instance.LabelsHash,
instance.CurrentState,
instance.CurrentReason,
instance.CurrentStateSince.Unix(),
instance.CurrentStateEnd.Unix(),
instance.LastEvalTime.Unix(),
nullableTimeToUnix(instance.ResolvedAt),
nullableTimeToUnix(instance.LastSentAt),
)
}
// If no valid instances were found in this batch, skip insertion.
if len(placeholders) == 0 {
return nil
}
query.WriteString(strings.Join(placeholders, ","))
execArgs := make([]any, 0, len(args)+1)
execArgs = append(execArgs, query.String())
execArgs = append(execArgs, args...)
if _, err := sess.Exec(execArgs...); err != nil {
return fmt.Errorf("failed to insert instances: %w", err)
}
return nil
}
// nullableTimeToUnix converts a nullable time.Time to nil, if it is nil, otherwise it converts the time.Time to a unix timestamp.
func nullableTimeToUnix(t *time.Time) *int64 {
if t == nil {