Claude Fable 5.1 & GPT-6 Astra packages are live

Replication

Free

Read replicas, failover and lag — what replication actually guarantees, how to route reads safely, and how to avoid promoting a replica that has lost…

220 lines9.0 KB Deepseek Database
targetModels
DeepSeek V4DeepSeek V3.2DeepSeek R1DeepSeek V3 FamilyFuture DeepSeek Models
name
replication
category
Database
description
Read replicas, failover and lag — what replication actually guarantees, how to route reads safely, and how to avoid promoting a replica that has lost data.
license
MIT
author
Agent.md maintainers
last-verified
reviewed-by
unreviewed
<!-- Generated from models/_canonical by scripts/build-model-variants.js. Edit the canonical source, not this file. Behavioural profile for DeepSeek: scripts/model-profiles.json -->

#Task boundary

  1. Implement exactly the task as stated. Do not add abstractions, options, config, or files the task did not name.
  2. Comments, identifiers, commit messages and log strings are English only.
  3. Stop when the checklist at the end passes. Do not refactor or "improve" surrounding code.
  4. Every checklist item below is backed by an assertion in a test or by pasted command output, never by a sentence.

#Purpose

Rules for running replicas. Replication solves two different problems — read scaling and availability — and the configuration for each differs. Conflating them produces a system that is neither.

Central fact: asynchronous replication means a replica is always behind, and a failover can lose the writes it had not yet received. Every rule follows from that.


#Modes and what they guarantee

ModeCommit waits forData loss on failoverWrite latency
AsynchronousPrimary onlyUp to the current lagLowest
Semi-synchronous (remote_write)Replica received the WALOnly on simultaneous failure+1 network round trip
Synchronous (remote_apply)Replica applied itNoneHighest; a slow replica stalls writes
ini
# PostgreSQL — durable, and tolerant of one replica being down
synchronous_commit = remote_write
synchronous_standby_names = 'ANY 1 (replica_a, replica_b)'

ANY 1 (…) is important: naming a single mandatory standby means that standby going down blocks every write on the primary. Quorum form keeps writes flowing while still guaranteeing one durable copy.

MySQL equivalent: rpl_semi_sync_master_enabled = 1 with rpl_semi_sync_master_timeout — note that on timeout MySQL silently falls back to asynchronous. Alert on that fallback; it is the moment your durability guarantee disappears. → Database/mysql


#Read routing

Not every read may go to a replica. Classify explicitly:

ReadRoute to
Immediately after the user's own writePrimary
Anything inside a write transactionPrimary
A read whose result drives a writePrimary
Authentication / authorization decisionsPrimary
Dashboards, reports, search, feedsReplica
Analytics and exportsA dedicated replica

The failure this prevents: user saves a profile, is redirected, and sees the old values because the read hit a lagging replica. Then they save again.

Two robust patterns, in preference order:

sql
-- 1. Bound by LSN: read the replica only if it has caught up past your write
SELECT pg_current_wal_lsn();                    -- capture after the write
SELECT pg_last_wal_replay_lsn() >= $1;          -- on the replica, before reading
kotlin
2. Sticky window: route this session's reads to the primary for N seconds after
   any write. Cruder, but needs no per-query coordination.

Never route reads to a replica based on the query looking read-only. The question is whether the caller can tolerate stale data, and only the caller knows.


#Lag

Monitor lag in bytes and seconds, on the replica.

sql
-- Postgres, on the replica
SELECT now() - pg_last_xact_replay_timestamp() AS replay_lag;

-- On the primary, per connected standby
SELECT client_addr, state, sent_lsn, replay_lsn,
       pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replay_bytes
FROM pg_stat_replication;

Common causes of a lag spike, in order of frequency:

  1. A long-running query on the replica conflicting with WAL replay (Postgres will either cancel the query or pause replay, depending on max_standby_streaming_delay).
  2. A bulk write, index build, or VACUUM on the primary generating WAL faster than the replica can apply it.
  3. Single-threaded apply on the replica — enable parallel apply where available.
  4. Network saturation between regions.

