From bb74479737e87ed9191fdb5b7c437758193c99a6 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 19 Mar 2024 16:57:09 +0100 Subject: [PATCH] MySQL driver: on connect try setting wsrep_sync_wait, swallow error 1193 In Galera clusters wsrep_sync_wait=7 lets statements catch up all pending sync between nodes first. This way new child rows await fresh parent ones from other nodes not to run into foreign key errors. MySQL single nodes will reject this with error 1193 "Unknown system variable" which is OK. --- pkg/config/database.go | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) 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{