Self-fencing: the sidecar's two rules

The sidecar stops its own MySQL writing when it can no longer confirm it is the active site. Two rules, evaluated in a fixed order, plus a startup safety net that is not a rule at all.

By the end of this topic you can

  1. Name the two rules the fencing monitor evaluates each tick and the order it evaluates them in
  2. Explain why one reachable peer keeps a primary writable, and why that is not a quorum
  3. Say what the startup safety net does before MySQL is allowed to accept writes

Scale the operator to zero and watch playground carry on:

kubectl -n bloodraven-playground scale deployment/bloodraven --replicas=0

Press + Increment: the write still lands on iad. Nothing pages, nothing promotes, nothing fences. That is correct and it is the point — “A healthy primary and replica keep serving reads and writes with zero operator involvement. The operator is on the failure-detection and promotion path, not the request path.” So ask the opposite question. With the operator gone, what would ever stop iad accepting writes if it could no longer confirm it is still the active site? Not the operator. The sidecar.

Two rules, evaluated in order

Every MySQL pod runs a bloodraven-sidecar container, and inside it a FencingMonitor. Each tick it pings the operator’s /healthz, reads the operator’s /active-site, pings every peer sidecar’s /peer/ping, reads every peer’s /peer/active-site — and then evaluates. Evaluation runs two rules, in FencingMonitor.evaluate (internal/sidecar/fencing.go):

Order is the lesson. Rule #1 fires and returns. A site that has learned the active site is somebody else fences without ever consulting the lease — which is exactly the stale-primary case, where peers are perfectly reachable and rule #2 would stay quiet forever.

Before either rule, the monitor reads @@read_only and returns early if it is set. A read-only instance never self-fences, because there is nothing left to fence. That single line is why the replica’s monitor is a permanent no-op, and why “the sidecar fenced it” is never the explanation for a site that was already read-only.

The replica’s cache is not idle, though: it keeps the operator’s answer, and until the moment of a failover that answer names the old primary. So when the monitor sees its own @@read_only go from 1 to 0 with no failed probe in between — only an actor with SUPER does that, which means a promotion — it drops every view it held from before, and grants itself a fresh lease, exactly as it does when the operator lifts a self-fence. It logs fencing: MySQL turned writable; discarding active-site view from before the promotion. Without it, an operator that died right after promoting this site would leave the new primary to fence itself on rule #1, with the old primary down and nobody left to promote it again. A failed probe in between breaks the chain on purpose: that writable answer may come from a restarted mysqld, which is not a promotion. A live operator’s next answer is still newer than the reset, so a replica someone unfences by hand is fenced on the following tick.

Two more early exits sit in front of the rules. If MySQL does not answer that read, the tick ends there: a sidecar that cannot reach its own MySQL cannot fence it either. And if a CLONE INSTANCE is running on this site, the monitor skips both rules and never kills the clone session, logging fencing: clone in progress on this site; not fencing. The operator also keeps a clone recipient at read_only=ON, clearing only super_read_only, so the read-only exit normally catches it first.

Sidecar decision tree. Can I still confirm I am the primary? Yes stays writable. No sets super_read_only. Two rules stacked: topology mismatch first, lease expiry second.
The sidecar decides locally. It does not ask the operator, and it does not need a quorum.
OrderOne FencingMonitor tick

Put these in the order they happen.

    1. Read @@read_only — Already read-only, or MySQL not answering? Return. A read-only instance has nothing left to fence, and one the sidecar cannot reach cannot be fenced, so the rest of the tick is skipped entirely.
    1. Rule #1 — topology mismatch — Cached authoritative activeSite is non-empty and != mySite? Fence and return. The lease is never consulted.
    1. Rule #2 — lease expiry — Operator silent beyond leaseTimeout AND every peer silent beyond leaseTimeout? Fence. Either one answering suppresses it.
    1. Otherwise, nothing — No third rule exists. The monitor takes no action and waits for the next peerCheckInterval tick.

The thing that is not a rule

Server.RunSafetyNet (internal/sidecar/server.go) is a separate startup step, called from cmd/sidecar/main.go after the HTTP server is up and before the FencingMonitor starts ticking. It is not a third rule. Anything that presents it beside the two rules is describing what happens when the sidecar or its MySQL starts, not the steady state.

