diff --git a/pkg/config/database.go b/pkg/config/database.go index e7f837396..5eddc5719 100644 --- a/pkg/config/database.go +++ b/pkg/config/database.go @@ -1,7 +1,9 @@ package config import ( + "context" "database/sql" + "database/sql/driver" "fmt" "github.com/go-sql-driver/mysql" icingadbDriver "github.com/icinga/icingadb/pkg/driver" @@ -22,6 +24,8 @@ import ( var registerDriverOnce sync.Once +var errUnknownSysVar = &mysql.MySQLError{Number: 1193} + // Database defines database client configuration. type Database struct { Type string `yaml:"type" default:"mysql"` @@ -82,7 +86,43 @@ func (d *Database) Open(logger *logging.Logger) (*icingadb.DB, error) { return nil, errors.Wrap(err, "can't open mysql database") } - connector := &icingadbDriver.RetryConnector{Connector: c, SqlDriver: &mysql.MySQLDriver{}, Logger: logger} + wsrepSyncWait := int64(d.Options.WsrepSyncWait) + + // setGaleraOpts tries SET SESSION wsrep_sync_wait. + // + // This ensures causality checks will take place before execution, + // ensuring that every statement is executed on a fully synced node. + // https://mariadb.com/kb/en/galera-cluster-system-variables/#wsrep_sync_wait + // + // It prevents running into foreign key errors while inserting into linked tables on different MySQL nodes. + // Error 1193 "Unknown system variable" is ignored to support MySQL single nodes. + var setGaleraOpts = func(ctx context.Context, conn driver.Conn) error { + const galeraOpts = "SET SESSION wsrep_sync_wait=?" + + stmt, err := conn.(driver.ConnPrepareContext).PrepareContext(ctx, galeraOpts) + if err != nil { + err = errors.Wrap(err, "can't prepare "+galeraOpts) + } else { + _, err = stmt.(driver.StmtExecContext).ExecContext(ctx, []driver.NamedValue{{Value: wsrepSyncWait}}) + if err != nil { + err = errors.Wrap(err, "can't execute "+galeraOpts) + } + } + + if errors.Is(err, errUnknownSysVar) { + err = nil + } + + if stmt != nil { + if errClose := stmt.Close(); errClose != nil && err == nil { + err = errors.Wrap(errClose, "can't close statement "+galeraOpts) + } + } + + return err + } + + connector := &icingadbDriver.RetryConnector{Connector: c, SqlDriver: &mysql.MySQLDriver{}, Logger: logger, InitConn: setGaleraOpts} db = sqlx.NewDb(sql.OpenDB(connector), icingadbDriver.MySQL) case "pgsql": uri := &url.URL{