Skip to content

fix: auto-recover from CHANGE MASTER errno 1201 via RESET SLAVE ALL retry - #2

Merged
xcompass merged 1 commit into
mainfrom
fix/changemaster-recover-from-1201
May 14, 2026
Merged

fix: auto-recover from CHANGE MASTER errno 1201 via RESET SLAVE ALL retry#2
xcompass merged 1 commit into
mainfrom
fix/changemaster-recover-from-1201

Conversation

@xcompass

Copy link
Copy Markdown
Member

Summary

When a previous failed switchover leaves a pod with truncated master.info /
relay-log.info files (a 0-byte file is the field-observed signature), the
next CHANGE MASTER TO against the default replication channel errors with:

Error 1201 (HY000): Could not initialize master info structure for ''

The operator's switchover state machine loops on this error every reconcile,
forever, because nothing in the existing path tries to repair the residual
slave-channel state.

Add a one-shot recovery: detect errno 1201 from CHANGE MASTER, call
RESET SLAVE ALL to clear the corrupt on-disk state, and retry once. This
is safe because the residual state is exactly what the in-flight
ConfigureReplica is about to overwrite — clearing it removes the
corruption barrier without losing any meaningful information.

Field incident

moodle-education-stg-db, 2026-05-14: a previous switchover left pod-1
with master.info truncated to 0 bytes. The operator looped on
changePrimaryToReplica → CHANGE MASTER TO → errno 1201 every ~10 seconds.
Cluster was wedged with primary on the wrong pod until manual
STOP SLAVE; RESET SLAVE ALL; cleared the state on pod-1, after which the
next operator-issued CHANGE MASTER TO succeeded and the switchover
completed in seconds.

What this PR does

Layer Change
pkg/sql/sql.go Add named constant ErrCodeMasterInfo = 1201 and helper IsMySQLErrorCode(err, code) using errors.As for typed-error detection through wrapping.
pkg/controller/replication/config.go Wrap CHANGE MASTER in changeMaster(): on errno 1201, call ResetAllSlaves and retry once. All other errors propagate unchanged.

Failure modes the new path handles

Pre-existing pod state Old behaviour New behaviour
Clean replica CHANGE MASTER succeeds CHANGE MASTER succeeds (no extra calls)
Residual master.info from prior wedge errno 1201 returned forever RESET SLAVE ALL + retry → succeeds
CHANGE MASTER fails for any other reason error returned error returned (unchanged)
RESET SLAVE ALL itself fails n/a combined error surfaced with both messages

Test plan

  • pkg/sql/sql_test.go: TestIsMySQLErrorCode — 8 table-driven cases covering nil, non-driver, wrapped (%w / %v), single vs nested wrapping, matching/non-matching codes. All pass with -race.
  • pkg/controller/replication/switchover_test.go: documented design record for the end-to-end recovery path, matching the file's existing integration-test-only convention for live-SQL paths.
  • go test ./... -race, go vet ./..., gofmt all clean.
  • Full go build ./... clean.
  • Field validation on staging cluster after merge (the original incident is already resolved by manual intervention; verification will be on the next time a switchover phase 4-6 sequence is interrupted)

Non-goals

This does not change the upstream cause of the master.info truncation
(a switchover failing between phases 4-6). That root-cause fix belongs to
a different PR; this PR only ensures the cluster can self-heal on the
next reconcile instead of wedging indefinitely.

It also does not blanket-enable RESET SLAVE ALL on every CHANGE MASTER —
only the narrowly-targeted errno 1201 path triggers it, so a CHANGE
MASTER that fails for unrelated reasons (auth, network, syntax) still
surfaces the original error cleanly.

…etry

Field incident on moodle-education-stg-db (2026-05-14): a previous failed
switchover left pod-1 (the old primary) with a 0-byte master.info file. On
every subsequent reconcile the switchover state machine reached phase 6
(changePrimaryToReplica), issued CHANGE MASTER TO with the default channel
name to demote pod-1 to a replica of pod-0, and got rejected with:

  Error 1201 (HY000): Could not initialize master info structure for '';
  more error messages can be found in the MariaDB error log

The error path returned without recovery, the reconcile failed, and the
loop repeated indefinitely. Manual recovery required executing
`STOP SLAVE; RESET SLAVE ALL;` on pod-1 between reconciles to clear the
residual slave-channel state, after which the operator's next CHANGE
MASTER TO succeeded and the switchover completed in seconds.

The condition is structural, not transient: any switchover that completes
phase 4 (which runs RESET MASTER / RESET SLAVE ALL on the new primary)
but fails partway through phase 5 or 6 can leave the old primary's data
dir with truncated master.info / relay-log.info. On the next retry,
CHANGE MASTER cannot rebind the in-memory master_info structure from the
inconsistent on-disk state.

Recovery is mechanical and safe: RESET SLAVE ALL clears precisely the
slave-channel state that CHANGE MASTER is about to overwrite anyway, so
running it as a one-shot retry before the second CHANGE MASTER costs
nothing in the healthy case and unblocks the wedge in the broken case.

Add an error-code classifier in pkg/sql (sql.IsMySQLErrorCode, plus a
named constant sql.ErrCodeMasterInfo = 1201) and wrap the CHANGE MASTER
call in pkg/controller/replication/config.go:changeMaster so that:

  - on err == nil, return immediately (no behaviour change)
  - on err matching errno 1201, call ResetAllSlaves once and retry
    CHANGE MASTER, returning the retry result
  - on any other err, return the wrapped error unchanged

The classifier uses errors.As so wrapped errors anywhere in the chain are
detected, matching the standard library idiom for typed errors.

Tests:
- pkg/sql/sql_test.go: TestIsMySQLErrorCode covers nil, non-driver,
  wrapped/unwrapped driver errors with matching and non-matching codes,
  and the %v-vs-%w wrapping distinction (eight table-driven cases).
- pkg/controller/replication/switchover_test.go: documented design record
  for the end-to-end recovery path, following this file's existing
  convention for paths that need a live SQL connection.
@xcompass
xcompass merged commit 068ecf7 into main May 14, 2026
1 check passed
@xcompass
xcompass deleted the fix/changemaster-recover-from-1201 branch May 14, 2026 17:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant