Skip to content

Commit c5f014c

Browse files
DISTMYSQL-243 - Orchestrator uses inefficient subquery in REPLACE to update cluster aliases
Problem: -------- Orchestrator has backend table cluster_alias to store aliases. At certain intervals, orchestrator will update the aliases or insert new host aliases. To do this, it uses the below query in UpdateClusterAliases(): ``` replace into cluster_alias (alias, cluster_name, last_registered) select suggested_cluster_alias, cluster_name, now() from database_instance left join database_instance_downtime using (hostname, port) where suggested_cluster_alias!='' /* exclude newly demoted, downtimed masters */ and ifnull( database_instance_downtime.downtime_active = 1 and database_instance_downtime.end_timestamp > now() and database_instance_downtime.reason = ? , 0) = 0 order by ifnull(last_checked <= last_seen, 0) asc, read_only desc, num_slave_hosts asc ``` The problem with the select query is, it will generate the duplicate records. Note that the "order" of records is important to determine the active nodes and the alias should only be updated to active nodes. REPLACE does this operation by doing DELETE+INSERT. REPLACE repeatedly does the same work for all the duplicated records. This creates un-necessary work for two sub systems in InnoDB 1. Purge 2. SELECTS (ReadView) All those delete marked records create stress on Purge. SELECTs, when they have to build a older version of record, they have to build a long chain of old version records (using undo log). So the un-necessary REPLACE work will create a long chain of records to be built. Fix: ---- Since the order is important, we cannot use "GROUP BY" to remove duplicates. Use Insert On Duplicate Key Update (IODKU) instead of REPLACE as it generates less work for InnoDB IODKU: ------ ``` INSERT INTO cluster_alias(alias, cluster_name, last_registered) SELECT di.suggested_cluster_alias, di.cluster_name, now() FROM database_instance di LEFT JOIN database_instance_downtime did USING (hostname, port) WHERE di.suggested_cluster_alias != '' AND Ifnull(did.downtime_active = 1 AND did.end_timestamp > Now() AND did.reason = 'lost-in-recovery', 0) = 0 ORDER BY Ifnull(di.last_checked <= di.last_seen, 0) ASC, di.read_only DESC, di.num_slave_hosts ASC ON DUPLICATE KEY UPDATE alias = di.suggested_cluster_alias, cluster_name = di.cluster_name, last_registered = now(); ``` Sample Performance numbers: ---------------------------- REPLACE: Query OK, 14880 rows affected (16.93 sec) Records: 7440 Duplicates: 7440 Warnings: 0 IODKU: Query OK, 468 rows affected (6.17 sec) Records: 7440 Duplicates: 234 Warnings: 0 Observe the number of records affected,number of duplicates affected and the time. IODKU is way better.
1 parent 982c07f commit c5f014c

File tree

1 file changed

+60
-23
lines changed

1 file changed

+60
-23
lines changed

go/inst/cluster_alias_dao.go

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ import (
2525
"github.com/openark/orchestrator/go/db"
2626
)
2727

28+
func IsSQLite() bool {
29+
return config.Config.IsSQLite()
30+
}
31+
2832
// ReadClusterNameByAlias
2933
func ReadClusterNameByAlias(alias string) (clusterName string, err error) {
3034
query := `
@@ -114,29 +118,62 @@ func writeClusterAliasManualOverride(clusterName string, alias string) error {
114118
// gained from database_instance
115119
func UpdateClusterAliases() error {
116120
writeFunc := func() error {
117-
_, err := db.ExecOrchestrator(`
118-
replace into
119-
cluster_alias (alias, cluster_name, last_registered)
120-
select
121-
suggested_cluster_alias,
122-
cluster_name,
123-
now()
124-
from
125-
database_instance
126-
left join database_instance_downtime using (hostname, port)
127-
where
128-
suggested_cluster_alias!=''
129-
/* exclude newly demoted, downtimed masters */
130-
and ifnull(
131-
database_instance_downtime.downtime_active = 1
132-
and database_instance_downtime.end_timestamp > now()
133-
and database_instance_downtime.reason = ?
134-
, 0) = 0
135-
order by
136-
ifnull(last_checked <= last_seen, 0) asc,
137-
read_only desc,
138-
num_slave_hosts asc
139-
`, DowntimeLostInRecoveryMessage)
121+
var err error
122+
if IsSQLite() {
123+
// 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)
147+
} else {
148+
// MySQL backend (Orchestrator supports only SQLite and MySQL backends)
149+
// 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)
176+
}
140177
return log.Errore(err)
141178
}
142179
if err := ExecDBWriteFunc(writeFunc); err != nil {

0 commit comments

Comments
 (0)