Repository navigation
Description
🤖 Generated by the Daily AI Engineer
Problem
A single degraded replica in the coroot-db observability database silently blocked every
platform PR from merging for ~4 hours today, and degraded prod HA. The failure is systemic, not a
one-off.
The chain:
- A CNPG switchover left the demoted old primary (
coroot-db-7) unable to rejoin:pg_rewindfound
the servers diverged on timeline 8 and could not restore the WAL segment it needed from the archive. - CNPG retried
pg_rewindforever — 4+ hours, zero restarts, no self-healing and no re-clone. The
pod stayedRunningbut never Ready, so theClustersat inWaiting for the instances to become active(2 of 3 ready). - The
infrastructureFlux Kustomization health-checks thatCluster, so it failed with
health check failed after 20m: timeout waiting for: [Cluster/observability/coroot-db]. 🚀 Deploy to Prodtherefore failed in everymerge_grouprun, so every platform PR was
evicted from the merge queue (observed on fix(backstage): give the startup probe a real budget so it survives a restart #2636).
The important part: the database was serving fine the whole time — primary healthy, one healthy
replica, ContinuousArchiving=True, LastBackupSucceeded=True. A non-critical, still-serving
observability database with one lagging replica took down the entire merge lane.
I unwedged prod by hand (deleted the diverged replica's PVC + pod so CNPG re-cloned it from the
primary; the cluster is back to 3/3 healthy and the merge queue is moving). That is a manual remedy,
not a fix.
Proposed direction
Two independent defects, either of which alone would have prevented this:
- A degraded-but-serving observability DB must not gate the merge queue. Deploy health gating
should not hang for 20 minutes on a replica that has no bearing on whether the deploy is good.
Either scope theinfrastructurehealth checks away from non-critical stateful replicas, or make
this class alert instead of block. - A replica that cannot
pg_rewindshould be re-cloned, not retried forever. Retrying a rewind
that can never succeed (the WAL segment is not in the archive and never will be) is an infinite
loop. Options: ensure WAL needed for rewind is retained (HA replication slots / WAL retention), or
detect the unrecoverable-rewind condition and re-bootstrap the instance.
Also worth noting for the runbook: this switchover was very likely triggered by the rolling Talos node
reboots performed during the GHCR-credential remediation earlier today. After a rolling node reboot,
stateful workloads must be verified re-converged — a reboot can leave a demoted primary permanently
stuck, and nothing alerts on it.
Rough size
M — (1) is a manifest/health-check scoping change; (2) is a CNPG configuration or operator-behaviour
question that may need an upstream conversation.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Effort
Projects
- StatusShow more project fields✅ Done
Activity
coderabbitai commentedon Jul 14, 2026
🔗 Related PRs
#2330 - ci: heal prod when a merge-group deploy fails (restore main) [merged]
#2424 - docs(dr): add CNPG StorageClass migration runbook (longhorn → longhorn-wffc) [merged]
#2425 - fix(cnpg): move platform Postgres clusters to the longhorn-wffc StorageClass [merged]
#2578 - fix(policy-reporter): run all components at the HA replica floor [merged]
#2622 - fix(ci): heal cancelled merge-group deploys [merged]
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.🧪 Issue enrichment is currently in open beta.
You can configure auto-planning by selecting labels in the issue_enrichment configuration.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
devantler commentedon Aug 5, 2026
This recurred today on a different cluster — same chain, same outcome
umami-dbis now doing exactly whatcoroot-dbdid on 07-14, and the platform merge queue is wedgedagain as I write this.
Live evidence (2026-08-05 ~17:0xZ, prod):
umami-db-13is inCrashLoopBackOff, 58 restarts over ~5h. It cannot self-heal — every startends in
PANIC: could not locate a valid checkpoint record at A/13000028, i.e. its WAL/pg_control isunrecoverable. Unlike the
pg_rewindcase, this one does restart; it just PANICs each time.Cluster/umami/umami-dbtherefore sits at 2 of 3 ready,phase: "Cluster cannot proceed to reconciliation due to an error while interacting with plugins".umami-db-12healthy,currentPrimary == targetPrimary,ContinuousArchiving=True,LastBackupSucceeded=True. Exactly the "degraded but serving" shapethis issue describes.
appsKustomization (spec.wait: true, no explicithealthChecks, 20m timeout) consequentlyfails, and Flux names the single culprit:
health check failed after 20m0.03s: timeout waiting for: [Cluster/umami/umami-db status: 'InProgress']🚀 Deploy to Prodfails in everymerge_grouprun and every platform PR is evicted.Measured today on fix(k8s): declare CoreDNS's nonroot UID instead of inheriting it #2975:
added_to_merge_queue 14:35 → removed 15:20,added 16:18 → removed 16:57.fix(ci): retry the cordon claim when an unrelated Node write breaks its CAS test #2977 and fix(ci): fail the deploy when the Cilium rollout gate outruns its window #2973 are cycling through the same 20-minute failure right now.
Two distinct causes were behind #2975's two evictions, worth separating:
(
DENIED: You have exceeded a secondary rate limit). Transient, and already tracked as A transient GHCR push denial is treated as non-retryable and evicts PRs from the merge queue #2957 —the same
verify_evidencestep succeeded 4 minutes later in the Heal job, which is a clean control.appshealth-check timeout onCluster/umami/umami-db.Why this raises the priority
This is the second occurrence in three weeks, on a different database, in a different
Kustomization (
apps, notinfrastructure). That rules out the one-off reading: any CNPG clusteranywhere in the tree can wedge the entire merge lane by losing one non-critical replica, and the
blast radius is every product's ability to ship.
Direction (1) in the issue body — a degraded-but-serving database must not gate the merge queue — is
the part that would have prevented both occurrences, and it is a manifest change rather than an
upstream conversation. I am treating it as the actionable half.
What I could not do
Unwedging needs the corrupt replica's PVC + pod deleted so CNPG re-clones it from the primary — the
same manual remedy used on 07-14. Cluster mutation is outside my runtime authority (and deleting a
production PVC is destructive-shaped even though CNPG makes it recoverable), so I have not
attempted it and have raised the unwedge to the maintainer instead.
devantler commentedon Aug 5, 2026
Second occurrence today on
umami-db— and the mechanism is not what we recordedumami-db-13(prod,umaminamespace) wedged the merge queue the same way this issue describes forcoroot-db:Clusterstuck at 2/3,appsKustomization failing its 20m health check,🚀 Deploy to Prodfailing in everymerge_grouprun, PRs evicted (#2975 twice, then #2977/#2973).Unwedged by the documented remedy (delete the replica's PVC + pod → CNPG re-cloned from the primary).
Recovery took 47 seconds; cluster is
Ready=True, 3/3, all instances on TL 61. Note CNPG assignedthe new instance serial 1, not 13 — it picks the lowest free serial, so the replica is now
umami-db-1.The corrupt-WAL framing is wrong, and it matters for the fix
Nothing was corrupt. Evidence from the live cluster:
pvc-9ce20317-…:state: attached,robustness: healthy.Node
prod-worker-3Ready, no disk or memory pressure.restore_commandwas working. The barman sidecar successfully restored0000002E.historythrough
0000003D.historyfrom the archive during every crash loop.pg_controlsat on timeline 44(
0000002C0000000A00000013), last checkpoint Aug 2 19:27 — three days stale. The cluster was ontimeline 61.
wal-restoreeven loggedRefusing to restore future timeline history file … fileTimeline:62, clusterTimeline:61.PostgreSQL followed the history and was served the TL 45 copy
0000002D0000000A00000013. The recordat
A/13000028in that file is not a valid checkpoint →invalid xl_info in checkpoint record→PANIC: could not locate a valid checkpoint record at A/13000028.So the failure is: a replica that is down or behind across a timeline switch can never rejoin, because
the pre-switch timeline's WAL segment was not retained in the archive. It is unrecoverable by
construction — no amount of retrying, and no
pg_rewind, can produce a WAL segment that does not exist.This is a different mechanism from the
coroot-dbcase in the issue body (which waspg_rewinddivergence on a demoted primary), but it has the same root: the WAL needed to reconcile across a
timeline switch is not retained. Proposed direction #2 above is therefore aimed correctly, but its
framing ("a replica that cannot
pg_rewind") is too narrow — it does not describe today's failure, whichnever reached
pg_rewindat all. It failed in standby startup.Why it recurs: timeline churn
The cluster was created 2026-06-03 and is on timeline 61 — 60 timeline switches in 63 days,
roughly one per day. Every switch is another chance for a replica that is briefly down to be stranded
permanently. Two contributing observations, both live:
umami-db-11andumami-db-12are 8-day-old pods carrying 20 and 18 in-place container restarts(all graceful,
exitCode 0; both last terminated at the 10:32Z promotion). Every primary restart is afailover is a burned timeline. I have not diagnosed what restarts them that often — that is the
open question, and it is probably the highest-leverage one here.
autoscale-cx43-…). The cluster sets onlypodAntiAffinityType: preferred— nonodeAffinity/nodeSelectorkeeping instances on stableworkers — so autoscaler scale-down forces a failover.
Suggested retarget
retention that outlives the staleness window a replica can plausibly accumulate. This is what makes
the failure survivable rather than terminal.
switch/day on a 3-node cluster is the thing generating the exposure.
Detail worth knowing for the runbook
The
Cluster'sphasereadCluster cannot proceed to reconciliation due to an error while interacting with plugins, namingdial tcp 10.99.190.72:9090: connect: connection refused. That string was stale —the barman plugin endpoint was
readyand completing pre-hooks every 3s throughout. Do not read thatphase as "the operator is stuck"; check the plugin endpoint and the plugin's own logs before acting on it.
Also:
kubectl get clusterresolves toclusters.ksail.ioon this cluster, not CNPG. The fully-qualifiedclusters.postgresql.cnpg.iois required or you get a confusingNotFound.