The card you keep

Every default, reason string, promotion step, Service selector and metric label set on one screen — plus a glossary, and a rule for which facts belong in the dated appendix instead.

By the end of this topic you can

  1. Recall any shipped default and its playground override from one page
  2. Name the five condition reasons, the nine promotion steps with fatality, and the four Service kinds without looking them up
  3. Use the version appendix to decide whether a fact you remember is still true

Everything from here is reference. Nothing in this topic is new; all of it is spread across the six units behind you, which is exactly the problem — at 03:00 nobody reads a course. Print this page, or keep the tab open. It is the one screen the rest of the course was building toward.

Two rules for using it. Numbers marked shipped are CRD defaults; numbers marked playground are what the local group overrides them to. And anything with a date on it belongs to the version appendix rather than to this card — check there before you quote a version, an issue number or a documentation defect.

Timings and thresholds

SettingShipped defaultPlaygroundWhat it actually controls
pollInterval2ssameThe poll tick. One SELECT @@read_only per site, in parallel, each under a 5 s ceiling.
failureThreshold3sameConsecutive failed polls before unreachable. Detection = pollInterval × failureThreshold = 6 s.
recoveryThreshold2sameConsecutive writable polls before writable. Not a term in the detection sum.
adaptivesameUndocumented backoff: past failureThreshold the whole loop’s interval doubles per extra failure, exponent capped at 4, hard cap 30 s. One site down ⇒ a second fault takes 30 × 3 = 90 s to detect.
failoverCooldown5m30sGates automatic promotion only, at one call site, immediately before the promotion call.
connectionDrainTimeout30ssameBounds the post-promotion retry window for evicting sessions from a fenced former primary — only while it is reachable.
replication.maxLagSeconds30030The ReplicationLagging Degraded reason. Never a promotion gate. Skipped entirely for role: read-only sites.
replication.readOnlyMaxLagSeconds(nil — inherits maxLagSeconds)10Reader-endpoint membership only. An explicit 0 means zero reported lag, and is not the same as unset.
dns.ttl6010recordTTL on the DNSEndpoint. The floor on how long a stale answer survives.
sidecar.leaseTimeout20ssameRule #2 self-fence window. CEL: ≥ 3 s, and ≥ 3 × peerCheckInterval.
sidecar.peerCheckInterval5ssameSidecar tick. CEL: ≥ 1 s. 20 / 5 = 4 ticks of silence before a lease fence.
relay-log drain30ssameStep 3 of the promotion. Non-fatal on timeout.
source convergence20ssameA separate poll stage, not a failover step.
plannedFailover.maxLagWait5msameThe GTID-superset gate. Rollback lives here.
plannedFailover.drainTimeout30ssameConnection drain before the write endpoint moves. Exhaustion proceeds anyway.
plannedFailover.onCooldownrejectsamedefer parks the request instead of refusing it.
dragonfly.plannedFailover.maxSyncWait30ssameAlso the REPLTAKEOVER timeout argument; the client adds 5 s of I/O grace.
Dragonfly emergency budget10ssameHard-coded. MySQL failover is never delayed by cache.
backup.maxLagSecondsForSource300Picks the backup source. A different setting from maxLagSeconds, with the same default.
backup.pitr.archivePollInterval60sArchiver ticker beside inotify.
backup.pitr.maxBinlogSize100MWritten before spec.mysqlConf, so your override wins.
backup.retention7Backups kept per profile.
Measured failover, clean12.0 sNine-plus recorded runs, 12.004–12.02 s, one outlier at 13.008 s.
Measured failover, full drain36.0 s6 s detection + a 30 s drain that spent its whole budget.

The five condition reasons

There are exactly five, they come straight from EvalCrossSite, and Failover is not one of them.

ReasonThe topology it describes
HealthyExactly one core site writable, none unreachable. Degraded is False.
DegradedEverything else that is not one of the three below — including the promotion is about to happen and a writable non-promotable site needs fencing.
SplitBrainMore than one core site writable. Readers are excluded from the tally.
NoPrimaryNo core site writable and none unreachable. Will not self-heal; the matrix refuses to auto-elect.
TotalLossEvery core site unreachable.

Replication adds its own reasons to Degraded: ReplicationLagging, ReplicationBroken, ReplicationError, ReplicationSourceMismatch. All four skip role: read-only sites entirely.

Evaluation order (a row fires only if every row above declined): fence-first early return → TotalLossSplitBrain → failover → NoPrimary → degraded-with-peer-down → Healthy.

The nine promotion steps, with fatality

FailoverController.Execute, in order. Fatal means the promotion aborts; everything else logs and carries on.

#StepOn error
1SET GLOBAL super_read_only = ON on the old primarywarns only
2Kill application connections on the old primarywarns only
3Relay-log drain on the candidate, 30 s budgetwarns, promotes anyway
4STOP REPLICA on the candidatefatal
5RESET REPLICA ALL on the candidatefatal
6SELECT @@global.gtid_executedpromotionGtidExecutedwarns only
7SET GLOBAL super_read_only = OFFfatal
8SET GLOBAL read_only = OFFfatal
9Writable confirmation, synchronouslylogs, skips the DNS flip, returns

