Tolerate transient HA promotion observations - #914
Conversation
🔐 Codex Security Review
Review SummaryOverall Risk: NONE FindingsNo concrete security, correctness, or reliability issues were found in the reviewed diff. NotesThe HA changes retain the existing lease/proof deadline during transient timeline mismatches, reject writer-generation changes, and validate the DCS leader lease before tolerating disagreement. Formatting and diff checks passed. Targeted Go tests could not run because the read-only environment prevented creation of the required module cache/toolchain. Generated by Codex Security Review | |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a166eb6c19
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- require the observed DCS writer to match before retaining active lifetime - cover higher-generation mismatch demotion
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 97691a8ec1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
This PR adjusts HA ownership handling so a transient Patroni/PostgreSQL timeline mismatch during promotion does not immediately demote an already-active Fleet runtime; instead, the coordinator marks the observation as unavailable and continues retrying within the existing proof/lease deadline. It also adds/extends tests to cover pre-activation retry, active convergence, and persistent mismatch expiry behavior.
Changes:
- Return the observed writer identity alongside
ErrTimelineMismatchso callers can decide whether to tolerate the mismatch. - Teach the HA coordinator to treat
ErrTimelineMismatchas a non-fatal “observation unavailable” state while active (bounded by the existing lease/proof deadline). - Add runtime/coordinator/observer tests covering transient and persistent timeline-mismatch scenarios.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| server/internal/ha/runtime_test.go | Adds synctest-driven runtime tests for transient/persistent timeline mismatch behavior. |
| server/internal/ha/observer.go | Returns an observation along with ErrTimelineMismatch so downstream code can validate writer identity. |
| server/internal/ha/observer_test.go | Extends mismatch test to assert returned observation fields on error. |
| server/internal/ha/coordinator.go | Special-cases ErrTimelineMismatch during active renewals to mark observations unavailable without deactivating. |
| server/internal/ha/coordinator_test.go | Adds coordinator tests for retaining active lifetime across mismatch and rejecting mismatch from a new writer; adjusts test observer behavior to return observations on error. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- close the DCS observation before tolerating a timeline mismatch - demote when the writer changes during identity validation
19baea4 to
3219a0e
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3219a0eaa4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- require the observed DCS writer to match before retaining active lifetime - cover higher-generation mismatch demotion
- close the DCS observation before tolerating a timeline mismatch - demote when the writer changes during identity validation
25b6dca to
49d8e07
Compare
- require the observed DCS writer to match before retaining active lifetime - cover higher-generation mismatch demotion
- close the DCS observation before tolerating a timeline mismatch - demote when the writer changes during identity validation
49d8e07 to
d677dcf
Compare
Reviewable diff: +82/-35 across 2 files (excludes generated, test, and story files).
Summary
Keeps Fleet available when Patroni and the connected PostgreSQL server briefly report different timelines during promotion. Fleet now treats that mismatch as unavailable ownership evidence and retries within the last valid proof's existing deadline; convergence avoids an unnecessary process restart and VIP withdrawal, while persistent disagreement still causes crash-only demotion.
Stack: #888 -> #914 -> #890 -> #891. This diff is relative to #888, which supplies the HA observer, ownership lease, crash-only runtime, and keepalived health contract. #890 and #891 remain responsible for passive and active application updates. Guided-installer behavior, update behavior, keepalived policy, topology, and qualification evidence are out of scope here.
How it works
The observer still requires one consistent ownership sample: the Patroni primary and connected PostgreSQL server must agree on address, port, recovery state, and timeline before the lease store can acquire or renew ownership. A mismatch therefore never writes or renews the Fleet ownership lease. Before returning the mismatch, the observer repeats its linearizable DCS read, requires the cluster, leader, writer generation, lease, and member endpoints to remain unchanged, and verifies that the unchanged leader lease is still live.
Before activation, the existing coordinator loop leaves the node passive and retries after its normal interval. After activation, only a timeline mismatch keeps the active lifetime alive. The coordinator marks the current observation unavailable but preserves the prior ownership, proof deadline, and watchdog timer unchanged. A matching observation before that deadline renews ownership and restores current status. If disagreement persists, the unchanged watchdog cancels the active lifetime, closes the command gate, aborts the runtime, and lets keepalived withdraw the VIP.
flowchart TD P["Patroni reports primary identity"] --> O["Fleet ownership observer"] D["Connected PostgreSQL reports server identity"] --> O O --> M{"Address, port, recovery, and timeline match?"} M -->|"Yes"| R["Acquire or renew ownership proof"] M -->|"No timeline match"| U["Observation unavailable; no lease write"] U --> C{"Prior proof still valid?"} C -->|"Yes"| T["Retry on existing coordinator cadence"] C -->|"No"| A["Crash-only runtime abort"] T --> O A --> V["keepalived withdraws VIP"]Areas of the code involved
server/internal/ha/observer.goserver/internal/ha/observer_test.goserver/internal/ha/coordinator.goserver/internal/ha/coordinator_test.goserver/internal/ha/runtime_test.goKey technical decisions & trade-offs
ErrTimelineMismatchbracketed by unchanged DCS snapshots for the held writer gets bounded convergence handling; a changed cluster, leader, writer generation, lease, member endpoint, wrong PostgreSQL identity, or DCS failure remains fail closed.Testing & validation
just _lint-serverreports zero issues.git diff --checkpasses.v0.2.10-ha-promotion.2at25b6dcac28e5750a49dc3106cb51fe82b331c6aa: Patroni promoted at21:38:58.325Z, PostgreSQL accepted connections at21:38:58.398Z, Fleet opened active jobs at21:39:00.196Z, and keepalived entered MASTER at21:39:04Z.21:38:54Z, first failed at21:38:55Z, and returned stable HTTPS beginning at21:39:04Z(9-second interruption, below the 15-second target). The promoted Fleet container kept the same ID and start time with restart count zero; no timeline-mismatch abort occurred. The old active rejoined as a synchronous standby with about 6 ms replay lag, and both hosts returned tofailover_ready: true.Post-Deploy Monitoring & Validation
Patroni and PostgreSQL timelines do not match,active lifetime ended, andHA Fleet runtime abortedaround promotion.failover_ready: true.