Skip to content

Commit d7183d1

Browse files
DISTMYSQL-396: Fix for DISTMYSQL-243 can lead to Orchestrator hitting duplicate keys
https://perconadev.atlassian.net/browse/DISTMYSQL-396 Fix for DISTMYSQL-243 didn't consider the case when two clusters have the same suggested custer aliases (the value of it can originate from ClusterNameToAlias, DetectClusterAliasQuery or be set manually by the user). In such a case we end up with the situation like: > create table t1 (a int primary key, b int, unique key (b)); > insert into t1 values (0, 1); > insert into t1 values (1, 2); > insert into t1 values (0, 2) on duplicate key update a=0, b=2; ERROR 1062 (23000): Duplicate entry '2' for key 't1.b' The fix implements the fallback to REPLACE INTO approach in case of the failure of the optimized approach using INSERT INTO ... ON DUPLICATE KEY.
1 parent f13d2ba commit d7183d1

File tree

2 files changed

+87
-50
lines changed

2 files changed

+87
-50
lines changed

go/inst/cluster_alias_dao.go

Lines changed: 86 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -114,65 +114,102 @@ func writeClusterAliasManualOverride(clusterName string, alias string) error {
114114
return ExecDBWriteFunc(writeFunc)
115115
}
116116

117+
// Original, safe approach, which uses REPLACE INTO
118+
func updateClusterAliasesUsingReplace() error {
119+
_, err := db.ExecOrchestrator(`
120+
replace into
121+
cluster_alias (alias, cluster_name, last_registered)
122+
select
123+
suggested_cluster_alias,
124+
cluster_name,
125+
now()
126+
from
127+
database_instance
128+
left join database_instance_downtime using (hostname, port)
129+
where
130+
suggested_cluster_alias!=''
131+
/* exclude newly demoted, downtimed masters */
132+
and ifnull(
133+
database_instance_downtime.downtime_active = 1
134+
and database_instance_downtime.end_timestamp > now()
135+
and database_instance_downtime.reason = ?
136+
, 0) = 0
137+
order by
138+
ifnull(last_checked <= last_seen, 0) asc,
139+
read_only desc,
140+
num_slave_hosts asc
141+
`, DowntimeLostInRecoveryMessage)
142+
143+
return err
144+
}
145+
146+
// Optimized approach using INSERT INTO ... ON DUPLICATE KEY UPDATE
147+
// While this approach is much faster and works in most cases, it is not
148+
// guaranteed to be working in every case.
149+
// cluster_alias table has two unique indexes:
150+
// 1. primary on cluster_name column
151+
// 2. alias_uidx on alias colum
152+
//
153+
// The data which is going to be inserted originates from database_instance
154+
// table, in particular the following columns:
155+
// 1. `cluster_name` varchar(128) NOT NULL
156+
// 2. `suggested_cluster_alias` varchar(128) CHARACTER SET ascii COLLATE
157+
// ascii_general_ci NOT NULL
158+
//
159+
// So it is possible to end up in the following situation when we use this
160+
// approach:
161+
// create table t1 (a int primary key, b int, unique key (b));
162+
// insert into t1 values (0, 1);
163+
// insert into t1 values (1, 2);
164+
// insert into t1 values (0, 2) on duplicate key update a=0, b=2;
165+
// ERROR 1062 (23000): Duplicate entry '2' for key 't1.b'
166+
func updateClusterAliasesUsingInsert() error {
167+
_, err := db.ExecOrchestrator(`
168+
INSERT INTO cluster_alias
169+
(
170+
alias,
171+
cluster_name,
172+
last_registered
173+
)
174+
SELECT di.suggested_cluster_alias,
175+
di.cluster_name,
176+
now()
177+
FROM database_instance di
178+
LEFT JOIN database_instance_downtime did
179+
USING (hostname, port)
180+
WHERE di.suggested_cluster_alias != ''
181+
/* exclude newly demoted, downtimed masters */
182+
AND Ifnull(did.downtime_active = 1
183+
AND did.end_timestamp > Now()
184+
AND did.reason = ?, 0) = 0
185+
ORDER BY Ifnull(di.last_checked <= di.last_seen, 0) ASC,
186+
di.read_only DESC,
187+
di.num_slave_hosts ASC
188+
ON DUPLICATE KEY
189+
UPDATE alias = di.suggested_cluster_alias,
190+
cluster_name = di.cluster_name,
191+
last_registered = now()
192+
`, DowntimeLostInRecoveryMessage)
193+
194+
return err
195+
}
196+
117197
// UpdateClusterAliases writes down the cluster_alias table based on information
118198
// gained from database_instance
119199
func UpdateClusterAliases() error {
120200
writeFunc := func() error {
121201
var err error
122202
if IsSQLite() {
123203
// Sql lite backend
124-
_, err = db.ExecOrchestrator(`
125-
replace into
126-
cluster_alias (alias, cluster_name, last_registered)
127-
select
128-
suggested_cluster_alias,
129-
cluster_name,
130-
now()
131-
from
132-
database_instance
133-
left join database_instance_downtime using (hostname, port)
134-
where
135-
suggested_cluster_alias!=''
136-
/* exclude newly demoted, downtimed masters */
137-
and ifnull(
138-
database_instance_downtime.downtime_active = 1
139-
and database_instance_downtime.end_timestamp > now()
140-
and database_instance_downtime.reason = ?
141-
, 0) = 0
142-
order by
143-
ifnull(last_checked <= last_seen, 0) asc,
144-
read_only desc,
145-
num_slave_hosts asc
146-
`, DowntimeLostInRecoveryMessage)
204+
err = updateClusterAliasesUsingReplace()
147205
} else {
148206
// MySQL backend (Orchestrator supports only SQLite and MySQL backends)
149207
// INSERT ON DUPLICATE KEY UPDATE is more performant than REPLACE in MySQL
150-
_, err = db.ExecOrchestrator(`
151-
INSERT INTO cluster_alias
152-
(
153-
alias,
154-
cluster_name,
155-
last_registered
156-
)
157-
SELECT di.suggested_cluster_alias,
158-
di.cluster_name,
159-
now()
160-
FROM database_instance di
161-
LEFT JOIN database_instance_downtime did
162-
USING (hostname, port)
163-
WHERE di.suggested_cluster_alias != ''
164-
/* exclude newly demoted, downtimed masters */
165-
AND Ifnull(did.downtime_active = 1
166-
AND did.end_timestamp > Now()
167-
AND did.reason = ?, 0) = 0
168-
ORDER BY Ifnull(di.last_checked <= di.last_seen, 0) ASC,
169-
di.read_only DESC,
170-
di.num_slave_hosts ASC
171-
ON DUPLICATE KEY
172-
UPDATE alias = di.suggested_cluster_alias,
173-
cluster_name = di.cluster_name,
174-
last_registered = now()
175-
`, DowntimeLostInRecoveryMessage)
208+
err = updateClusterAliasesUsingInsert()
209+
if (err != nil) {
210+
// Fallback to the original, safe implementation
211+
err = updateClusterAliasesUsingReplace()
212+
}
176213
}
177214
return log.Errore(err)
178215
}

script/ensure-go-installed

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/bin/bash
22

33
PREFERRED_GO_VERSION=go1.20.3
4-
SUPPORTED_GO_VERSIONS='go1.1[6789]|go1.2[0]'
4+
SUPPORTED_GO_VERSIONS='go1.1[6789]|go1.2[01]'
55

66
export ROOTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )"
77
cd $ROOTDIR

0 commit comments

Comments
 (0)