Skip to content

Commit

Permalink
HA: Use ... FOR UPDATE lock clause unconditionally
Browse files Browse the repository at this point in the history
  • Loading branch information
yhabteab committed Oct 25, 2024
1 parent c9deda3 commit 04dd8ff
Showing 1 changed file with 12 additions and 12 deletions.
24 changes: 12 additions & 12 deletions pkg/icingadb/ha.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,28 +290,28 @@ func (h *HA) realize(
takeover = ""
otherResponsible = false
isoLvl := sql.LevelSerializable
selectLock := ""

if h.db.DriverName() == database.MySQL {
// The RDBMS may actually be a Percona XtraDB Cluster which doesn't
// support serializable transactions, but only their equivalent SELECT ... LOCK IN SHARE MODE.
// However, in order to reduce the deadlocks on both sides, it is necessary to obtain an exclusive lock
// on the selected rows. This can be achieved by utilising the SELECT ... FOR UPDATE command.
// Nevertheless, deadlocks may still occur, when the "icingadb_instance" table is empty, i.e. when
// there's no available row to be locked exclusively.
//
// See also: https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
// The RDBMS may actually be a Percona XtraDB Cluster which doesn't support serializable
// transactions, but only their equivalent SELECT ... LOCK IN SHARE MODE.
// See https://dev.mysql.com/doc/refman/8.4/en/innodb-transaction-isolation-levels.html#isolevel_serializable
isoLvl = sql.LevelRepeatableRead
selectLock = " FOR UPDATE"
}

tx, errBegin := h.db.BeginTxx(ctx, &sql.TxOptions{Isolation: isoLvl})
if errBegin != nil {
return errors.Wrap(errBegin, "can't start transaction")
}

query := h.db.Rebind("SELECT id, heartbeat FROM icingadb_instance "+
"WHERE environment_id = ? AND responsible = ? AND id <> ?") + selectLock
// In order to reduce the deadlocks on both sides, it is necessary to obtain an exclusive lock
// on the selected rows. This can be achieved by utilising the SELECT ... FOR UPDATE command.
// Nevertheless, deadlocks may still occur, when the "icingadb_instance" table is empty, i.e. when
// there's no available row to be locked exclusively.
//
// Note that even without the ... FOR UPDATE lock clause, this shouldn't cause a deadlock on PostgreSQL.
// Instead, it triggers a read/write serialization failure when attempting to commit the transaction.
query := h.db.Rebind("SELECT id, heartbeat FROM icingadb_instance " +
"WHERE environment_id = ? AND responsible = ? AND id <> ? FOR UPDATE")

instance := &v1.IcingadbInstance{}
errQuery := tx.QueryRowxContext(ctx, query, envId, "y", h.instanceId).StructScan(instance)
Expand Down

0 comments on commit 04dd8ff

Please sign in to comment.