It is also bounded. Each of its MySQL writes gives up after 3 s, so a hung mysqld cannot hold the sidecar’s startup hostage. If the fence (or, on the active site, the clear) does not land in time, the safety net is handed to the monitor, which finishes the same rule on the first tick where MySQL answers. The monitor also re-arms it after MySQL has been unreachable for 30 s or more, because a mysqld gone that long may have restarted. A restarted mysqld comes back with the read_only=ON it is started with, which stops application users but not a SUPER session; the re-armed check turns that into the real fence, super_read_only=ON, unless the operator names this site active. Those deferred runs log under the same safety net: prefix, with their own msg strings.

It fences first and asks afterwards. On boot it sets super_read_only=ON, then queries the operator for the active site, and only clears the fence if the answer names this site. So it fails closed by staying fenced rather than by actively fencing — and when the startup fence lands it has three distinct fenced exits, each with its own log line:

Log line (verbatim)What actually happened
safety net: could not query active site, staying fencedThe operator was unreachable or answered badly. The sidecar refuses to guess.
safety net: no active site reported by operator, staying fencedThe operator answered, and admitted it has no active site yet.
safety net: confirmed standby site, staying fencedThe operator answered with a different site. This pod is a standby and stays that way.

Learn the prefixes, because in a log bundle they mean different things. A safety net: line is a pod that has never been allowed to write since it, or its mysqld, started. A SELF-FENCING: / SELF-FENCED: line from the monitor is a pod that was writing and lost the argument. Same read-only outcome, opposite incident.

Where the belief comes from: Adopt versus Set

The monitor’s TopologyCache has two writers with deliberately different tempers (internal/sidecar/topology_cache.go):

That asymmetry is the entire trust model, expressed in two method names. Peers are a relay, not an authority.

The lease numbers, and the trap inside them

spec.sidecar.leaseTimeout defaults to 20s; spec.sidecar.peerCheckInterval defaults to 5s. Three CEL rules guard them at admission: peerCheckInterval between 1s and 1m, leaseTimeout between 3s and 10m, and leaseTimeout >= 3 × peerCheckInterval. The shipped pair sits exactly on the 3× floor — 3 × 5 s = 15 s, which 20 s clears with 5 s to spare — so a lease covers 20 / 5 = 4 consecutive ticks of silence. Raise peerCheckInterval to 10 s alone and 3 × 10 s = 30 s > 20 s: the API server rejects the object. You must move both.

Now the hard part. Rule #2 requires the operator and every peer to be silent for the whole window. One reachable peer keeps the primary writable. That is documented as “retained compatibility behavior, not a quorum guarantee” — no counting, no majority, no tie-break. And a role: ReadOnly reader counts as a peer: it relays topology and answers /peer/ping, and “A reachable peer without fresh authoritative topology can still suppress the lease-only all-peers-unreachable fence.”

So adding a reader makes the lease fence less likely to fire. On playground today, iad has two peers — pdx and reader — which means rule #2 needs three parties (operator + 2 peers) silent for the full 20 s, up from two before the reader existed. Sit with that before you size your next group. A reader you added for read scaling has quietly widened the window in which an isolated primary keeps accepting writes. It has not made rule #1 weaker — but rule #1 needs someone to tell it the truth.

What fencing actually does to MySQL

SET GLOBAL super_read_only = ON is not a switch that cuts writers off.

You can now look at a read-only site and name the cause: rule #1 if a live operator disagrees about the active site, rule #2 only if operator and every peer went quiet, and the startup safety net if the pod never got permission in the first place. Next question: what happens when two sites both believe they are right at the same time?

Flashcards

FencingMonitor rule #1

Topology mismatch: the cached operator-authoritative active site is non-empty and is not this site, so fence immediately and return without consulting the lease.

1 / 10

Quiz

Question 1 of 5

The pdx sidecar in playground logs SELF-FENCING: ... setting super_read_only=ON. From the same bundle you can see the operator pod was Running and answering /healthz throughout, and pdx was successfully pinging the iad and reader sidecars the whole time. Which rule fired?

Show answer

Answer: Rule #1 — the cached authoritative active site was a different site to pdx

Rule #2 needs the operator AND every peer silent beyond leaseTimeout; here both were answering, so it cannot have fired — that rules out both rule #2 options. The second option also misdescribes the lease: it tracks reachability of /healthz and /peer/ping, not any per-site role confirmation. The third option confuses two endpoints — a failed /active-site read leaves the topology cache to age silently and is not a lease signal at all. The safety net is out because every line it writes is prefixed safety net:, whether it runs at startup or the monitor finishes it later; a SELF-FENCING: line can only come from the monitor's two rules. That leaves rule #1: the monitor learned, from the operator or relayed by a peer, that the active site is somebody else, and fenced immediately without consulting the lease. (objective 1)