Then, outside the sequence: the durable failover record and the counter are stamped before DNS. Node taints and source convergence are poll neighbours, not steps.

The four Service kinds

2 × len(sites) + 2 objects. Eight for a three-site group.

ServiceSelectorApplication use
mysql-<group>-primaryinstance + role=primarywrites
mysql-<group>-replicasinstance + role=replica + healthy=yesreads
mysql-<group>-<site>name + instance + site, plus healthy=yes on a read-only sitesite-pinned tooling only
mysql-<group>-<site>-internalname + instance + site, no health gate, publishNotReadyAddresses: truenever yours — sidecar and peer traffic

role="fenced" matches neither shared Service. A read-only site earns healthy=yes only when all five hold: converged source, replicating, non-nil lag, canonical direct source host, and lag within readOnlyMaxLagSeconds.

Metrics, and their label sets

Label sets are not uniform. Read them before you copy a selector between rules.

MetricLabels
bloodraven_site_statesite, state — a one-hot over writable/read-only/unreachable/unknown
bloodraven_replication_lag_secondssite only — -1 means not replicating, not a small lag
bloodraven_replication_runningsite, thread (io/sql)
bloodraven_state_transitions_totalsite, from, to
bloodraven_poll_latency_secondssite — its _count is the loop’s heartbeat
bloodraven_failovers_totaltarget_site
bloodraven_planned_failovers_totaltarget_site, result
bloodraven_divergent_transactionssite
bloodraven_primary_reassert_totalsite
bloodraven_split_brain_auto_resolve_totalprefer_site
bloodraven_dns_flips_totalsite
bloodraven_replication_source_statenamespace, group, site, state
bloodraven_archiver_backlog_filesnamespace, group, site
bloodraven_backup_last_success_timestamp_secondsgroup, profile
bloodraven_backup_verified_timestamp_secondsgroup, profile
bloodraven_keyring_phasemysql_namespace, failover_group, site, phase — one-hot

Four words for two concepts: namespace / mysql_namespace, and group / failover_group. That is not a typo in this table.

Annotations you apply by hand

KeyValueNotes
bloodraven.shipstream.io/planned-failover<site> or <site>:maxLagWait=10mConsumed and cleared. Unknown override keys are rejected.
bloodraven.shipstream.io/reclone-site<site>:<divergentGtid prefix ≥8 chars>Cold form, when nothing is recorded: <site>:confirm=<group>.
bloodraven.shipstream.io/rotate-keyring<site>Refused on the active primary.

Written by the operator, never by you: bloodraven.shipstream.io/last-failover and …/last-failover-target, RFC3339 at second precision, as a pair.

Roles, in one line each

Glossary

Active site — the one site status.activeSite names as the writable authority. One name, or empty; empty means authority is ambiguous, and every endpoint is shed.

Anti-flap cooldownfailoverCooldown. Suppresses automatic promotion and nothing else.

Core site — any site whose role is not read-only. What the matrix counts.

Divergent GTID setGTID_SUBTRACT(old primary, new primary): the transactions the old primary holds that the new one never saw. Its cardinality is your lost-transaction count.

FencingSET GLOBAL super_read_only = ON. Blocks writes even from SUPER. Closes no sockets, so surviving sessions keep serving stale reads. At the Service layer, a separate thing: stamping the pod role=fenced, which matches neither shared selector.

GTID set — the transaction history a server has executed, as uuid:interval[:interval…], comma-separated across UUIDs. In MySQL 9.x a set may carry a user tag, uuid:tag:interval, and the tag is part of the identity — uuid:A:1-3 and uuid:B:1-3 are six transactions, not three.

Ordered update — the operator-driven rollout of a spec change: standby first, then a real failover, then the old active. Triggered by spec-hash drift; not cooldown-gated.

Promotability — exactly role == primary-candidate. Not earned by having the freshest data.

RPO / RTO — how much recently committed data you accept losing / how long you accept being unable to write. Bloodraven’s RPO on sudden primary loss is not zero, by design; its whole engineering budget goes to RTO.

Self-fence — the sidecar setting super_read_only=ON on its own MySQL without asking anyone. Rule #1: the known authoritative active site is somebody else. Rule #2: the operator and every peer have been silent past leaseTimeout.

Safety net — a different thing from self-fencing: a one-shot at sidecar startup that fences first and asks afterwards, and completes before the fencing monitor exists. safety net: in a log means a pod that has never been allowed to write; SELF-FENCED: means one that was writing and lost the argument.

Sealed — the steady-state keyring phase: the keyring file is projected read-only from the escrow Secret, so mysqld physically cannot add a key. Unsealed is mid-flight, and rotation re-enters it from Sealed, so the phase string alone is ambiguous — read it beside unsealReason.

Source convergence — the independent poll stage that repoints replicas at the current authority. Demands a direct source; a replica chained off another replica does not count as converged.

Split brain — more than one core site writable at once. sitePriorities does not prevent it and merges nothing; it is a standing decision about whose unreplicated writes you will discard.

Star topology — every replica replicates directly from the active site. Bloodraven does not accept chains.