Alert on lag before it matters — a replica three minutes behind is a three-minute data-loss window if the primary fails now.

hot_standby_feedback = on stops the replica cancelling long queries, but at the cost of holding the primary's vacuum horizon. Pick one deliberately. → Database/postgres


#Settings that govern behaviour

SettingEngineEffect
wal_level = replicaPostgresMinimum for streaming; logical for logical replication
max_wal_sendersPostgresOne per standby plus headroom for pg_basebackup
wal_keep_sizePostgresWAL retained for a lagging standby without a slot
max_slot_wal_keep_sizePostgresCaps slot retention so a dead slot cannot fill the disk
max_standby_streaming_delayPostgresHow long replay waits for a conflicting query before cancelling it
recovery_min_apply_delayPostgresDeliberate delayed replica — a window to catch a bad DELETE
rpl_semi_sync_master_timeoutMySQLFallback to async after this many ms
slave_parallel_workersMySQLParallel apply; single-threaded apply is a common lag cause
read_only / super_read_onlyMySQLMust be ON on every replica to prevent accidental writes

max_slot_wal_keep_size is the guard that stops an abandoned replication slot from filling the primary's disk — the most common self-inflicted replication outage. Set it, and alert on pg_replication_slots.active = false.

A delayed replica (recovery_min_apply_delay = '1h') is cheap insurance against operator error: it is one hour behind on purpose, so a destructive statement can be caught before it applies.


#Failover

Automatic failover requires three things, and it is dangerous without all of them:

  1. A consensus-based manager (Patroni, orchestrator, or the managed service's own) — never a script that pings and promotes.
  2. Fencing. The old primary must be demoted or killed before the new one accepts writes. Without it you get split-brain: two primaries taking writes, and a manual merge afterwards.
  3. A tested procedure. Failover that has never been exercised is a plan, not a capability.

After promotion, other replicas must be re-pointed at the new primary (pg_rewind or a rebuild). A replica left following the old primary silently stops receiving data.

Never promote a replica manually during an incident without checking its replay position. Promoting the most-behind replica discards every write the others had.

Rehearse: a scheduled failover drill in staging every quarter, timed, with the runbook followed as written. → DevOps/disaster-recovery


#Replicas are not backups

Replication propagates DROP TABLE in milliseconds. It protects against hardware and host failure, not against a bad migration, a buggy delete, or ransomware.

You need point-in-time recovery from base backups plus WAL archives, tested by restore. → Database/backup


#Anti-patterns

Anti-patternWhy it failsFix
Treating replicas as backupsDeletes replicate instantlyIndependent PITR backups
All reads to replicasUsers see their own writes vanishClassify reads explicitly
Routing by "looks read-only"Staleness tolerance is the caller's propertyExplicit routing per call site
No lag monitoringData-loss window is unknownAlert on bytes and seconds
Single named synchronous standbyIts outage blocks all writesANY 1 (…) quorum
Ignoring semi-sync timeout fallbackDurability silently becomes asyncAlert on the fallback
Promotion by ping scriptSplit-brainConsensus manager with fencing
Untested failoverThe drill happens during the outageQuarterly rehearsal
Not re-pointing surviving replicasThey silently stop replicatingpg_rewind or rebuild
Long analytics queries on a serving replicaCancels replay or stalls itDedicated analytics replica

#Checklist

  • Replication mode is chosen deliberately and its data-loss window is written down
  • Synchronous standbys are configured as a quorum, not a single named node
  • Semi-synchronous timeout fallback raises an alert
  • Every read call site is classified as primary-required or replica-safe
  • Read-after-write is handled by LSN check or a sticky primary window
  • Replication lag is monitored in both bytes and seconds, with alerts
  • hot_standby_feedback trade-off is a conscious decision
  • Failover is managed by a consensus system with fencing
  • Failover is rehearsed on a schedule and timed
  • Surviving replicas are re-pointed after promotion
  • Independent, restore-tested backups exist separately from replication