Question 2 of 5

A site is read-only and its sidecar log contains exactly one relevant line: safety net: confirmed standby site, staying fenced. What happened?

Show answer

Answer: The pod started, fenced itself before asking anything, and the operator named a different site as active — it has never been allowed to write

This exact line comes only from the startup safety net, which runs before the monitor starts evaluating rules, so no rule fired and the pod never held writes — that eliminates the rule #1 and rule #2 options, both of which describe a pod that was writing and lost the argument (and both of which would log SELF-FENCING:). The 'could not reach the operator' option names a real safety-net exit, but a different one: that path logs safety net: could not query active site, staying fenced. confirmed standby site means the operator answered, and named a site other than this one. The distinction matters in triage: a safety-net line is a pod that never got permission, a monitor line is a demotion. (objective 3)

Question 3 of 5

An engineer reports that a site Bloodraven considers fenced still accepted a write from an admin account holding SUPER. What is the most likely explanation?

Show answer

Answer: Only read_only=ON is in effect on that instance; read_only permits updates from CONNECTION_ADMIN or SUPER

read_only is the weaker of the pair — the server 'permits no client updates except from users who have the CONNECTION_ADMIN privilege (or the deprecated SUPER privilege)'. super_read_only is the actual barrier, prohibiting client updates 'even from users who have CONNECTION_ADMIN or SUPER'. So a SUPER write landing means the stronger flag is not set. The second option invents a grandfathering rule that does not exist: the variable is evaluated per statement, not per session, which is exactly why fencing does not need to close sockets. The third option is backwards — the restriction is on client updates by privilege, not on binlog generation. The fourth confuses direction: replication threads apply what the source sent, they do not resurrect a locally rejected statement. (objective 1)

Question 4 of 5

Adding a role: ReadOnly reader site to playground makes the lease-expiry fence more likely to fire, because there is now one more peer whose silence the monitor must account for.

Show answer

Answer: False

The reversal: an extra peer makes rule #2 less likely to fire, not more. Rule #2 requires the operator and every peer to be silent for the full leaseTimeout, so each additional peer is one more party that can single-handedly suppress the fence — and a reader counts as a peer, since it answers /peer/ping and relays topology. 'A reachable peer without fresh authoritative topology can still suppress the lease-only all-peers-unreachable fence. This is retained compatibility behavior, not a quorum guarantee.' Nothing counts votes or requires a majority: one reachable reader is enough to keep an otherwise isolated primary writable. Size your group knowing that a reader added for read scaling widened that window. (objective 2)

Question 5 of 5

A fence call from the sidecar sits for several seconds before returning, and on another occasion returns an error. Explain both, and say what the monitor must not conclude from the error.

Show answer

Answer:

SET GLOBAL super_read_only = ON does not cut writers off — it blocks while other clients have an ongoing statement, an active LOCK TABLES WRITE, or an ongoing commit, until those locks are released and the statements and transactions end. That is the multi-second wait: the fence is queuing behind live write traffic on the very primary it is trying to demote. Separately, the statement fails outright if the issuing session holds explicit LOCK TABLES locks or a pending transaction. The error must not be read as 'the fence did not land': cancelling the context tears down the client connection, it does not roll back a write the server already applied, so an error means 'unknown'. Treating it as a definite failure is what once made the monitor re-fence a site it had just promoted; the correct move is to re-read the instance and find out.

A full-credit answer shows: A strong answer covers: (a) the SET blocks on ongoing statements, LOCK TABLES WRITE, or ongoing commits, so the delay is contention with live traffic rather than a slow operator; (b) it fails outright when the issuing session holds explicit locks or a pending transaction; (c) an errored SET GLOBAL may still have landed, because tearing down the connection does not roll back an applied write, so the result is ambiguous and must be resolved by re-reading rather than assumed to be a failure. Mentioning that fencing does not close sockets, so surviving sessions can still serve stale reads, is a bonus, not a requirement.

Both behaviours come from MySQL, not from Bloodraven: the fence is a blocking SET GLOBAL, and its failure mode is ambiguous rather than clean. An engineer who assumes the fence is instantaneous will misread a slow demotion as a hung sidecar, and one who assumes an error means 'not fenced' will re-fence a site that is already fenced — potentially one the operator has just promoted. (objective 1)

Erase saved progress?

This erases all quiz scores, reading progress, project checklists, and your name on the certificate. It cannot be undone, and it affects only this course in this browser.