super_read_only vs read_onlysuper_read_only also blocks users holding CONNECTION_ADMIN or SUPER, which plain read_only does not. Setting super_read_only=ON implicitly forces read_only=ON; clearing read_only implicitly clears super_read_only.

Where this leaves you

You have a card. Use it to stop looking things up, and use the version appendix to check anything on it that carries a date. What you can now say about a group you are handed is what the whole course was for: what it will do next, what it will refuse to do, what it will cost you when it does, and which of those numbers you measured yourself.

Flashcards

Detection delay, from first principles

pollInterval × failureThreshold = 2 s × 3 = 6 s. recoveryThreshold gates the opposite transition and is never a term in that sum; neither is the 5 s per-probe ceiling.

1 / 12

Quiz

Question 1 of 5

Your on-call runbook says 'the operator detects a dead site in about 10 seconds (2 s poll × 3 failures, plus 2 recovery polls × 2 s)'. Correct it.

Show answer

Answer: Detection is 6 s — pollInterval × failureThreshold. recoveryThreshold gates the transition back to writable and is never a term in the detection sum

Adding recoveryThreshold to the detection sum is the single most common wrong answer about this operator. The two counters gate opposite transitions: failureThreshold guards the way to unreachable, recoveryThreshold guards the way back to writable. Option 2 adds the per-probe ceiling, which bounds one probe and does not pace the loop. Option 4 overstates the backoff: it engages only once a site's failCount climbs past failureThreshold, so the first detection is always 6 s — but it is a real caveat worth adding to the runbook for the second fault during an existing outage, where the answer becomes 30 s × 3 = 90 s. (objectives 10, 11)

Question 2 of 5

A colleague is building an alert set for a new group and copies your BloodravenPITRArchiveLagging rule, changing only the group name in the label matcher. It never fires, even during a real archiver backlog. What did they get wrong?

Show answer

Answer: The label sets are not uniform: archiver metrics carry {namespace, group, site} while backup metrics carry {group, profile} and site metrics carry {site} alone, so a selector copied between families silently matches nothing

A selector naming a label the series does not carry matches nothing and reports nothing — the failure mode of an alert that is silently always-green. Reading the label set before copying a selector is the habit; the reference card exists so that reading takes five seconds. Options 2 and 4 are plausible-sounding PromQL claims that do not hold here. Option 3 confuses where the archiver runs with where its metrics are exported. (objective 11)

Question 3 of 5

You remember from this course that Bloodraven ships without a licence file and that a particular fix was still an open issue. Both are safe to state in a production readiness review.

Show answer

Answer: False

Neither is safe to state without re-checking, and that is what the version appendix is for. Both facts were true and grounded on the date the course was written, and both are exactly the kind that move: an issue gets closed, a pull request merges, a LICENSE file appears. The appendix records each with a date and the command that re-checks it — gh issue view, gh repo view --json licenseInfo, a grep over the source — so one lookup replaces a guess. The mechanisms you learned do not expire. The dated facts about them do. (objective 12)

Question 4 of 5

Which of these belongs on the reference card, and which belongs in the version appendix?

Show answer

Answer: failoverCooldown defaults to 5m belongs on the card; 'issue #144 is open' belongs in the appendix, because one is a CRD default and the other is a fact with a date on it

The split is between facts that describe a mechanism and facts that describe a moment. A CRD default is readable off the shipped CRD at any time and changes rarely and loudly; an issue's state changes quietly and without a release. Option 4 inverts it exactly — an issue number is stable but its state is the fact you care about, and that is the perishable part. Option 3 would make the card empty, which defeats the point of having one screen at 3am. (objective 12)

Question 5 of 5

You are handed a group you have never seen. Using only the reference card, write the four commands or reads you would perform first, and say what each one rules in or out.

Show answer

Answer:

One: kubectl get mysqlfailovergroup <group> -o jsonpath='{.status.activeSite}' — an empty answer means authority is ambiguous and every Service endpoint has been shed, which is the shape of split brain and of no-primary; a name means there is one writable authority. Two: the Degraded condition's reason, which is one of exactly five strings and tells me the topology shape rather than the action — and specifically rules out looking for a Failover reason, which does not exist. Three: status.updatePhase — non-empty means somebody is mid-rollout and any failover I am looking at is theirs, not a fault. Four: the per-site rows, state plus secondsBehindSource plus recoveryState, remembering that the writable primary's replication keys are absent rather than zero, that a lag of -1 means not replicating at all, and that role is not in status so I have to read spec.sites[].role to tell a read-only reader from a lagging candidate.

A full-credit answer shows: A strong answer names status.activeSite and reads an empty value correctly (ambiguous authority, endpoints shed); names the Degraded reason and that there are five of them with no Failover; checks status.updatePhase to separate a rollout from an incident; and reads the per-site rows with at least two of the three traps — absent-is-not-zero, -1 is not a small lag, and role lives in spec rather than status. Credit any order that starts with authority and ends with per-site detail.

The value of the card is not that it contains these facts but that it puts them one screen apart, so the first thirty seconds of an incident are spent reading rather than recalling. Every trap in the answer above is one this course spent a topic on. (objectives 10, 11)

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.