PostgreSQLXV · High Availability, Failover and Disaster RecoveryHA
Client routing: DNS, VIP, proxy, service discovery
What you'll learn
- Compare routing mechanisms by failover time and failure mode
- Use target_session_attrs and state its limit precisely
- Account for client reconnection in the real RTO
- Avoid the routing mistakes that make a correct failover look broken
Prerequisites
Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27
A promotion that clients cannot find is not a recovery. This is frequently the largest term in the real RTO and the least examined part of an HA design.
The mechanisms
| Mechanism | Failover time | Fails by |
|---|---|---|
| DNS change | TTL + caching | Clients caching past the TTL |
| Virtual IP | Seconds | Both nodes claiming it; ARP not converging |
| Proxy (HAProxy, pgpool) | Health-check interval | Being a single point of failure itself |
target_session_attrs | Connection time | Only helping at connect |
| Service discovery | Watch latency | The discovery system being down |
target_session_attrs
libpq accepts a list of hosts and an attribute the chosen host must satisfy.
$ psql "postgresql://postgres@rbpg-prim2,rbpg-sb:5432/postgres?target_session_attrs=read-write" \
-c "SELECT inet_server_addr(), pg_is_in_recovery()" 172.26.0.3 in_recovery=false <- the primary, listed second$ psql "postgresql://postgres@rbpg-sb,rbpg-prim2:5432/postgres?target_session_attrs=standby" \
-c "SELECT inet_server_addr(), pg_is_in_recovery()" 172.26.0.2 in_recovery=true <- the standby, listed secondlibpq tried each host in order and kept the first one matching. List order did not matter; the attribute did.
The values are any (default), read-write, read-only, primary,
standby and prefer-standby.
What the application must do
Routing gets a new connection to the right place. These are the application’s job:
Notice the connection is dead. Many pools discover this only on next use, so the first query after a failover fails no matter how fast the failover was. A pool with a validation query or a TCP keepalive notices sooner.
Reconnect without stampeding. Two hundred clients reconnecting simultaneously against a cluster that has just started with a cold cache is a load event on top of an incident. Jittered backoff.
Retry safely. A transaction interrupted mid-flight may or may not have committed. Retrying a non-idempotent operation blindly is how a failover produces duplicate charges. This is application design, not configuration.
Fail loudly if the new primary is not writable. A client that
silently ends up on a standby and treats cannot execute INSERT in a read-only transaction as a transient error will retry forever.
# libpq keepalives, so a dead connection is noticed rather than hung on
keepalives=1 keepalives_idle=30 keepalives_interval=10 keepalives_count=3
connect_timeout=5
Those settings matter more than most of the routing discussion, because without them a broken connection can hang for the operating system’s default TCP timeout — which can be many minutes.
What to take from this
- Four mechanisms, four failover times, four failure modes.
target_session_attrspicks a host by role at connect time, and list order does not matter — measured both ways after a real failover.- It does nothing for connections already open. Those break, and the application’s reconnect behaviour is your real RTO.
- Set libpq keepalives and
connect_timeout, or a broken connection can hang for minutes. - DNS TTL is a lower bound. Measure what your stack actually does.
- A proxy should ask the HA stack who the primary is, not ask PostgreSQL. Fence, promote, then route.
Cross-course references
- Linux for Production Sysadmins — Part XXIII (DNS) covers TTL and negative caching, which decide how long clients keep using a dead primary, and Part LVI (Keepalived and VRRP) covers the VIP alternative.
- Kubernetes for Production Sysadmins — Part XXXVIII (Services), Part XXXIX (Service discovery) and Part XLI (CoreDNS) cover the same decision inside a cluster.
- Observability for Production Sysadmins — Part LXIII (Synthetic monitoring) covers proving from outside that clients reach the current primary, which no database metric can tell you.
Quiz
Knowledge check · 6 questions
Q1. An application uses a multi-host connection string with target_session_attrs=read-write. A failover occurs. What happens to its existing pooled connections?
Q2. A proxy health-checks backends with SELECT pg_is_in_recovery() and routes writes to whichever answers false. What can go wrong during a failover?
Q3. Why is a 30-second DNS TTL a lower bound on client failover time rather than a guarantee?
Q4. Which are the application's responsibility rather than the routing layer's? Select all that apply.
Q5. A 30-second DNS TTL guarantees that clients will reach the new primary within 30 seconds of a failover.
Q6. Why is client reconnection usually the largest term in a real RTO, and what reduces it?
Passing score: 75%. Answers are checked in this browser.