From 534fe0f1d217003b76fb4f199984dc3aaf21c3fe 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 | 47 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/pkg/config/database.go b/pkg/config/database.go index bb6c8cc7a..9465c2471 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" @@ -20,7 +22,10 @@ import ( "time" ) -var registerDriverOnce sync.Once +var ( + registerDriverOnce sync.Once + wsrepSyncWait int64 +) // Database defines database client configuration. type Database struct { @@ -77,12 +82,14 @@ func (d *Database) Open(logger *logging.Logger) (*icingadb.DB, error) { } } + wsrepSyncWait = int64(d.Options.WsrepSyncWait) + c, err := mysql.NewConnector(config) if err != nil { return nil, errors.Wrap(err, "can't open mysql database") } - connector := &icingadbDriver.RetryConnector{Connector: c, Logger: logger} + connector := &icingadbDriver.RetryConnector{Connector: c, Logger: logger, InitConn: setGaleraOpts} db = sqlx.NewDb(sql.OpenDB(connector), icingadbDriver.MySQL) case "pgsql": uri := &url.URL{ @@ -182,3 +189,39 @@ func (d *Database) isUnixAddr() bool { func unknownDbType(t string) error { return errors.Errorf(`unknown database type %q, must be one of: "mysql", "pgsql"`, t) } + +var errUnknownSysVar = &mysql.MySQLError{Number: 1193} + +// 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. +func setGaleraOpts(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 err != nil && 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 +}