A Go rewrite of MySQL MHA (Master High Availability). Automates failover and online switchover for GTID-based, single-primary MySQL replication topologies.
| Topic | Perl MHA 0.58 | mha-go |
|---|---|---|
| Implementation language | Perl | Go |
| Packaging model | Manager plus node tools | Single manager binary; agent/SSH paths are optional extension points |
| Primary compatibility goal | Broad historical MySQL/MariaDB-era deployments | Modern MySQL GTID single-primary replication |
| Supported MySQL baseline | Legacy versions, including non-GTID paths | MySQL 8.4.x release baseline; MySQL 9.7 ER/EA forward track |
| Replication positioning | File/position and GTID-era logic | GTID-only; no relay-log position model |
| State and history | Script output and manager logs | In-process run state plus structured log-file audit trail |
| Writer endpoint model | Typically handled by hook scripts such as VIP failover scripts | Dedicated writer_endpoint step with optional precheck and verify commands |
| Hook role | Operational scripts often carry critical switch behavior | Notification, audit, and compatibility callbacks; not the main VIP/proxy switch path |
| Fencing model | Mostly external scripts and operational convention | Explicit fencing steps with required/optional semantics |
| Execution model | Commands generally execute when invoked | switch and failover-execute execute by default; add --dry-run to preview |
| Persistence policy | No embedded state DB | No SQLite or embedded DB; persistent history belongs in logs |
| Controller HA model | Single active manager in normal operation | Single active manager by default, matching the Perl MHA operating model |
Legend: ✓ supported, - not supported by design, Partial implemented for the common path but not feature-complete.
| Area | Capability | Perl MHA 0.58 | mha-go |
|---|---|---|---|
| Deployment | Single self-contained binary | - | ✓ |
| Deployment | No Perl runtime dependency | - | ✓ |
| Deployment | No mandatory node package on every MySQL host | - | ✓ |
| Version scope | Legacy MySQL support | ✓ | - |
| Version scope | Explicit MySQL 8.4 release baseline | - | ✓ |
| Version scope | MySQL 9.7 ER/EA forward-compatibility track | - | Partial |
| Replication model | GTID-only safety model | - | ✓ |
| Replication model | Non-GTID / file-position failover | ✓ | - |
| Topology check | One-shot replication health check | ✓ | ✓ |
| Topology check | Capability-driven SQL discovery | - | ✓ |
| Failover | Automatic primary failure detection | ✓ | ✓ |
| Failover | Candidate priority / no-master controls | ✓ | ✓ |
| Failover | Typed, ordered failover plan before execution | - | ✓ |
| Failover | Explicit dry-run for write operations | - | ✓ |
| Recovery | Relay-log / binlog recovery through SSH node tools | ✓ | - |
| Recovery | GTID catch-up from SQL-accessible donors | - | ✓ |
| Recovery | SSH/node-tool binlog salvage for unreachable SQL paths | ✓ | Partial |
| Switchover | Online primary switchover | ✓ | ✓ |
| Writer endpoint | VIP/proxy switch by external command | ✓ | ✓ |
| Writer endpoint | Precheck before promotion | - | ✓ |
| Writer endpoint | Post-switch verify command | - | ✓ |
| Fencing | SQL read-only fence | Partial | ✓ |
| Fencing | Configurable required/optional fencing steps | - | ✓ |
| Hooks | Lifecycle shell callbacks | ✓ | ✓ |
| Hooks | Hooks used as the main VIP move path | ✓ | - |
| Observability | Structured logs for audit/history | - | ✓ |
| Secrets | Env/file/plain credential references | - | ✓ |
| Testing | Go unit tests and CI static builds | - | ✓ |
| Version | Status |
|---|---|
| MySQL 8.4.x | Primary target (release-blocking) |
| MySQL 9.7 ER/EA | Forward-compatibility target |
MySQL 5.7, 8.0, and 9.6 are not supported. GTID must be enabled on all nodes.
For MySQL 9.7.0, use the same configuration shape and set each node's
version_series to "9.7".
On all MySQL nodes, ensure GTID is enabled (my.cnf):
[mysqld]
gtid_mode = ON
enforce_gtid_consistency = ON
log_bin = ON
log_replica_updates = ONVerify:
SHOW VARIABLES WHERE Variable_name IN ('gtid_mode', 'enforce_gtid_consistency');Run on the primary (replicates to all replicas via GTID):
CREATE USER IF NOT EXISTS 'mha'@'<your-subnet>%'
IDENTIFIED BY '<strong-password>';
-- Operational privileges for health checks, switchover, and failover
GRANT SELECT,
RELOAD,
PROCESS,
SUPER,
REPLICATION CLIENT,
REPLICATION SLAVE,
REPLICATION_SLAVE_ADMIN,
SYSTEM_VARIABLES_ADMIN,
SESSION_VARIABLES_ADMIN
ON *.* TO 'mha'@'<your-subnet>%';
FLUSH PRIVILEGES;Tip: Replace
<your-subnet>with your network range (e.g.192.168.1.%or10.0.%).
Prebuilt binaries do not require Go. Building from source requires Go 1.25+.
Download a prebuilt Linux binary:
MHA_VERSION=v0.1.4
case "$(uname -m)" in
x86_64) ASSET="mha_${MHA_VERSION}_linux_amd64" ;;
aarch64|arm64) ASSET="mha_${MHA_VERSION}_linux_arm64" ;;
*) echo "unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac
curl -fL -o mha \
"https://github.com/fanderchan/mha_go/releases/download/${MHA_VERSION}/${ASSET}"
curl -fL -o SHA256SUMS \
"https://github.com/fanderchan/mha_go/releases/download/${MHA_VERSION}/SHA256SUMS"
grep " ${ASSET}$" SHA256SUMS | sha256sum -c -
chmod +x mhaOr build from source:
git clone git@github.com:fanderchan/mha_go.git
cd mha_go
# Dynamic build
go build -o mha ./cmd/mha
# Static build (recommended for deployment)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-extldflags=-static" -o mha ./cmd/mhaCopy and edit the example config:
cp examples/cluster-8.4.yaml /etc/mha/cluster.yamlexamples/cluster-8.4.yaml is the starter template. It intentionally leaves out
advanced sections such as VIP/proxy switching, custom fencing, hooks, and SSH
binlog salvage. Use examples/cluster-8.4.full.yaml
as the field-by-field reference when enabling those features.
For a MySQL 9.7.0 cluster, keep the same template and change every
version_series: "8.4" entry to version_series: "9.7".
Minimal configuration (cluster.yaml):
name: my-cluster
topology:
kind: mysql-replication-single-primary
replication:
mode: gtid
semi_sync:
policy: disabled
nodes:
- id: db1
host: 10.0.0.11
port: 3306
version_series: "8.4" # use "9.7" for MySQL 9.7.0
expected_role: primary
sql:
user: mha
password_ref: file:/etc/mha/secrets/admin-password
replication_user: repl
replication_password_ref: file:/etc/mha/secrets/repl-password
- id: db2
host: 10.0.0.12
port: 3306
version_series: "8.4" # use "9.7" for MySQL 9.7.0
expected_role: replica
candidate_priority: 100
sql:
user: mha
password_ref: file:/etc/mha/secrets/admin-password
replication_user: repl
replication_password_ref: file:/etc/mha/secrets/repl-password
- id: db3
host: 10.0.0.13
port: 3306
version_series: "8.4" # use "9.7" for MySQL 9.7.0
expected_role: replica
candidate_priority: 90
sql:
user: mha
password_ref: file:/etc/mha/secrets/admin-password
replication_user: repl
replication_password_ref: file:/etc/mha/secrets/repl-passwordCreate the secret files referenced by the config. Keep credentials out of the
cluster YAML and out of systemd Environment= lines:
install -d -m 0700 /etc/mha/secrets
printf '%s\n' 'your-admin-password' > /etc/mha/secrets/admin-password
printf '%s\n' 'your-replication-password' > /etc/mha/secrets/repl-password
chmod 0600 /etc/mha/secrets/admin-password /etc/mha/secrets/repl-password
chown root:root /etc/mha/secrets/admin-password /etc/mha/secrets/repl-password./mha check-repl --config /etc/mha/cluster.yamlExpected output:
Cluster: my-cluster mode=mysql-replication-single-primary primary=db1 nodes=3
- db1 role=primary health=alive addr=10.0.0.11:3306 ro=false sro=false
- db2 role=replica health=alive addr=10.0.0.12:3306 ro=true sro=true
replica: source=db1 io=true sql=true lag=0s autopos=true
- db3 role=replica health=alive addr=10.0.0.13:3306 ro=true sro=true
replica: source=db1 io=true sql=true lag=0s autopos=true
Assessment: OK
# Online switchover (preview first, then execute)
./mha switch --config /etc/mha/cluster.yaml --new-primary db2 --dry-run
./mha switch --config /etc/mha/cluster.yaml --new-primary db2
# Failover planning and execution
./mha failover-plan --config /etc/mha/cluster.yaml
./mha failover-execute --config /etc/mha/cluster.yaml
# Start the HA monitor daemon
./mha manager --config /etc/mha/cluster.yaml| Command | Description |
|---|---|
check-repl |
One-shot topology and replication health check |
manager |
Long-running HA monitor; triggers automatic failover on primary death |
switch |
Online (graceful) switchover to a specified or best-available replica |
failover-plan |
Build and display a failover plan without executing |
failover-execute |
Build and execute a failover plan |
Operational subcommands (check-repl, manager, switch, failover-plan, and failover-execute) accept:
--config <file>— cluster config file (required)--discoverer sql|static(defaultsql)--log-level debug|info|warn|error(defaultinfo)--log-format text|json(defaulttext)
manager, switch, and failover-execute also accept --dry-run for preview/no-write mode. switch and failover-execute execute by default when --dry-run is omitted.
The sql.password_ref and sql.replication_password_ref fields support three schemes:
| Scheme | Example | Notes |
|---|---|---|
file:/path |
file:/etc/mha/secrets/admin-password |
Recommended for production on hosts/VMs. Protect the file with owner and mode, for example root:root 0600. Trailing newline is stripped. |
env:VAR |
env:MHA_ADMIN_PASSWORD |
Useful for short-lived tests, containers, or external secret injection. Avoid putting secrets in shell history or systemd Environment= lines. |
plain:value |
plain:s3cr3t |
Literal value. Use only for local demos/tests; do not use in production. |
# /etc/systemd/system/mha-manager.service
[Unit]
Description=MHA Go Manager
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/mha manager --config /etc/mha/cluster.yaml --log-format json
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.targetThe service above assumes password_ref: file:/etc/mha/secrets/... in the
cluster config. Avoid storing SQL passwords in Environment= because process
environments and unit files are easier to expose during troubleshooting.
systemctl daemon-reload
systemctl enable --now mha-manager
journalctl -u mha-manager -fNote: The manager exits after a successful failover. Update the config to reflect the new topology, verify it with
check-repl, then restart the service explicitly.Restart=on-failureonly covers crashes or non-zero exits.
| Document | Description |
|---|---|
| Operations Guide | Full config reference, MySQL prerequisites, all workflows |
| Architecture Blueprint | Design decisions and module responsibilities |
| Deployment Guide | Step-by-step deployment with dbbot |
| Testing Guide | Unit, CI, local MySQL 8.4 integration tests, and MySQL 9.7 validation notes |
| Changelog | Release history |
| Example: MySQL 8.4 starter | Small starter config for a 3-node cluster |
| Example: MySQL 8.4 full reference | Verbose field-by-field annotated config |
Apache License 2.0 — see LICENSE for details.