Skip to content
Merged
5 changes: 5 additions & 0 deletions docs/architecture.rst
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ cannot connect *and* it hasn't reported its state through `node_active` for
a while. This allows, for example, PostgreSQL to be restarted without
causing a health check failure.

.. figure:: ./tikz/seq-monitoring-protocol.svg
:alt: Sequence diagram of the node_active polling and health check protocols

The two protocols between a node's keeper and the monitor

Synchronous vs. asynchronous replication
----------------------------------------

Expand Down
5 changes: 5 additions & 0 deletions docs/failover-state-machine.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ Example of state transitions in a new cluster
A good way to get acquainted with the states is by examining the
transitions of a cluster from birth to high availability.

.. figure:: ./tikz/seq-birth-of-a-cluster.svg
:alt: Sequence diagram of two nodes and the monitor from registration to primary/secondary

From two freshly registered nodes to a converged primary/secondary pair

After starting a monitor and running keeper init for the first data node
("node A"), the monitor registers the state of that node as "init" with
a goal state of "single." The init state means the monitor knows nothing
Expand Down
19 changes: 19 additions & 0 deletions docs/fault-tolerance.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ PostgreSQL service:
The network\_partition\_timeout can be setup in the keeper's
configuration and defaults to 20s.

.. figure:: ./tikz/seq-primary-self-fence.svg
:alt: Sequence diagram of a primary self-fencing to demote_timeout after losing contact with both the monitor and the secondary

The primary self-fences rather than risk a split brain

- Monitor can't connect to Primary

Once all the retries have been done and the timeouts are elapsed, then
Expand All @@ -126,6 +131,11 @@ PostgreSQL service:
delta to resorb (30s by default) then the secondary can be promoted with
uncertainty about the data durability in the group.

.. figure:: ./tikz/seq-monitor-cant-reach-primary.svg
:alt: Sequence diagram of the monitor failing over to the secondary after losing contact with the primary

The monitor promotes the secondary and fences the old primary

- Monitor can't connect to Secondary

As soon as the secondary is considered unhealthy then the monitor
Expand All @@ -140,6 +150,11 @@ PostgreSQL service:
assigned the SECONDARY state, and the replication will be switched back to
synchronous.

.. figure:: ./tikz/seq-secondary-unhealthy.svg
:alt: Sequence diagram of the fallback to asynchronous replication and back

Falling back to asynchronous replication and resynchronizing

Failure handling and network partition detection
------------------------------------------------

Expand Down Expand Up @@ -170,6 +185,10 @@ expected to shut down after at least 30 and at most 60 seconds. To factor in
worst-case scenarios, the monitor waits for 90 seconds before promoting the
secondary to become the new primary.

.. figure:: ./tikz/seq-asymmetric-partition.svg
:alt: Sequence diagram of an asymmetric partition where the primary can still reach the secondary but not the monitor

Asymmetric partition: the monitor's 90s safety wait before promoting

See also
--------
Expand Down
14 changes: 8 additions & 6 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ __ https://github.com/hapostgres/pg_auto_failover

.. note::

The development of pg_auto_failover has been driven by Citus Data, since
then a team at Microsoft. The Citus Data team at Microsoft generously
maintains the pg_auto_failover Open Source Software so that its users may
continue using it in production.
The development of pg_auto_failover was originally driven by Citus Data,
and then a team at Microsoft. The project is now orphaned in terms of
financial support: maintenance and development happen on volunteers'
free time.

For enhancements, improvements, and new features, consider contributing
to the project. Pull Requests are reviewed as part of the offered
maintenance.
to the project. Pull Requests are reviewed as time allows.

For professional support or to sponsor ongoing maintenance, see
`oss.theartofpostgresql.com <https://oss.theartofpostgresql.com>`_.

.. note::

Expand Down
1,474 changes: 1,474 additions & 0 deletions docs/tikz/seq-asymmetric-partition.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
129 changes: 129 additions & 0 deletions docs/tikz/seq-asymmetric-partition.tex
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
\RequirePackage{luatex85}
\documentclass[border=10pt,12pt]{standalone}

\usepackage{cfr-lm}
\usepackage{amssymb}
\usepackage{pgf}
\usepackage{tikz}
\usetikzlibrary{arrows.meta,calc,positioning}

\begin{document}

\sffamily\bfseries

%% Actor x-positions (cm)
%% P=0 S=9 M=19
\def\px{0}
\def\sx{9}
\def\mx{19}
\def\ybot{22.2}

\begin{tikzpicture}[
>=Stealth,
yscale=-1,
]

\input{common.tex}

\tikzstyle{actor}=[rectangle, minimum width=3.6cm, minimum height=0.7cm,
inner sep=4pt, rounded corners=2pt, align=center, font=\bfseries]
\tikzstyle{ll}=[densely dashed, gray!60, line width=0.5pt]
\tikzstyle{lldead}=[densely dashed, orange!60, line width=0.5pt]
\tikzstyle{msg}=[->, line width=0.65pt]
\tikzstyle{rsp}=[->, densely dashed, line width=0.65pt]
\tikzstyle{broken}=[red!70, densely dotted, line width=0.8pt]
\tikzstyle{lbl}=[font=\small, inner sep=2pt]
\tikzstyle{note}=[rectangle, rounded corners=2pt, inner sep=5pt,
font=\small, align=center]
\tikzstyle{seclbl}=[font=\small\itshape, text=gray!70!black, anchor=west]

