Skip to content

Commit

Permalink
MySQL driver: on connect try setting wsrep_sync_wait=4, swallow error…
Browse files Browse the repository at this point in the history
… 1193

In Galera clusters wsrep_sync_wait=4 ensures inserted rows to be synced over
all nodes before reporting success to their inserter. That allows inserting
child rows immediately after that on another node without running into
foreign key errors. MySQL single nodes will reject this with error 1193
"Unknown system variable" which is OK.
  • Loading branch information
Al2Klimov committed Nov 15, 2023
1 parent 85bde9e commit e99a39b
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 1 deletion.
2 changes: 1 addition & 1 deletion pkg/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func (d Driver) OpenConnector(name string) (driver.Connector, error) {

// Register makes our database Driver available under the name "icingadb-*sql".
func Register(logger *logging.Logger) {
sql.Register(MySQL, &Driver{ctxDriver: &mysql.MySQLDriver{}, Logger: logger})
sql.Register(MySQL, &Driver{ctxDriver: &mySQLDriver{}, Logger: logger})
sql.Register(PostgreSQL, &Driver{ctxDriver: PgSQLDriver{}, Logger: logger})
_ = mysql.SetLogger(mysqlLogger(func(v ...interface{}) { logger.Debug(v...) }))
sqlx.BindDriver(PostgreSQL, sqlx.DOLLAR)
Expand Down
84 changes: 84 additions & 0 deletions pkg/driver/mysql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package driver

import (
"context"
"database/sql/driver"
"github.com/go-sql-driver/mysql"
"github.com/pkg/errors"
)

// mySQLDriver extends mysql.MySQLDriver with auto-SETting Galera cluster options.
type mySQLDriver struct {
mysql.MySQLDriver
}

// Open implements the driver.Driver interface.
func (md mySQLDriver) Open(name string) (driver.Conn, error) {
return setGaleraOpts(md.MySQLDriver.Open(name))
}

// OpenConnector implements the driver.DriverContext interface.
func (md mySQLDriver) OpenConnector(name string) (driver.Connector, error) {
connector, err := md.MySQLDriver.OpenConnector(name)
if err != nil {
return nil, err
}

return &galeraAwareConnector{connector, md}, nil
}

// galeraAwareConnector extends mysql.connector with auto-SETting Galera cluster options.
type galeraAwareConnector struct {
driver.Connector

driver driver.Driver
}

// Connect implements the driver.Connector interface.
func (gac *galeraAwareConnector) Connect(ctx context.Context) (driver.Conn, error) {
return setGaleraOpts(gac.Connector.Connect(ctx))
}

// Driver implements the driver.Connector interface.
func (gac *galeraAwareConnector) Driver() driver.Driver {
return gac.driver
}

// setGaleraOpts tries SET SESSION wsrep_sync_wait=4 on conn if err != nil.
// It closes conn on failure (as mysql.mysqlConn#handleParams() does).
// Error 1193 "Unknown system variable" is ignored to support MySQL single nodes.
func setGaleraOpts(conn driver.Conn, err error) (driver.Conn, error) {
const galeraOpts = "SET SESSION wsrep_sync_wait=4"

if err == nil {
var stmt driver.Stmt
if stmt, err = conn.Prepare(galeraOpts); err != nil {
err = errors.Wrap(err, "can't prepare "+galeraOpts)
//lint:ignore SA1019 StmtExecContext isn't mandatory, would fall back anyway
} else if _, err = stmt.Exec(nil); err != nil {
err = errors.Wrap(err, "can't execute "+galeraOpts)
_ = stmt.Close()
} else if err = stmt.Close(); err != nil {
err = errors.Wrap(err, "can't close statement "+galeraOpts)
}

if err != nil {
var me *mysql.MySQLError
if errors.As(err, &me) && me.Number == 1193 { // Unknown system variable
err = nil
} else {
_ = conn.Close()
conn = nil
}
}
}

return conn, err
}

// Assert interface compliance.
var (
_ driver.Driver = mySQLDriver{}
_ driver.DriverContext = mySQLDriver{}
_ driver.Connector = (*galeraAwareConnector)(nil)
)

0 comments on commit e99a39b

Please sign in to comment.