AlwaysOn SUSPEND_FROM_PARTNER: cuando reinicios secuenciales sin failover detienen tu Availability Group
> Caso real ocurrido en mayo de 2026 sobre un cluster AlwaysOn de produccion. Nombres de servidores y bases de datos enmascarados; comportamiento del motor, errores y procedimiento de recuperacion son los reales.
## La ventana de 30 minutos que se comio media maana
La ventana de mantenimiento de un cluster AlwaysOn debia durar 30 minutos. Acabo durando casi cuatro horas, y no porque algo se rompiese en el sistema operativo ni porque el parcheo fallara, sino porque el motor de SQL Server hizo exactamente lo que estaba diseado para hacer: protegerse a si mismo de una situacion en la que dos nodos se habian movido en paralelo sin que nadie hubiese coordinado los roles.
Era una manana de mayo, parcheo de seguridad de Windows sobre los dos nodos del Availability Group, ventana coordinada con infraestructura, comunicacion al negocio enviada el dia anterior. Sobre el papel, una operacion rutinaria. En la practica, una leccion bastante cara sobre por que el orden de los reinicios en un AG no es un detalle estetico, sino una decision operativa con consecuencias directas sobre la disponibilidad.
Esta es la historia de como un SUSPEND_FROM_PARTNER me obligo a reanudar manualmente cada base de datos del grupo, hacer failover de vuelta al nodo original y explicar al responsable de aplicaciones por que durante varias horas su BD estuvo NOT SYNCHRONIZING.
## El contexto: dos nodos, una ventana, cero coordinacion de roles
El cluster tenia la topologia tipica de un AG sincrono con failover automatico:
- Nodo primario del AG: rol PRIMARY en el momento de iniciar la ventana, sirviendo escrituras de la BD de aplicacion.
- Nodo secundario: rol SECONDARY, replica sincrona, candidato a failover automatico si el primario caia.
El plan era sencillo en su descripcion: parchear primero el secundario, validar que volvia limpio al AG, y despues parchear el primario. Asumiamos que el failover automatico haria su trabajo cuando el primario se reiniciara, y que al volver el ultimo nodo lo recogeriamos como secundario sin mas complicaciones.
Lo que no contemplamos, y aqui esta el error humano del que parte todo lo demas, fue que ese segundo failover automatico, el que ocurre cuando el primario se reinicia mientras el otro nodo ya esta de vuelta, le dice al motor de SQL Server algo muy concreto: "ha habido un cambio de rol mientras uno de los nodos estaba fuera de servicio". Y SQL Server, ante esa frase, no se fia. Suspende el movimiento de datos hasta que un DBA verifique manualmente que todo esta bien.
## Que paso: el log lleno de SUSPEND_FROM_PARTNER
Cuando el nodo primario original volvio del reinicio, lo hizo como SECONDARY, porque el otro nodo se habia promovido a PRIMARY mientras tanto. Hasta ahi, nada raro. Lo raro empezo unos segundos despues, cuando el dashboard de AlwaysOn marco las bases de datos del AG en rojo: NOT SYNCHRONIZING. La BD de aplicacion, la BD de master del AG y la de msdb del AG aparecian las tres detenidas.
El error log del nodo recien arrancado mostraba, para cada base de datos del grupo, una entrada como esta:
`
Always On Availability Groups data movement for database 'X' has been suspended
for the following reason: "failover from partner" (Source ID 1; Source string: 'SUSPEND_FROM_PARTNER').
To resume data movement on the database, you will need to resume the database manually.
`
Esa cadena, SUSPEND_FROM_PARTNER, es la pista. No es un fallo de red, no es un problema de quorum, no es corrupcion. Es el motor diciendo, con todas las letras, que el partner (el nuevo primario) ha enviado una orden de suspender el movimiento de datos hacia este secundario. Y lo ha hecho intencionadamente.
## El diagnostico: reconstruir la linea temporal
Para entender por que el motor habia llegado a esa decision, hubo que reconstruir el orden exacto de los eventos cruzando los error logs de los dos nodos y el log del Windows Cluster. La linea temporal quedo asi:
| Hora aproximada | Evento |
|---|---|
| 08:36 | El nodo secundario inicial se apaga para mantenimiento |
| 08:36 | El nodo primario detecta la caida y mantiene el rol PRIMARY sin cambios |
| ~09:50 | El primario original se apaga para parcheo, sin failover manual previo |
| ~09:50 | El otro nodo, ya de vuelta, hace failover automatico y pasa a PRIMARY |
| 09:57:14 | El primario original arranca como SECONDARY |
| 09:57:25 | Las bases de datos del AG quedan SUSPENDED con razon SUSPEND_FROM_PARTNER |
| 09:57:46 | DBA lanza ALTER DATABASE ... SET HADR RESUME en cada BD |
| 09:57:55 | Failover manual de vuelta al nodo primario original |
| 09:58:34 | AG completamente sincronizado y operativo |
Una vez ordenada la secuencia, el patron es facil de leer: dos reinicios consecutivos sobre los dos nodos del AG, con un failover automatico entre medias, sin ningun failover manual coordinado por el DBA. El motor termina con un SECONDARY que estuvo fuera mientras el rol PRIMARY cambiaba, y para SQL Server eso es justo el escenario en el que prefiere parar y esperar a que alguien revise.
## La causa raiz: por que SQL Server suspende intencionadamente
Aqui esta la parte que conviene entender bien, porque no es un bug, no es un comportamiento documentado a medias y no se arregla con un parche. Es una decision de diseo.
Cuando un nodo SECONDARY de un Availability Group ha estado offline mientras se ha producido un failover en el grupo, al volver online el nuevo PRIMARY le envia automaticamente un SUSPEND_FROM_PARTNER. La logica del motor es la siguiente:
- Durante el tiempo que el SECONDARY estuvo fuera, no recibio el log que el nuevo PRIMARY estaba generando.
- Cuando el SECONDARY vuelve, su LSN esta retrasado respecto al PRIMARY.
- En condiciones normales, eso se resuelve solo: el SECONDARY pide los bloques de log que le faltan y se pone al dia.
- Pero hay un caso intermedio peligroso: que durante el tiempo offline se haya producido mas de un cambio de rol o que la rama de log del SECONDARY haya divergido respecto al PRIMARY actual.
En ese caso, reanudar automaticamente el data movement podria propagar log inconsistente o, peor, sobrescribir el log del PRIMARY con el del SECONDARY si la replicacion se hace en la direccion equivocada. SQL Server prefiere no jugar a adivinar, asi que congela el movimiento de datos y delega la decision al DBA. Es exactamente la misma filosofia conservadora que aplica el motor cuando ve un snapshot huerfano bloqueando un recovery: ante la duda, parar.
En este incidente concreto, el log no habia divergido, los dos nodos estaban consistentes y el RESUME funciono limpiamente. Pero el motor no podia saberlo a priori, y por eso suspende por defecto.
## La resolucion: RESUME por base de datos y failover de vuelta
Una vez identificada la causa, el procedimiento de recuperacion es directo. Desde el SECONDARY afectado, hay que reanudar manualmente cada base de datos suspendida:
`sql
ALTER DATABASE [BD_aplicacion] SET HADR RESUME;
ALTER DATABASE [AG_PROD_master] SET HADR RESUME;
ALTER DATABASE [AG_PROD_msdb] SET HADR RESUME;
`
Cada RESUME tardo apenas un par de segundos. El movimiento de datos arranco, el SECONDARY se puso al dia con el PRIMARY actual y las bases de datos pasaron de NOT SYNCHRONIZING a SYNCHRONIZING y enseguida a SYNCHRONIZED.
Con el AG ya sincronizado, el ultimo paso fue devolver el rol PRIMARY al nodo original, para dejar la topologia como estaba antes del mantenimiento:
`sql
ALTER AVAILABILITY GROUP [AG_PROD] FAILOVER;
`
Failover limpio, comprobacion de listener, validacion desde la aplicacion. El AG volvio a estar plenamente operativo a las 09:58. Pero la ventana ya habia sangrado mucho mas tiempo del esperado: entre la deteccion, el diagnostico inicial, el procedimiento de RESUME y las validaciones, el impacto real sobre escrituras de la BD de aplicacion fue de practicamente cuatro horas.
## El procedimiento correcto: failover manual antes de cada reinicio
La leccion operativa es sencilla de enunciar y de aplicar, y se reduce a una regla: antes de reiniciar el nodo que en ese momento es PRIMARY, hay que hacer un failover manual al otro nodo. Asi el reinicio se produce siempre sobre un SECONDARY, no se dispara ningun failover automatico no coordinado y el motor no tiene que decidir si suspender o no el movimiento de datos.
El procedimiento, paso a paso, para una ventana de mantenimiento de dos nodos seria:
1. Estado inicial: identificar quien es PRIMARY y quien es SECONDARY. No asumir, comprobar con sys.dm_hadr_availability_replica_states.
2. Reinicio del SECONDARY actual: parchear primero el nodo que ya es SECONDARY. Apagar el servicio, reiniciar Windows, validar que vuelve al AG en estado SYNCHRONIZED.
3. Failover manual antes del segundo reinicio: con el SECONDARY ya recuperado, ejecutar desde el nodo que aun es PRIMARY:
`sql
ALTER AVAILABILITY GROUP [AG_PROD] FAILOVER;
`
Ahora el PRIMARY original es SECONDARY, y el otro nodo es PRIMARY.
4. Reinicio del nuevo SECONDARY: parchear el segundo nodo, que ya no es PRIMARY. Esto evita el failover automatico no controlado.
5. Failover de vuelta: una vez los dos nodos estan parcheados y sincronizados, hacer un ultimo failover manual para restaurar la topologia original si el negocio lo requiere.
Con esta secuencia, los dos reinicios se hacen siempre sobre el SECONDARY del momento, no se produce ningun cambio de rol no coordinado, y el motor nunca tiene motivos para enviar un SUSPEND_FROM_PARTNER.
## La leccion: AlwaysOn no es solo configurar el AG
Lo que me llevo de este incidente, y lo que repito cada vez que alguien me pregunta por mantenimiento de Availability Groups, es que un AG bien configurado no te exime de un procedimiento operativo bien ejecutado. La alta disponibilidad de SQL Server cubre fallos no anunciados, no procesos planificados.
Cuando el equipo de infraestructura programa una ventana de parcheo, el DBA tiene que entrar a coordinar el orden de los reinicios. No vale con confiar en el failover automatico, porque el failover automatico esta diseado para incidentes, no para mantenimientos. Cuando lo usas como sustituto del procedimiento operativo, te encuentras justo en el escenario que dispara las protecciones del motor: cambios de rol mientras un nodo esta fuera, log divergente potencial y SUSPEND_FROM_PARTNER esperandote en el log.
La buena noticia es que el coste de hacerlo bien es practicamente cero: un ALTER AVAILABILITY GROUP ... FAILOVER antes de cada reinicio, validacion de roles entre pasos, y la ventana de 30 minutos se queda en 30 minutos. La mala noticia es que el coste de hacerlo mal es proporcional al tamao de la BD y a la criticidad del servicio. En este caso fueron cuatro horas. En otros entornos que conozco han sido jornadas enteras.
Si gestionas un AG y aun no tienes documentado el procedimiento de mantenimiento con failovers manuales explicitos, este es probablemente el siguiente runbook que te toca escribir.