%% ── actor headers ───────────────────────────────────────────────────────
\node[actor, fill=pbox, text=ptxt] (Ph) at (\px,0) {Primary};
\node[actor, fill=sbox, text=stxt] (Sh) at (\sx,0) {Secondary};
\node[actor, fill=mbox, text=mtxt] (Mh) at (\mx,0) {Monitor};

%% lifelines
\draw[ll] (\px,0.35) -- (\px,\ybot);
\draw[ll] (\sx,0.35) -- (\sx,\ybot);
\draw[ll] (\mx,0.35) -- (\mx,\ybot);

%% ── steady state ────────────────────────────────────────────────────────
\node[seclbl] at (-3.4,1.3) {steady state};

\draw[msg] (\px,1.6) -- (\sx,1.6)
node[lbl,midway,above] {WAL stream};

\draw[msg] (\px,2.7) -- (\mx,2.7);
\node[lbl, above] at (\px+4.5,2.7) {node\_active(reported=primary)};
\draw[rsp] (\mx,3.3) -- (\px,3.3);
\node[lbl, above] at (\mx-2.2,3.3) {goal=primary};

\draw[msg] (\sx,4.3) -- (\mx,4.3);
\node[lbl, above] at (\sx+3.3,4.3) {node\_active(reported=secondary)};
\draw[rsp] (\mx,4.9) -- (\sx,4.9);
\node[lbl, above] at (\mx-2.2,4.9) {goal=secondary};

%% ── asymmetric partition ────────────────────────────────────────────────
\node[seclbl] at (-3.4,6.3) {asymmetric partition: only the Primary-Monitor link breaks};

\draw[broken] (\px,7.2) -- (13,7.2);
\node[red!70!black, font=\Large] at (13.4,7.2) {$\times$};
\node[lbl, above, text=red!70!black] at (6.5,7.2)
{node\_active(reported=primary) attempted -- no response};

\node[note, fill=gray!10, draw=gray!45, text width=13.5cm, font=\small]
at (9.5, 8.4)
{the primary keeps replicating normally to the secondary throughout --
only its link to the monitor is down};

%% ── monitor initiates failover ─────────────────────────────────────────
\node[seclbl] at (-3.4,9.8) {secondary is still reachable, monitor initiates failover};

\draw[msg] (\sx,10.7) -- (\mx,10.7);
\node[lbl, above] at (\sx+3.3,10.7) {node\_active(reported=secondary)};
\draw[rsp, color=pbox, line width=0.8pt] (\mx,11.3) -- (\sx,11.3);
\node[lbl, above, text=pbox] at (\mx-2.6, 11.3) {goal=stop\_replication};

\node[note, fill=sbox!25, draw=sbox!70!black, text width=8cm]
at (\sx, 12.3) {\small disconnects from Primary};

%% ── primary loses its last connection ──────────────────────────────────
\node[seclbl] at (-3.4,13.7) {primary loses its last connection};

\node[note, fill=orange!15, draw=orange!70!black, text width=12.5cm]
at (4.5, 14.6)
{\small with the secondary gone too, the primary self-fences the same
way as a full partition (previous diagram): expected to stop within
30--60s};

\draw[lldead] (\px,15.4) -- (\px,\ybot);

%% ── 90s safety wait ─────────────────────────────────────────────────────
\node[seclbl] at (-3.4,16.9) {90s safety wait};

\node[note, fill=blue!8, draw=blue!40, text width=13.5cm]
at (\mx-1, 17.8)
{\small worst case is 60s, so the monitor waits a full 90s before
promoting -- a margin against edge cases};

\draw[gray!50, line width=0.5pt, ->]
(\mx+2.6, 16.5) .. controls (\mx+4.3,16.8) and (\mx+4.3,18.8) .. (\mx+2.6,19.1)
node[right, font=\small\itshape, text=gray!70] {waiting\ldots};

%% ── promotion ────────────────────────────────────────────────────────────
\node[seclbl] at (-3.4,20.2) {promotion};

\draw[msg] (\sx,21.1) -- (\mx,21.1);
\node[lbl, above] at (\sx+3.7,21.1) {node\_active(reported=stop\_replication)};
\draw[rsp, color=pbox, line width=1pt] (\mx,21.7) -- (\sx,21.7);
\node[lbl, above, text=pbox, font=\small\bfseries] at (\mx-2.4,21.7)
{goal=wait\_primary};

%% ── actor footers ───────────────────────────────────────────────────────
\node[actor, fill=orange!25, draw=orange!70!black, text=orange!30!black]
at (\px, \ybot+0.35) {\small Primary (self-fenced)};
\node[actor, fill=pbox, text=ptxt] at (\sx, \ybot+0.35) {New Primary};
\node[actor, fill=mbox, text=mtxt] at (\mx, \ybot+0.35) {Monitor};

\end{tikzpicture}
\end{document}
Loading