@@ -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
119199func 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 }
0 commit comments