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:
co-authored by
brendamuir
parent
22a9770ec5
commit
1f8f9a45d7
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user