Cuando el failover no es seguro: cómo construir PostgreSQL de alta disponibilidad sobre Kubernetes
(datadoghq.com)- Cómo resolvieron una debilidad estructural en clústeres de PostgreSQL sobre k8s, donde durante fallas de red se acumula el retraso de replicación (replication lag) y el failover seguro se vuelve imposible
- La arquitectura existente priorizaba la disponibilidad (availability) por encima de la durabilidad (durability), de modo que mientras el primario seguía aceptando escrituras, las réplicas se iban quedando atrás hasta que ya no había ningún candidato que pudiera promoverse sin pérdida de datos
- Como solución, aplicaron replicación síncrona (synchronous replication) a los candidatos de failover y la coordinaron con el administrador de alta disponibilidad open source Patroni
- Con un modelo híbrido de replicación en el que solo los standby del leader pool participan en replicación síncrona y las réplicas de lectura siguen siendo asíncronas, lograron equilibrar durabilidad y latencia
- Aunque hubo un costo de rendimiento, como un aumento del 53% en la latencia de escritura al usar el modo
remote_apply, consiguieron failover automático y seguro tras validar cinco escenarios de falla
El problema que salió a la luz en un gameday
- Datadog realiza gamedays de forma periódica para encontrar de manera proactiva huecos en sistemas y procesos, aplicando carga intencional a la plataforma para aprender cómo responde en condiciones reales
- En uno de esos gamedays, simularon una falla de zona de disponibilidad (AZ) en staging, lo que provocó latencia de red y expuso una debilidad en la arquitectura de PostgreSQL
- Los nodos primarios/writer de varios clústeres de PostgreSQL sobre Kubernetes estaban ejecutándose en la AZ afectada
- El fuerte aumento en la latencia de red impidió que el primario se comunicara de forma estable con las réplicas, aumentó el retraso de replicación → se estancaron las escrituras → las aplicaciones comenzaron a entregar datos desactualizados
- Como no había ninguna réplica suficientemente actualizada, el failover no era seguro y el clúster quedó prácticamente detenido
- Esta arquitectura funcionaba bien en condiciones normales, pero ante ciertos problemas de red priorizaba la disponibilidad sobre la durabilidad, así que no existía una ruta de recuperación segura
- El primario seguía aceptando escrituras incluso mientras la replicación se retrasaba, lo que aumentaba el lag y hacía que las réplicas se quedaran aún más atrás
- Como resultado, no era posible promover un candidato de failover sin riesgo de pérdida de datos, y la única opción era esperar a que bajara la latencia y las réplicas alcanzaran al primario
- El objetivo era lograr un failover automático y seguro sin degradar más de lo necesario las características de rendimiento de PostgreSQL
Arquitectura base: PostgreSQL sobre Kubernetes
- El clúster de PostgreSQL sobre Kubernetes está compuesto por dos pools: leader pool y read replica pool; PostgreSQL es un sistema de un solo writer (single-writer)
- Separar lectura y escritura permite escalar lecturas sin cargar al líder, y mantener una latencia de escritura predecible y estable
- El leader pool está formado por 1 nodo writer activo único que procesa todas las escrituras y 2 nodos standby
- Los standby no atienden tráfico de la aplicación, pero pueden promoverse si falla el líder
- El read replica pool está compuesto por múltiples nodos que atienden tráfico de solo lectura; está optimizado para escalado de lectura y aislamiento de consultas, y queda excluido intencionalmente del failover
El papel de Patroni y ZooKeeper
- Patroni administra replicación, failover y elección de líder, y usa ZooKeeper como almacén distribuido de configuración (DCS)
- ZooKeeper guarda metadatos como la key/lock del líder actual, la configuración del clúster y el estado de replicación de cada miembro (por ejemplo, el LSN más reciente)
- Con esa información, Patroni toma decisiones conservadoras sobre promoción y degradación, priorizando consistencia de datos por encima de failovers agresivos
- Cuando un nodo nuevo se une al clúster, primero revisa en ZooKeeper si ya existe un líder
- Si no hay líder, intenta obtener la key de líder creando un znode temporal; ZooKeeper garantiza que solo un nodo pueda obtenerla, lo que evita la formación de múltiples primarios
- Si ya existe un líder, se configura como réplica e inicia la replicación por streaming
- En una partición de red (network partition), una réplica que pierde conexión con el líder o con ZooKeeper no puede verificar el estado del clúster, así que Patroni pausa o degrada temporalmente ese nodo
- Si el líder pierde conectividad, Patroni coopera con ZooKeeper para que solo un standby elegible obtenga el lock de líder, garantizando failover controlado incluso ante fallas parciales de red
- Cuando se restablece la conectividad, si el líder anterior no puede recuperar el lock de líder, se degrada a sí mismo a standby, evitando split brain
Por qué no era posible un failover seguro
- En el modelo de un solo writer, cuando ocurre una falla, Patroni elige un nuevo líder entre los standby sanos
- Para evitar pérdida de datos, Patroni ejecuta verificaciones de seguridad antes de promoverlo; la principal es confirmar que el retraso de replicación esté dentro del umbral
maximum_lag_on_failover- Si el standby está por detrás del líder, al promoverlo puede haber datos faltantes o inconsistentes
- En el gameday, cuando el primario perdió conectividad, el retraso de replicación de todos los standby superó el umbral y Patroni rechazó correctamente el failover
- El clúster quedó sin un primario seguro para escritura no por culpa de Patroni, sino porque no existía ningún candidato seguro para promover
Los dos modos de replicación por streaming
- En la replicación por streaming, el líder envía continuamente a las réplicas el write-ahead log (WAL) con todos los cambios, y las réplicas aplican ese WAL localmente para mantenerse sincronizadas
-
Replicación asíncrona (predeterminada)
- El líder no espera confirmación de las réplicas antes de hacer commit de la transacción
- Minimiza la latencia de escritura y soporta alto throughput
- Pero si falla el líder, una transacción ya confirmada en el primario pero aún no replicada puede perderse durante la promoción
-
Replicación síncrona
- El líder espera la confirmación de al menos 1 réplica antes de responder al cliente
- Esto reduce mucho el riesgo de que al menos una réplica se quede demasiado atrás, y ofrece mayor durabilidad porque responde solo después de confirmar que la transacción committeada ya existe en otro nodo
- Hace más probable que el candidato de failover esté actualizado y pueda promoverse sin riesgo de divergencia de datos
Configuración híbrida de replicación
- Para equilibrar durabilidad, latencia y throughput, adoptaron un modelo híbrido de replicación
- Los nodos standby del leader pool participan en replicación síncrona; el líder hace commit de una escritura solo después de la confirmación del standby síncrono designado
- Las read replicas siguen con replicación asíncrona; solo atienden tráfico de lectura y no son objetivo de failover, lo que limita la carga de replicación sobre el leader pool
- Así, aplican garantías estrictas de durabilidad solo a los candidatos de failover sin imponer el mismo costo de latencia a las réplicas de lectura
Ajustes de PostgreSQL y Patroni para un failover seguro
- Para activar la replicación síncrona, ajustaron parámetros tanto en PostgreSQL como en Patroni
-
Parámetros principales ajustados
synchronous_mode: activa la replicación síncrona en Patroni; si está entrue, hace commit tras la confirmación del standby síncrono segúnsynchronous_node_count(valor predeterminado false → true, administrado por Patroni, obligatorio)synchronous_node_count: cantidad de nodos standby síncronos; se usa para generar la listasynchronous_standby_names(valor predeterminado 1 → 1, administrado por Patroni, opcional)synchronous_mode_strict: fuerza el modo síncrono estricto; si está entruey no hay réplicas disponibles, bloquea escrituras en vez de cambiar a modo asíncrono (valor predeterminado false → true, administrado por Patroni, opcional)synchronous_commit: configuración de durabilidad de commit en PostgreSQL (valor predeterminado on → remote_apply, administrado por PostgreSQL, opcional)
- Después de aplicarlo, el líder solo responde la transacción al cliente una vez que el standby síncrono confirma que recibió y aplicó los datos
Equilibrio entre durabilidad y latencia
- La replicación síncrona mejora la durabilidad, pero incrementa la latencia de escritura porque el líder debe esperar confirmación del standby síncrono; bajo carga sostenida también puede afectar el throughput
- El impacto en rendimiento depende de la cantidad de standby síncronos (
synchronous_node_count) y del nivel de durabilidad configurado consynchronous_commit -
Trade-offs por nivel de durabilidad de
synchronous_commitremote_apply: espera hasta que la réplica escriba, haga flush y reproduzca el WAL; ofrece la consistencia más fuerte y la latencia más altaon(internamente remote_flush): espera hasta que la réplica haga flush del WAL al disco; brinda fuerte durabilidad, pero todavía no permite lectura en el standbyremote_write: espera hasta que el WAL llegue a la caché del sistema operativo de la réplica (no al disco); tiene menor latencia, pero es vulnerable ante crashes del SOlocal: hace commit tras el flush al disco local sin esperar al standby; no garantiza durabilidad entre nodosoff: hace commit antes del flush local del WAL; ofrece la menor latencia y el mayor riesgo de pérdida de datos
Benchmark de rendimiento de replicación síncrona
- Como cada commit debe esperar la confirmación de uno o más standby, la replicación síncrona agrega latencia; para cuantificar el impacto hicieron benchmarks con pgbench, la herramienta estándar de pruebas de carga de PostgreSQL (Patroni versión 3.2.1)
- Usaron la suite transaccional TPC-B, que simula una mezcla simple de lectura y escritura, y midieron dos métricas
- Latencia promedio: tiempo medio de procesamiento por transacción (ms)
- Transacciones por segundo (tps): cantidad de transacciones completadas por segundo, excluyendo el tiempo de establecimiento de conexión
-
Parámetros de prueba
- Variaron el factor de escala (tamaño de la base de datos), cantidad de clientes (tráfico concurrente), número de hilos (CPU y paralelismo) y número de transacciones (intensidad de carga) para aproximar condiciones parecidas a producción
- El modo de commit por quórum para replicación síncrona no se probó en este benchmark
-
Resultados del benchmark
- Aumento de latencia de escritura:
remote_apply53%,on46%,remote_write38%,local32% - Reducción de throughput (tps):
remote_apply34%,on31%,remote_write27%,local23% remote_applyesperó hasta la reproducción y aplicación del WAL en la réplica, por eso mostró de forma consistente la mayor latencia y el menor throughput, pero su consistencia más fuerte lo hizo adecuado para failover seguro
- Aumento de latencia de escritura:
-
Implementación en producción
- Después del benchmarking, desplegaron
remote_applyen varios clústeres con alta tasa de escritura, y bajo carga sostenida de producción no observaron un impacto significativo en la latencia ni en el throughput de escritura a nivel aplicación - Para mitigar riesgos de rendimiento, hicieron un rollout gradual por centro de datos y por nivel de carga, aplicando períodos de observación y monitoreo continuo entre etapas
- Por ejemplo, una carga de procesamiento de recursos de alto throughput siguió funcionando sin retrasos de procesamiento ni backlog aguas abajo, aun con el aumento de latencia de escritura en la BD tras activar replicación síncrona
synchronous_commitpuede ajustarse de inmediato sin downtime mediantepatronictl edit-config, lo que da flexibilidad para reducir rápidamente la durabilidad del commit en workloads de throughput extremadamente alto
- Después del benchmarking, desplegaron
Validación del failover con escenarios de falla
- Validaron que la replicación síncrona y el control estricto de failover protegieran la integridad de datos, evitaran split-brain y aseguraran recuperación automática
-
Escenario 1: pérdida de 1 standby síncrono
- Si se pierde un standby síncrono, Patroni intenta asignar otro standby elegible para mantener la replicación síncrona
- Patroni en el nodo líder detecta conexiones de streaming caídas, estancadas o con retraso usando
pg_stat_replication, y rastrea la membresía de réplicas mediante ZooKeeper - Recalcula la lista de réplicas por streaming sanas y elegibles, actualiza
synchronous_standby_namessegúnsynchronous_node_county sigue operando con replicación síncrona activa
-
Escenario 2: pérdida de todos los standby síncronos
- El comportamiento depende del valor de
synchronous_mode_strict -
Modo no estricto: prioridad a la disponibilidad de escritura
- Patroni vacía
synchronous_standby_namesy desactiva temporalmente la replicación síncrona; hasta que se reincorpore una réplica sana, el líder cambia a asíncrono y sigue permitiendo escrituras
- Patroni vacía
-
Modo estricto: bloqueo de escrituras por seguridad
- Patroni configura
synchronous_standby_namescomo*; aunque no haya un standby síncrono explícito, PostgreSQL acepta y hace commit localmente de la transacción de escritura, pero bloquea la respuesta al cliente hasta que una réplica confirme el WAL - Cuando se reconecta una réplica apta para unirse a la replicación síncrona, Patroni le asigna el rol de standby síncrono
- Patroni configura
- El comportamiento depende del valor de
-
Escenario 3: indisponibilidad de todos los standby y réplicas
- Si todas las réplicas están indisponibles y
synchronous_mode_strict = true, PostgreSQL retiene la confirmación de transacciones hasta que vuelva al menos una réplica elegible - Se mantiene la consistencia de datos, pero a nivel aplicación se produce una imposibilidad temporal de escribir
- Si todas las réplicas están indisponibles y
-
Escenario 4: falla del líder durante un commit síncrono
- Es un caso límite en el que el líder falla mientras espera la confirmación del standby síncrono y antes de recibirla
- Causas comunes: cancelación de la transacción por el cliente durante el commit, crash o terminación del proceso PostgreSQL del líder, o partición de red durante la fase de commit
- Si PostgreSQL hizo flush del WAL localmente pero falló al replicarlo al standby, la transacción no aparece en la réplica porque nunca hubo confirmación
- Si el líder se cae antes de replicar el WAL al standby síncrono y ese standby es promovido, puede perderse la transacción y divergir el historial entre el líder anterior y el nuevo primario
- El líder anterior usa pg_rewind para identificar el punto de divergencia de timeline y rebobinar su directorio de datos para alinearlo con el timeline del nuevo primario, descartando cambios locales no replicados antes de reincorporarse como standby
- Este comportamiento no es resultado de Patroni, sino del manejo interno del commit síncrono en PostgreSQL, y subraya la necesidad de ajustar y monitorear cuidadosamente la configuración de quórum y el mecanismo de confirmación
-
Escenario 5: ZooKeeper no disponible
- Si ZooKeeper no está disponible, Patroni no puede verificar liderazgo ni hacer una nueva elección, así que cambia a un comportamiento conservador para evitar inconsistencias de datos
-
Con failsafe mode desactivado
- Aunque ZooKeeper sea inaccesible, si el líder sigue operativo y todos los nodos están sanos, las escrituras continúan, pero solo hasta que expire el TTL del lock del líder
- Cuando pasa el tiempo del loop de actualización de la key del líder y ya no puede renovarla, Patroni degrada al líder y cambia el clúster a solo lectura
-
Con failsafe mode activado
- Si el líder pierde conexión con ZooKeeper, Patroni verifica de forma continua mediante la API REST si puede alcanzar a todos los miembros del clúster
- Solo permite continuar con escrituras si todos los miembros son accesibles; de lo contrario, degrada el clúster a solo lectura
Failover y switchover manuales bajo replicación síncrona
- Además del failover y switchover automáticos basados en health checks y coordinación con ZooKeeper, Patroni también soporta operaciones manuales mediante el comando
patronictl; como no todos los standby son candidatos válidos cuando la replicación síncrona está activa, aplica guardrails para proteger la integridad de datos -
Failover hacia un standby asíncrono
patronictl failover: falla si el nodo elegido es asíncronopatronictl switchover: falla si el nodo elegido es asíncrono- Forzar un failover a un nodo asíncrono con replicación síncrona activa eludiría las garantías de durabilidad y podría causar pérdida de datos
-
Cuando el objetivo es un standby síncrono
patronictl failover: tiene éxito; el líder cambia al standby síncronopatronictl switchover: tiene éxito; realiza una transferencia ordenada entre el líder y el standby síncrono
- Después de validar el comportamiento de varios modos de
synchronous_commity los guardrails de Patroni, activaron replicación síncrona en clústeres de producción con workloads de alta escritura, alta lectura y mixtos, sin impacto medible en latencia ni throughput - Si surge algún problema, pueden volver de forma segura a replicación asíncrona sin downtime usando
synchronous_mode: false
Por qué no eligieron DRBD
- Durante la evaluación de alta disponibilidad también consideraron el sistema de replicación a nivel bloque DRBD (Distributed Replicated Block Device), que refleja entre servidores el volumen completo, incluyendo el directorio de datos de PostgreSQL y los archivos WAL, para crear una réplica standby casi en tiempo real
- DRBD puede ofrecer menor latencia que la replicación por streaming integrada de PostgreSQL, pero requería un cambio arquitectónico importante, incluyendo nueva infraestructura, monitoreo y playbooks operativos
- Considerando su configuración madura sobre Kubernetes y el control fino que ofrece la replicación síncrona de PostgreSQL, eligieron replicación a nivel base de datos por su mejor visibilidad, flexibilidad y confianza operativa
Monitoreo de replicación síncrona
- Después de activar la replicación síncrona, monitorearon de cerca el estado de replicación y la preparación para failover; en particular, dos señales ayudaron a mantener la estabilidad a gran escala
-
Evento de espera SyncRep
- Ocurre cuando el primario espera la confirmación del standby síncrono antes de completar el commit y devolver el estado; cierto nivel es normal, pero esperas largas o frecuentes sugieren problemas de rendimiento en la réplica o latencia de red entre nodos
- Por qué importa: una espera prolongada incrementa la latencia de escritura y reduce el throughput
- Qué rastrear: duración y frecuencia de los eventos de espera
SyncRepyWalSenderWaitForReply, recolectados en la métrica de Datadogpostgresql.activity.waitsfiltrada con la etiquetawait_event:SyncRep(internamente consulta la tablapg_stat_activity)
-
No detección de standby síncrono
- Si Patroni no detecta un standby síncrono sano durante un período prolongado, el clúster pierde su capacidad de failover seguro
- Por qué importa: sin standby síncrono, el failover queda expuesto a pérdida de datos
- Criterio de alerta: si
patroni_sync_standbypermanece vacío durante cierto tiempo, se dispara una alerta de salud de alta disponibilidad (HA); se recopila desde datos de OpenMetrics y no existe integración nativa de Datadog
- La replicación síncrona mejora la durabilidad, pero cuando las réplicas están anormales o inaccesibles puede degradar disponibilidad y rendimiento; monitorear tiempos de espera y disponibilidad de standby es clave para sostener disponibilidad y performance bajo carga
Failover seguro desde la etapa de diseño
- La falla de AZ simulada expuso una debilidad crítica en la arquitectura de PostgreSQL: las réplicas quedaban rezagadas frente al líder, obligando a elegir entre esperar a que se resolviera el problema de red o aceptar divergencia de datos, un trade-off inaceptable en producción
- Al adoptar replicación síncrona basada en Patroni y ajustar durabilidad y latencia, lograron que el failover fuera posible y seguro incluso en condiciones de red degradadas; con benchmarking y simulaciones repetidas de fallas confirmaron una recuperación predecible sin deteriorar el rendimiento a gran escala
- Al bloquear escrituras durante fallas de la replicación síncrona, exponen el fallo de forma explícita a los servicios aguas arriba; a diferencia de la replicación asíncrona, donde las escrituras pueden perderse en silencio, aquí se puede reaccionar con reintentos, colas y otras estrategias, haciendo que el modo de falla sea más visible y recuperable
- A futuro, están explorando modos de commit basados en quórum y una observabilidad más profunda del estado de replicación
Aún no hay comentarios.