Infrastructure

Azure SQL : qualifier un failover group avant un basculement forcé

Un runbook de production pour mesurer la réplication, vérifier le listener et la capacité secondaire, puis décider basculement planifié, forcé ou maintien en place.

16 sept. 2026 azureazure-sqlsql-databasefailover-groupgeo-replicationdisaster-recoverydnsobservabilityrunbookrollbackproduction

Une région répond mal, les connexions Azure SQL expirent et le failover group existe précisément pour ce scénario. La tentation est immédiate : promouvoir le secondaire avec --allow-data-loss pour rétablir le service. Cette commande peut réduire l’indisponibilité, mais elle transforme aussi un incident de disponibilité en décision irréversible sur les données.

Le cas d’usage est une API transactionnelle dont plusieurs bases appartiennent au même failover group. Le primaire est dégradé sans être totalement inaccessible. L’équipe doit établir l’état de réplication, prouver que l’application utilise le listener, valider la région secondaire et choisir entre attente, basculement planifié ou basculement forcé. Le runbook se termine par une décision documentée et un chemin de failback, pas par un clic isolé.

Figer le contrat de décision

Avant toute commande d’écriture, consignez l’heure UTC du début d’impact, les bases concernées, le RPO accepté et le point au-delà duquel l’indisponibilité devient plus coûteuse qu’une perte de données potentielle.

yaml failover-decision-contract.yml
incident_id: INC-2026-0916-sql
failover_group: fog-orders-prod
primary_server: sql-orders-weu
secondary_server: sql-orders-neu
read_write_listener: fog-orders-prod.database.windows.net
affected_databases:
- orders
- payments
rpo_max_seconds: 30
rto_max_minutes: 15
write_freeze_requested: true
forced_failover_approvers:
- incident-commander
- data-owner
rollback: planned-failback-after-primary-recovery-and-validation

Un RPO ne doit pas être reformulé en « quelques secondes ». Il doit être comparé au replication_lag_sec observé et aux transactions métier produites pendant cette fenêtre. Si le primaire accepte encore des écritures, tentez de les suspendre proprement : chaque nouvelle transaction agrandit l’enveloppe de divergence.

Prouver la topologie et le rôle courant

Interrogez le groupe depuis le serveur que l’équipe considère comme primaire. Ne déduisez pas le rôle depuis le nom de la ressource, une ancienne documentation ou la région habituellement active.

bash 01-failover-group-inventory.sh
PRIMARY_RG="rg-data-weu"
PRIMARY_SERVER="sql-orders-weu"
SECONDARY_RG="rg-data-neu"
SECONDARY_SERVER="sql-orders-neu"
FOG="fog-orders-prod"

az sql failover-group show \
--resource-group "$PRIMARY_RG" \
--server "$PRIMARY_SERVER" \
--name "$FOG" \
--query "{role:replicationRole,databases:databases,partners:partnerServers,readWrite:readWriteEndpoint,readOnly:readOnlyEndpoint}" \
--output json

az sql failover-group show \
--resource-group "$SECONDARY_RG" \
--server "$SECONDARY_SERVER" \
--name "$FOG" \
--query "{role:replicationRole,databases:databases,partners:partnerServers}" \
--output json

Les deux vues doivent décrire le même groupe, les mêmes bases et des rôles opposés. Une base absente, un partenaire inattendu ou un groupe visible d’un seul côté est un incident de topologie à qualifier avant le failover.

Mesurer la réplication base par base

Un failover group bascule toutes ses bases ensemble, mais leur retard n’est pas nécessairement identique. Commencez par l’état exposé par Azure Resource Manager, puis mesurez la liaison dans chaque base encore joignable.

bash 02-list-replication-links.sh
for DB in orders payments; do
az sql db replica list-links \
  --resource-group "$PRIMARY_RG" \
  --server "$PRIMARY_SERVER" \
  --name "$DB" \
  --query "[].{database:databaseName,partner:partnerServer,role:role,replicationState:replicationState,percentComplete:percentComplete}" \
  --output table
done

Depuis chaque base primaire, la vue dynamique fournit la preuve la plus proche du risque de perte :

sql 03-geo-replication-status.sql
SELECT
  DB_NAME() AS database_name,
  partner_server,
  partner_database,
  replication_state_desc,
  last_replication,
  replication_lag_sec
FROM sys.dm_geo_replication_link_status;

Exécutez cette requête séparément dans chaque base, pas dans master. Conservez l’heure de collecte et le résultat brut. CATCH_UP avec un lag faible rend un basculement planifié plausible ; SUSPENDED, un last_replication ancien ou une base inaccessible impose d’évaluer explicitement la perte. Une moyenne masque la base la plus en retard : la décision porte sur le maximum observé et sur sa valeur métier.

Vérifier la cible avant de la promouvoir

Une réplique à jour n’est pas encore une région de reprise prête. Vérifiez que le serveur secondaire et ses dépendances peuvent absorber la charge d’écriture : service tier et capacité, règles d’accès, identités Entra, clés gérées, Private DNS si le chemin est privé, quotas et composants applicatifs régionaux.

Le Private Endpoint, lorsqu’il existe, n’est qu’un segment du chemin. Le contrôle utile consiste à résoudre et joindre le listener depuis les workloads qui seront actifs après le basculement, puis à confirmer que leur identité peut ouvrir une session sur la base secondaire sans utiliser le nom direct du serveur.

