fix: the VIP fence of a caught-up MariaDB replica is cleared (keel#192) #194

Merged
navigator merged 3 commits from fix/mariadb-unfence-caught-up-192 into main 2026-10-16 11:40:17 -03:00
Owner

Fixes #192. Stacked on #191 → #190 → #189 → #187: merge those first.

The cross-site rehearsal of 2026-10-16 cut the MariaDB primary, and the VIP moved by itself. The old primary was reseeded, replicated from the holder and caught up, but stayed VIP-fenced: the clear of keel#166 is PostgreSQL only (dbpgfollow._unfence). A pct stop of the holder then gave no failover in 100 s, and after pct start both nodes were fenced and read only.

What changes:

  • keel database follow clears the VIP fence of a MariaDB replica that replicates from the holder, with its SQL thread running, once it applied the newest sequence that the holder logged in each GTID domain. "Applied" is its binary log (log_slave_updates) or its slave position, which a reseed sets from the dump, so a reseeded node that does not have the holder's older GTIDs of another server id in its own binary log is still caught up.
  • keel database watch runs follow on a fenced replica of either engine (it was PostgreSQL only).
  • docs/replication.md says it.

Tests: tests/test_system_dbfollow.py (caught up after a reseed: unfenced; behind, SQL thread stopped, holder down: fenced; the watch follows a fenced MariaDB replica). The real proof is the second failover of the LNMP 19.0-3 cross-site test.

keel 0.23.38.

Fixes #192. Stacked on #191 → #190 → #189 → #187: merge those first. The cross-site rehearsal of 2026-10-16 cut the MariaDB primary, and the VIP moved by itself. The old primary was reseeded, replicated from the holder and caught up, but stayed VIP-fenced: the clear of keel#166 is PostgreSQL only (`dbpgfollow._unfence`). A `pct stop` of the holder then gave no failover in 100 s, and after `pct start` both nodes were fenced and read only. What changes: - `keel database follow` clears the VIP fence of a MariaDB replica that replicates from the holder, with its SQL thread running, once it applied the newest sequence that the holder logged in each GTID domain. "Applied" is its binary log (log_slave_updates) or its slave position, which a reseed sets from the dump, so a reseeded node that does not have the holder's older GTIDs of another server id in its own binary log is still caught up. - `keel database watch` runs follow on a fenced replica of either engine (it was PostgreSQL only). - `docs/replication.md` says it. Tests: `tests/test_system_dbfollow.py` (caught up after a reseed: unfenced; behind, SQL thread stopped, holder down: fenced; the watch follows a fenced MariaDB replica). The real proof is the second failover of the LNMP 19.0-3 cross-site test. keel 0.23.38.
On a pair, keel-overlay-anubis-key.service makes no key on the database
replica: the key is shared and comes from the primary (decision 0041).
Nothing copied it, so anubis@keel did not start there, and the apply
failed the anubis overlay. The LNMP and LNPP pair tests copied the key
by hand (Keel-Linux/common#56).

The tests ask that the other member of the pair record, and no other
member, gets the key over the members' channel; that a paired replica
with no key asks for it in a run that may change the machine, and
writes it with mode 0600 before Anubis starts; that a replica in no
pair yet starts nothing and says why; and that an unreachable primary
refuses the step.
The secret message of the members' channel gives a second name,
anubis/signing_key, answered with /etc/anubis/keel.key only to the
other member of the pair record, as the replication credential is.

keel.system.anubiskey observes, on the live system and only in a run
that may change the machine, a node whose spec makes it a database
replica, with Anubis enabled, no key file and no
secrets.anubis_signing_key declared. It asks the other member of its
pair, and the plan writes the key (mode 0600) before the overlay's
units start. The key unit then finds the key and leaves it.

Before the pair record exists, the apply starts nothing for Anubis and
says why, so the first boot of a replica no longer fails the overlay. A
key that is there is never replaced.
chore: keel 0.23.34 (common#56)
Some checks failed
tests / mesh (pull_request) Successful in 11m45s
tests / vip (pull_request) Successful in 7m5s
tests / tests (pull_request) Has been cancelled
security / security (pull_request) Failing after 1m40s
tests / vip-upgrade (pull_request) Has been cancelled
tests / etcd-auth (pull_request) Has been cancelled
tests / mariadb (pull_request) Has been cancelled
tests / package (pull_request) Has been cancelled
tests / mariadb-simple (pull_request) Has been cancelled
tests / postgres (pull_request) Has been cancelled
tests / etcd (pull_request) Successful in 7m8s
8fc10142ee
keel-vip.service starts only when /var/lib/keel/etcd/cluster.json
exists. On a cloud simple pair, keel diff reported overlays.vip as
drift (enabled and inactive) on every run, and each apply printed a
start of the unit that did nothing.

The tests ask that, before the cluster exists, diff compares only
whether the unit is enabled and says why it does not run, and that
apply enables the unit and does not start it. With the cluster, an
inactive controller is drift and apply starts it, as before.
Before etcd's cluster.json exists (always in cloud simple, and in
cloud advanced until etcd forms), the start condition of
keel-vip.service holds it. keel diff now compares only whether the
unit is enabled, and gives the reason: without etcd, keel vip check
(keel-vip-check.timer) does the VIP's work. keel spec apply enables the
unit, does not start it, and says why. With the cluster, both compare
and converge the unit as before.
chore: keel 0.23.35 (keel#106)
Some checks are pending
security / security (pull_request) Waiting to run
tests / tests (pull_request) Waiting to run
tests / mesh (pull_request) Waiting to run
tests / etcd (pull_request) Waiting to run
tests / postgres (pull_request) Waiting to run
tests / package (pull_request) Waiting to run
tests / vip (pull_request) Waiting to run
tests / vip-upgrade (pull_request) Waiting to run
tests / etcd-auth (pull_request) Waiting to run
tests / mariadb (pull_request) Waiting to run
tests / mariadb-simple (pull_request) Waiting to run
093da566b6
On a cut MariaDB primary, a write that waits for the replica's ACK
returned OK when the semi-synchronous timeout (10 s) ended, before the
old primary dropped the VIP (15 s). The fence did not stop it either:
SET GLOBAL read_only = ON waits for the backup lock behind that write.
So writes that the old primary acknowledged were lost at a failover.

A dump thread to the cut replica also stayed on the old primary. When
that replica later connected again with the same server id, MariaDB
11.8.6 ended the old thread and took no connection after it.

tests/test_mariadb_fence_real.py runs two real mariadbd, semi-
synchronous with AFTER_SYNC. With the replica stopped by SIGSTOP, the
fence must end with read_only on, well within the timeout, with an
error and no OK for the waiting write. With the replica behind a relay
that stops moving bytes and keeps its sockets, the fence must end the
dump thread, and the server must answer after the replica connects
again with the same server id. The timeout must be at least twice the
VIP's release. The job "mariadb / trixie" runs it as root.
keel database follow turns a node read only that took writes when it
loses the VIP. It now sets read_only in a client of its own and, while
that waits for the backup lock, kills every connection but the
server's own threads and this node's own replication threads. A write
that waited for the replica's ACK gets an error, not OK. Its row stays
on the old primary as an errant transaction, so the node comes back
diverged and waits for the confirmed reseed, as before. The dump
threads of its replicas end too: a fenced node serves no replica, and
a KILL ends such a thread cleanly where the implicit end at a new
connection of the same server id hung the server (keel#193).

rpl_semi_sync_master_timeout goes from 10 s to 30 s: longer than the
VIP's release (15 s, keel#174) and the follow it starts, with room. A
lost replica still gives the fallback to asynchronous commits, 30 s
after the last ACK in place of 10 s. This changes the 10 s of the
maintainer's answer of 2026-10-03 (decision 0031): the release test's
criterion is no acknowledged write lost at a cut of the primary, and
10 s cannot meet it while the release is 15 s.
chore: keel 0.23.36 (keel#186, keel#193)
Some checks are pending
security / security (pull_request) Waiting to run
tests / tests (pull_request) Waiting to run
tests / mesh (pull_request) Waiting to run
tests / etcd (pull_request) Waiting to run
tests / vip (pull_request) Waiting to run
tests / vip-upgrade (pull_request) Waiting to run
tests / etcd-auth (pull_request) Waiting to run
tests / mariadb (pull_request) Waiting to run
tests / mariadb-simple (pull_request) Waiting to run
tests / postgres (pull_request) Waiting to run
tests / package (pull_request) Waiting to run
e3c6afa214
The cross-site rehearsal of 2026-10-16 booted 3 cloud advanced CTs with
overlays.wireguard and overlays.etcd disabled. At the third join, the
start of etcd wrote overlays.etcd: enabled into a spec that still said
wireguard: disabled, which does not validate, and etcd did not start.
The operator then enabled both and applied: etcd started, but nothing
wrote /var/lib/keel/etcd/started, and every later apply refused the
member as one keel never started.

The tests ask that the start enables wireguard with etcd, and that an
apply that starts etcd for the cluster of cluster.json records it.
keel.mesh.etcd.start sets overlays.wireguard: enabled with
overlays.etcd: enabled. etcd requires wireguard, and the join already
wrote network.overlay.

plan_etcd writes /var/lib/keel/etcd/started on the live system when
the cluster is known and etcd holds no member yet, as the join and the
form do when they start etcd. A member directory that keel did not
start is still refused.
chore: keel 0.23.37 (keel#188)
Some checks failed
tests / etcd (pull_request) Successful in 6m45s
tests / vip (pull_request) Successful in 6m44s
tests / mesh (pull_request) Successful in 11m17s
tests / postgres (pull_request) Successful in 10m42s
tests / etcd-auth (pull_request) Successful in 4m58s
tests / package (pull_request) Successful in 8s
tests / mariadb-simple (pull_request) Successful in 4m47s
tests / vip-upgrade (pull_request) Successful in 7m42s
tests / mariadb (pull_request) Successful in 7m18s
tests / tests (pull_request) Successful in 20m24s
security / security (pull_request) Failing after 1m26s
dd4f7bdbf9
The cross-site rehearsal of 2026-10-16 cut the MariaDB primary, and
the VIP moved by itself. The old primary was reseeded and replicated
from the holder, caught up, but stayed VIP-fenced: the clear of
keel#166 is PostgreSQL only. A pct stop of the holder then gave no
failover, and after pct start both nodes were fenced and read only.

The tests ask that follow clears the fence of a replica that replicates
from the holder, with its SQL thread running, once it applied the
newest sequence the holder logged in each GTID domain, also when a
reseed set it in the slave position only; that a replica behind, one
whose SQL thread stopped, or one whose holder does not answer stays
fenced; and that the watch follows a fenced MariaDB replica.
keel database follow clears the VIP fence of a MariaDB replica that
replicates from the holder, with its SQL thread running, once it
applied the newest sequence that the holder logged in each GTID
domain: in its binary log (log_slave_updates) or its slave position,
which a reseed sets from the dump. This is keel#166 for MariaDB. The
watch runs follow on a fenced replica of either engine.
chore: keel 0.23.38 (keel#192)
Some checks failed
tests / mesh (pull_request) Successful in 11m51s
tests / vip-upgrade (pull_request) Successful in 7m39s
tests / package (pull_request) Successful in 9s
tests / mariadb-simple (pull_request) Successful in 4m56s
tests / etcd-auth (pull_request) Failing after 5m48s
tests / mariadb (pull_request) Successful in 8m25s
security / security (pull_request) Failing after 2m14s
tests / tests (pull_request) Successful in 20m47s
tests / postgres (pull_request) Successful in 11m34s
tests / etcd (pull_request) Successful in 7m9s
tests / vip (pull_request) Successful in 6m59s
572b4ed88c
navigator force-pushed fix/mariadb-unfence-caught-up-192 from 572b4ed88c
Some checks failed
tests / mesh (pull_request) Successful in 11m51s
tests / vip-upgrade (pull_request) Successful in 7m39s
tests / package (pull_request) Successful in 9s
tests / mariadb-simple (pull_request) Successful in 4m56s
tests / etcd-auth (pull_request) Failing after 5m48s
tests / mariadb (pull_request) Successful in 8m25s
security / security (pull_request) Failing after 2m14s
tests / tests (pull_request) Successful in 20m47s
tests / postgres (pull_request) Successful in 11m34s
tests / etcd (pull_request) Successful in 7m9s
tests / vip (pull_request) Successful in 6m59s
to 52defb6087 2026-10-16 07:55:02 -03:00
Compare
navigator force-pushed fix/mariadb-unfence-caught-up-192 from 572b4ed88c
Some checks failed
security / security (pull_request) Failing after 2m14s
tests / etcd (pull_request) Successful in 7m9s
tests / vip (pull_request) Successful in 6m59s
tests / mesh (pull_request) Successful in 11m51s
tests / vip-upgrade (pull_request) Successful in 7m39s
tests / package (pull_request) Successful in 9s
tests / mariadb-simple (pull_request) Successful in 4m56s
tests / etcd-auth (pull_request) Failing after 5m48s
tests / mariadb (pull_request) Successful in 8m25s
tests / tests (pull_request) Successful in 20m47s
tests / postgres (pull_request) Successful in 11m34s
to fb23385529
All checks were successful
security / security (pull_request) Successful in 2m32s
tests / etcd (pull_request) Successful in 7m6s
tests / vip (pull_request) Successful in 6m55s
tests / mesh (pull_request) Successful in 11m38s
tests / vip-upgrade (pull_request) Successful in 7m48s
tests / package (pull_request) Successful in 8s
tests / etcd-auth (pull_request) Successful in 5m51s
tests / mariadb-simple (pull_request) Successful in 5m34s
tests / mariadb (pull_request) Successful in 8m20s
tests / tests (pull_request) Successful in 20m44s
tests / postgres (pull_request) Successful in 11m4s
2026-10-16 10:04:21 -03:00
Compare
Sign in to join this conversation.
No description provided.