Skip to content

fix: an old primary stays read only until it proves it holds the VIP (keel#104) - #111

Merged
marcos-mendez merged 1 commit into
mainfrom
fix/old-primary-read-only-until-proven
Oct 9, 2026
Merged

marcos-mendez merged 1 commit into
mainfrom
fix/old-primary-read-only-until-proven

Conversation

@marcos-mendez

@marcos-mendez marcos-mendez commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #104 (bug B2 of the real-node MariaDB pair test, Keel-Linux/handbook#60).

Problem

After a crash and a boot, the old primary read its own claim of the VIP from /var/lib/keel/vip and keel database follow set read_only OFF. The other node held the VIP at a newer epoch. The old primary was writable for 31 s, until keel vip check learned the newer epoch. A write in that window makes an errant GTID.

Change

  • keel/system/dbfollow.py: on a node whose newest claim is its own, follow lifts read_only only when the VIP is on wg0. keel adds the VIP to wg0 only after a proof: keel vip promote after its new claim; keel vip check when a peer answered and no peer knows a newer claim; with etcd, the controller after a lease renewal that the majority confirmed (the kernel valid_lft ends it). A boot removes the address. Without the proof, follow sets read_only ON and the root lock, and writes no primary role file. Follow waits up to 15 s (PROOF_WAIT) for the address, because a promote records its claim before it adds the VIP.
  • keel/mesh/vipnode.py (carry_held): when keel adds the VIP to wg0, it writes the VIP's state again, so keel-database-follow.path runs follow again. keel database watch also runs follow again within 30 s.
  • No claim on a paired node (the VIP state file removed, or before the first promote): not proven either. A declared primary stays read only and locked, with no primary role file.
  • keel/system/dbwatch.py (needs_follow): follows a server again when it takes writes with no claim, or while the VIP is not on wg0 (its valid_lft ran out).
  • keel/system/dbfollow.py (follow): one run at a time, under /var/lib/keel/database/follow.lock, so the unit and keel database watch never interleave between the proof and SET read_only OFF.
  • Without etcd the address has no lifetime, so the proof is weaker: keel vip check: without etcd, the proof that a node holds the VIP is weak #113 records it, and docs/replication.md says so.
  • docs/replication.md: the follow table and the rule.
  • debian/changelog, pyproject.toml, keel/__init__.py: 0.23.4.

Tests

  • tests/test_system_dbfollow.py, TestTheOldPrimaryAtBoot: crash, the other node promoted at a newer epoch, the old node boots, stays read only, then follows as a replica (cloud simple, as on the real mesh); a server found writable without a proof is made read only; the holder proven again after its boot takes writes; no peer answers; the proof comes while follow waits; etcd does not answer at boot. The old code fails five of these with SET GLOBAL read_only = OFF.
  • TestNoClaimAndNoAddress: the state file removed on a primary with a pair record, follow keeps it read only; watch follows a writable primary whose VIP left wg0; follow takes its lock.
  • tests/test_mesh_vipmove.py: the state is written again when keel vip check adds the VIP, and not when it is there.
  • tests/mariadb_netns.py: A now takes the VIP (and follow runs) before the application's data is written, because a paired node with no claim is read only.
  • tests/mariadb_netns.py (mariadb / trixie): the primary's server killed, its VIP helper stopped, cut off; keel database promote --old-primary-gone on the replica; the old primary boots with its old claim: @@read_only is 1 at boot, after follow, and in every 0.5 s sample until it replicates from the new primary over TLS by GTID.

Test plan

  • tests / coverage
  • mariadb / trixie (the new crash case)
  • vip, vip-upgrade, etcd, etcd-auth, mesh / trixie
  • package / changelog

CI record

First run (617bfbb's first version): mariadb / trixie failed in the new case only: the replica's I/O thread was still Connecting when the status was read (read only held in every sample, replica after 6.4 s). The case now waits for the I/O thread to stream, as case (a) does (amended). vip / trixie failed at g_promote_b (etcdctl get: context deadline exceeded), the same failure as main's run 37833124848 and run 37843439840. No job was rerun: the amended push ran all checks again, all green.

Security review: H1, M2 and L1 are fixed in the amended commit; M1 is #113.

Head 7cbfab1 (after the security review): every check green but mesh / trixie. Attempt 1 timed out in test_a_spent_invite_is_refused (the inviter process). I reran the job once (run 37869774880, attempt 2); it failed in another test, test_c_joins_through_the_fallback, the same failure as main's run 37833124848. This PR changes nothing in keel.mesh's join or invite code. #112 holds this commit plus its own, and its mesh / trixie passed at 132a13a. I did not rerun a second time.

…(keel#104)

After a crash and a boot, keel database follow read the claim of the
VIP in the node's state file and set read_only OFF. The other node held
the VIP at a newer epoch. On a real pair the old primary was writable
for 31 s, until keel vip check learned the newer epoch.

Follow now lifts read_only only when this node's own claim is the
newest it knows and the VIP is on wg0. keel adds the VIP to wg0 only
after keel vip promote's new claim, keel vip check when a peer answered
and no peer knows a newer claim, or with etcd a lease renewal that the
majority confirmed. A boot removes the address. Without the proof,
follow sets read_only ON and the root lock, and writes no primary role
file. This is also true when etcd or the peers do not answer at boot,
and on a paired node with no claim at all (its state file removed, or
before the first promote).

Follow waits up to 15 s for the VIP, because a promote records its
claim before it adds the VIP. carry_held writes the VIP's state again
when it adds the VIP, so keel-database-follow.path runs follow again.
keel database watch follows a server again when it takes writes with
no claim or while the VIP is not on wg0. One follow runs at a time,
under /var/lib/keel/database/follow.lock.

Without etcd the address has no lifetime, so the proof is weaker there;
keel#113 records it, and docs/replication.md says so.

Tests: unit tests for the crash, the newer epoch on the other node, the
boot, read only, then the rejoin as a replica; etcd that does not
answer; the state file removed on a primary; the VIP gone from wg0 on a
writable primary; the lock. In mariadb / trixie, a case kills the
primary, promotes the replica, boots the old primary and samples
read_only; A now takes the VIP before the application's data.
@marcos-mendez
marcos-mendez force-pushed the fix/old-primary-read-only-until-proven branch from 617bfbb to 7cbfab1 Compare October 9, 2026 01:26
@marcos-mendez
marcos-mendez merged commit 7cbfab1 into main Oct 9, 2026
40 of 42 checks passed
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.

old primary is writable for about 31 s after a crash and boot (errant GTID risk)

1 participant