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.

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? Return. A read-only instance has nothing left to fence, 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 one-shot, called from cmd/sidecar/main.go. It completes before the FencingMonitor is constructed, so it is not a third rule and never runs again. Anything that presents it beside the two rules is describing the sidecar’s startup, not its steady state.

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 it has three distinct 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 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 >= 1s, leaseTimeout >= 3s, and leaseTimeout >= 3 × peerCheckInterval. The shipped pair sits exactly on that 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: read-only 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 its lines are prefixed safety net: and it runs once at boot, before the monitor exists; a SELF-FENCING: line can only come from the monitor. 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

The safety net: prefix places this before the monitor was even constructed, 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: read-only 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.