Database Snapshots en AlwaysOn: la trampa del Error 5094 que bloquea el recovery
Caso real ocurrido en febrero de 2026 sobre un cluster AlwaysOn de producción. Nombres de servidores y bases de datos enmascarados; comportamiento del motor, errores y procedimiento de recuperación son los reales.
El reinicio nocturno que dejó el AG sin sincronizar
A las 02:30 de un viernes, el equipo de infraestructura aplicó un parche de seguridad de Windows en uno de los nodos del cluster AlwaysOn de producción. Operación rutinaria, ventana de mantenimiento aprobada. El reinicio del servicio SQL Server ocurrió a las 02:38 sin incidencias aparentes.
A las 07:15 el responsable de aplicaciones avisó: la base de datos BD_CRM aparecía como Not synchronizing / Recovery Pending en el dashboard de AlwaysOn. Los logs del SQL Server mostraban este mensaje cada pocos segundos:
Error: 5094, Severity: 16, State: 1.
The operation cannot be performed on a database with database snapshots
or active DBCC replicas. Drop all database snapshots and DBCC replicas
of database 'BD_CRM' and rerun the operation.
Y la base de datos no arrancaba el proceso de recovery. Llevaba casi 5 horas en ese estado.
Por qué un snapshot bloquea el recovery completo
Lo primero, lo que casi todo el mundo ignora: los database snapshots en SQL Server son ficheros sparse asociados al fichero de datos de la BD origen. Cuando se modifica una página en la BD origen, SQL Server copia la versión anterior al snapshot antes de sobreescribirla — esto es lo que hace que un snapshot pueda servir lecturas point-in-time.
La consecuencia es que el snapshot depende físicamente de la BD origen y mantiene un puntero a sus ficheros. Si la BD origen está intacta, no hay problema. Pero hay un escenario donde se rompe la abstracción: el reinicio del servicio SQL Server con snapshots activos.
Cuando el motor arranca, intenta hacer recovery de cada base de datos en este orden:
master,model,msdb,tempdb.- Resto de bases de datos de usuario, en paralelo según
MAXDOP. - Snapshots de cada BD, una vez la BD origen está ONLINE.
El problema es que en versiones afectadas de SQL Server (observado en 2019 CU16 y 2022 CU8) el motor decide que antes de hacer recovery de la BD origen tiene que verificar el estado de sus snapshots. Como los snapshots todavía no están montados (porque dependen de la BD origen), el motor lanza el Error 5094 y aborta el recovery.
El resultado es un deadlock lógico: la BD no puede hacer recovery porque hay snapshots, y los snapshots no se pueden recuperar porque la BD está en recovery. Sin un AG en juego, esto no es crítico — simplemente borras el snapshot manualmente. Con AG, el secundario sí está sincronizado pero no puede hacer failover automático porque el primario sigue sin completar recovery.
El diagnóstico: encontrar los snapshots ocultos
Lo primero al ver una BD en Recovery Pending es enumerar los snapshots existentes. La query estándar:
-- Snapshots actualmente en el motor (incluye los que bloquean recovery)
SELECT
DB_NAME(d.database_id) AS snapshot_name,
d.state_desc,
d.create_date,
DB_NAME(d.source_database_id) AS source_db,
d.source_database_id
FROM sys.databases d
WHERE d.source_database_id IS NOT NULL;
En el caso real, esto devolvió:
| snapshot_name | state_desc | create_date | source_db |
|---|---|---|---|
BD_CRM_SNP_BACKUP_DAILY | RECOVERY_PENDING | 2026-02-12 22:00:00 | BD_CRM |
Un snapshot creado cada noche por un job de backup interno como protección antes de la ventana de mantenimiento. El job lo borraba al final, pero la noche del incidente el job había fallado a la hora de borrarlo (la BD origen ya estaba en recovery por el reinicio antes de que el script de cleanup pudiera ejecutarse).
La solución: drop del snapshot
Una vez identificado el snapshot, la solución es directa pero requiere ejecutarse desde master y con la BD origen en estado válido (RECOVERY_PENDING permite el DROP del snapshot):
USE master;
GO
-- Verificar primero
SELECT name, state_desc, source_database_id
FROM sys.databases
WHERE name = 'BD_CRM_SNP_BACKUP_DAILY';
-- Hacer drop del snapshot
DROP DATABASE BD_CRM_SNP_BACKUP_DAILY;
GO
-- Inmediatamente comprobar que recovery arrancó
SELECT name, state_desc
FROM sys.databases
WHERE name = 'BD_CRM';
En el incidente real, el drop tardó ~3 segundos. Inmediatamente después, la BD pasó a RECOVERY y luego a ONLINE en menos de 2 minutos. El AG empezó a sincronizar automáticamente y el indicador de salud volvió a verde.
Lo total perdido fueron 4h 40min de disponibilidad de escritura sobre BD_CRM. El secundario podría haberse promovido manualmente para un failover de emergencia, pero el equipo decidió esperar a la causa raíz para no propagar el problema al otro nodo.
Cómo evitarlo: checklist pre-mantenimiento
Después de este incidente, el equipo añadió cinco minutos al inicio de cualquier ventana de mantenimiento para verificar que no hay snapshots activos:
-- CHECKLIST DE MANTENIMIENTO -- ejecutar antes de cualquier reinicio
-- Si devuelve filas, hacer DROP de cada snapshot antes de continuar
SELECT
DB_NAME(database_id) AS snapshot,
DB_NAME(source_database_id) AS source_db,
create_date,
DATEDIFF(HOUR, create_date, GETDATE()) AS horas_activo
FROM sys.databases
WHERE source_database_id IS NOT NULL
ORDER BY create_date;
Y un alerta automática en SQL Agent que se ejecuta cada 30 minutos: si encuentra snapshots con más de 4 horas de antigüedad, manda un email al equipo. Los snapshots legítimos suelen vivir minutos (para hacer un BACKUP DATABASE consistente, por ejemplo). Si llevan horas, son huérfanos.
Por qué este patrón es más peligroso de lo que parece
Hay tres razones por las que este caso particular es especialmente traicionero:
- El error es genérico. El 5094 dice "drop snapshots" pero no señala cuál bloquea recovery ni en qué BD. Si tienes 12 BDs con cada una potencialmente teniendo snapshot, el diagnóstico inicial puede llevar tiempo.
- Solo afecta tras reinicios. En operación normal, snapshots y BD coexisten sin problema. El bug se manifiesta solo en el momento del recovery, lo cual es justo cuando estás bajo presión por restaurar el servicio.
- AlwaysOn enmascara el síntoma. El secundario sigue sirviendo lecturas (si tienes routing read-only), así que la mitad de la aplicación parece funcionar. Hasta que alguien intenta escribir y descubre que no hay primario.
Conclusión
Database snapshots y AlwaysOn no se llevan bien en escenarios de reinicio. Si tu cluster usa snapshots por cualquier motivo (backups consistentes, reporting, testing temporal), añade la verificación de sys.databases WHERE source_database_id IS NOT NULL a tu checklist pre-mantenimiento.
El coste de la verificación es cero. El coste de no hacerla es 5 horas de downtime explicándole al CIO por qué el AG cayó después de un parche de Windows.