mirror of
https://github.com/grafana/grafana.git
synced 2025-08-02 16:02:22 +08:00
SQLStore: Prevent concurrent migrations (#44101)
* SQLStore: Prevent concurrent migrations * Hide behind a feature toggle * Configurable locking attempt timeout * Update docs/sources/administration/configuration.md Co-authored-by: Igor Suleymanov <radiohead@users.noreply.github.com> Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
This commit is contained in:

committed by
GitHub

parent
163b570f5d
commit
d718ee1918
@ -3,9 +3,11 @@ package migrator
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4/database"
|
||||
"github.com/lib/pq"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
@ -257,3 +259,76 @@ func (db *PostgresDialect) UpsertSQL(tableName string, keyCols, updateCols []str
|
||||
)
|
||||
return s
|
||||
}
|
||||
|
||||
func (db *PostgresDialect) Lock(cfg LockCfg) error {
|
||||
// trying to obtain the lock for a resource identified by a 64-bit or 32-bit key value
|
||||
// the lock is exclusive: multiple lock requests stack, so that if the same resource is locked three times
|
||||
// it must then be unlocked three times to be released for other sessions' use.
|
||||
// it will either obtain the lock immediately and return true,
|
||||
// or return false if the lock cannot be acquired immediately.
|
||||
query := "SELECT pg_try_advisory_lock(?)"
|
||||
var success bool
|
||||
|
||||
key, err := db.getLockKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate advisory lock key: %w", err)
|
||||
}
|
||||
_, err = cfg.Session.SQL(query, key).Get(&success)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !success {
|
||||
return ErrLockDB
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *PostgresDialect) Unlock(cfg LockCfg) error {
|
||||
// trying to release a previously-acquired exclusive session level advisory lock.
|
||||
// it will either return true if the lock is successfully released or
|
||||
// false if the lock was not held (in addition an SQL warning will be reported by the server)
|
||||
query := "SELECT pg_advisory_unlock(?)"
|
||||
var success bool
|
||||
|
||||
key, err := db.getLockKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate advisory lock key: %w", err)
|
||||
}
|
||||
_, err = cfg.Session.SQL(query, key).Get(&success)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !success {
|
||||
return ErrReleaseLockDB
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDBName(dsn string) (string, error) {
|
||||
if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") {
|
||||
parsedDSN, err := pq.ParseURL(dsn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dsn = parsedDSN
|
||||
}
|
||||
re := regexp.MustCompile(`dbname=(\w+)`)
|
||||
submatch := re.FindSubmatch([]byte(dsn))
|
||||
if len(submatch) < 2 {
|
||||
return "", fmt.Errorf("failed to get database name")
|
||||
}
|
||||
return string(submatch[1]), nil
|
||||
}
|
||||
|
||||
func (db *PostgresDialect) getLockKey() (string, error) {
|
||||
dbName, err := getDBName(db.engine.DataSourceName())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
key, err := database.GenerateAdvisoryLockId(dbName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user