text secondary-readiness.txt
Control                     Expected proof
Server and database capacity    Same approved tier or tested DR capacity
Replication                    Every database present; worst lag recorded
Listener path                  Application uses failover-group listener
DNS                            Listener resolves from future active workloads
Identity                       Login succeeds with production runtime identity
Dependencies                   App, queue, storage and secrets ready in target region
Write protection               No uncontrolled dual writer remains active
Observability                  Errors, latency and business checks visible by region

Si l’application se connecte à sql-orders-weu.database.windows.net au lieu du listener, le failover Azure ne déplacera pas ce trafic. Corrigez ou préparez cette dépendance avant de compter sur le groupe.

Qualifier le comportement applicatif et DNS

Le listener read-write conserve son nom et Azure met à jour sa cible après le basculement. Cela ne garantit pas que tous les processus rouvrent immédiatement leurs connexions. Les pools existants, les caches DNS et les retries mal bornés peuvent prolonger l’incident ou multiplier les écritures ambiguës.

Depuis chaque zone d’exécution, capturez avant le failover :

  • la chaîne de connexion sans secret, notamment le hostname et la base ;
  • la résolution du listener et son TTL observé ;
  • le nombre de connexions actives et la capacité à les renouveler ;
  • la politique de retry pour les erreurs transitoires ;
  • un identifiant métier permettant de vérifier la dernière écriture confirmée.

Le canari doit ouvrir une nouvelle connexion via le listener. Une session SQL restée ouverte avant la bascule n’est pas une preuve de reroutage.

Choisir le mode de basculement

Si le primaire et le secondaire communiquent, utilisez un basculement planifié. Le service synchronise les bases avant d’inverser les rôles, ce qui vise un changement sans perte de données.

bash 04-planned-failover.sh
az sql failover-group set-primary \
--resource-group "$SECONDARY_RG" \
--server "$SECONDARY_SERVER" \
--name "$FOG"

Si le primaire est indisponible et que le RTO impose une reprise, --allow-data-loss autorise un basculement même lorsque la synchronisation ne peut pas aboutir. Ne l’exécutez qu’après avoir consigné le pire lag observé, la dernière transaction métier vérifiable, les approbateurs et les conséquences attendues.

bash 05-forced-failover.sh
az sql failover-group set-primary \
--resource-group "$SECONDARY_RG" \
--server "$SECONDARY_SERVER" \
--name "$FOG" \
--allow-data-loss

L’option --try-planned-before-forced-failover essaie d’abord le chemin planifié puis force si celui-ci échoue. Elle reste une autorisation de perdre des données : ne la traitez pas comme une variante « plus sûre » sans critère d’arrêt.

Valider le nouveau primaire sans fermer trop tôt

Après la commande, relisez le rôle sur les deux serveurs et attendez que toutes les bases aient changé de rôle. Ouvrez ensuite de nouvelles connexions via le listener, jamais via le hostname direct.

La validation minimale couvre :

  1. le listener read-write mène au nouveau primaire depuis chaque workload actif ;
  2. une lecture retrouve la dernière transaction attendue ou borne précisément l’écart ;
  3. une écriture canari idempotente est confirmée puis relue ;
  4. les erreurs de connexion, retries et latences reviennent dans la plage approuvée ;
  5. aucun composant resté dans l’ancienne région ne continue à écrire directement.

Conservez l’identifiant du canari, les heures UTC, les sorties CLI et le résultat métier. Un statut Azure Primary sans transaction applicative ne suffit pas.

Décider maintien, failback ou restauration

text failover-decision.txt
MAINTAIN THE NEW PRIMARY
Listener, canary and business checks pass
Data-loss envelope is measured and accepted
Old-region writers are stopped or fenced

HOLD AND INVESTIGATE
Roles changed but one database, dependency or workload is unhealthy
Keep writes bounded; do not stack another failover on uncertain state

PLANNED FAILBACK
Original region is stable and replication has caught up
Execute a planned role reversal during an approved window
Repeat listener, write and business validation

RESTORE OR RECONCILE
Forced failover lost confirmed transactions
Preserve both timelines and reconcile from business evidence
Never merge by replaying an unbounded queue or old writer

Le rollback n’est pas un second failover forcé lancé par réflexe. Une fois l’ancienne région revenue, attendez que la réplication soit saine, bloquez tout writer résiduel et effectuez un failback planifié. Si des transactions confirmées manquent, le chemin est une restauration ou une réconciliation métier, pas une simple inversion de rôle.

Conclusion

Un failover group Azure SQL fournit le mécanisme de changement de rôle ; il ne prend pas la décision de perte de données à la place de l’équipe. Le runbook exploitable relie le rôle réel, le pire lag par base, le listener utilisé par l’application, la capacité de la cible et une preuve métier.

La décision devient alors explicite : attendre si le risque de perte dépasse l’impact d’indisponibilité, basculer de façon planifiée lorsque la synchronisation reste possible, ou forcer uniquement avec une enveloppe de perte acceptée. Après reprise, validez par une nouvelle connexion et une écriture canari, puis préparez un failback planifié ou une réconciliation documentée.