Scale-out storage: read replicas on MySQL, tested (ST-10, ST-15, tests 17 to 19)

Two source-and-replica pairs run in containers: one replicating with
GTIDs, one by binary log position. mysql_replica_tests covers test 17
(tests 9, 10 and 12 with GTIDs) and mysql_replica_position_tests covers
test 18 (lag from Seconds_Behind_Source, and a replica whose account
lacks REPLICATION CLIENT getting no reads) and test 19 (a parallel
replica without replica_preserve_commit_order left out at startup).

The lag reader now takes Seconds_Behind_Source whatever numeric type the
server returns, accepts the older column name, and says in the log why it
gave up measuring.
This commit is contained in:
2026-09-19 14:53:31 -07:00
parent 216bb5b711
commit e913a22b66
5 changed files with 475 additions and 6 deletions
@@ -410,17 +410,42 @@ async fn replica_lag(
let row: Option<mysql_async::Row> =
match conn.query_first("SHOW REPLICA STATUS").await {
Ok(row) => row,
Err(_) => return Ok(None),
Err(err) => {
report(
store.kind,
format!(
"Read replica {} won't report its status: {err}",
replica.label
),
);
return Ok(None);
}
};
let Some(row) = row else {
return Ok(None);
};
match row.get_opt::<Option<u64>, _>("Seconds_Behind_Source") {
Some(Ok(Some(seconds))) => Ok(Some(seconds * 1000)),
Some(Ok(None)) => Err(trc::StoreEvent::MysqlError
let behind = ["Seconds_Behind_Source", "Seconds_Behind_Master"]
.into_iter()
.find_map(|column| row.get_opt::<mysql_async::Value, _>(column))
.and_then(|value| value.ok());
match behind {
Some(mysql_async::Value::NULL) => Err(trc::StoreEvent::MysqlError
.into_err()
.details("Replication is stopped")),
_ => Ok(None),
Some(value) => match mysql_async::from_value_opt::<u64>(value.clone()) {
Ok(seconds) => Ok(Some(seconds * 1000)),
Err(_) => {
report(
store.kind,
format!(
"Read replica {}: Seconds_Behind_Source isn't a number ({value:?})",
replica.label
),
);
Ok(None)
}
},
None => Ok(None),
}
}
}