Cuando la base de datos está caída, a nadie le importa lo elegante que sea tu esquema. Les importa cuánto tarda en responder la página de pago, qué tan pronto se calma el canal de incidentes y si tu afirmación de “tenemos respaldos” sobrevive al contacto con la realidad.
Esta es una mirada práctica y de grado producción a los respaldos y restauraciones de MySQL y PostgreSQL a través de un único prisma: el tiempo para volver a estar en línea. No «el tiempo para completar una tarea de restauración eventualmente», sino el tiempo hasta un servicio que pueda recibir tráfico de forma segura otra vez—con la menor pérdida de datos que puedas prometer de forma creíble.
La pregunta real: ¿qué significa “volver en línea”?
Los respaldos y las restauraciones no son un concurso de belleza. El único sistema de puntuación que importa es el que usa tu negocio durante una interrupción:
- RTO (Recovery Time Objective): cuánto tiempo puedes estar caído.
- RPO (Recovery Point Objective): cuánta información puedes perder.
- Seguridad: si el sistema recuperado es consistente y no se corromperá bajo carga.
- Confianza: si la ruta de restauración está ensayada o es teórica.
“Volver en línea” también tiene niveles. Sé explícito.
- Nivel 0: el proceso de la base de datos arranca.
- Nivel 1: puede aceptar tráfico de solo lectura de forma segura.
- Nivel 2: puede aceptar tráfico completo de lectura/escritura, replicación configurada, monitorización en verde.
- Nivel 3: sincronizado hasta una marca de tiempo objetivo (PITR), la aplicación se ha reconectado y la cola se está vaciando.
Mi opinión firme: si tus runbooks no definen el nivel al que vas a restaurar, no tienes runbooks—tienes sensaciones.
Respuesta rápida: ¿quién es más rápido, y cuándo?
Para la mayoría de sistemas en producción, los respaldos físicos ganan
Si intentas minimizar el tiempo de inactividad, los respaldos físicos son la opción por defecto tanto para MySQL como para PostgreSQL:
- MySQL: Percona XtraBackup (para InnoDB) o equivalentes de proveedor; además binlogs para PITR.
- PostgreSQL: pgBackRest (u otro similar) más archivado de WAL para PITR; o respaldos de sistema de archivos/base con WAL.
Los dumps lógicos (mysqldump, pg_dump) tienen su lugar—migraciones, restauraciones selectivas, conjuntos de datos pequeños, portabilidad—pero rara vez son tu camino más rápido para volver después de un incidente de gran magnitud.
Dónde MySQL suele devolverte en línea más rápido
- Cuando puedes promover una réplica existente: MySQL + replicación semi-sync/async puede hacer que el failover parezca “instantáneo” si ya tienes una réplica sana y aceptas algún riesgo de RPO. (Lo mismo aplica a PostgreSQL, pero las herramientas y patrones operativos difieren.)
- Cuando tu restauración es básicamente “copiar archivos + recuperación por crash” y la puesta al día de binlogs es corta. En la práctica, muchas restauraciones MySQL están dominadas por la velocidad de copia I/O y luego un paso acotado de recuperación por crash.
- Cuando tu cadena de herramientas de respaldo está madura (XtraBackup en particular está probada en batalla y optimizada para grandes entornos InnoDB).
Dónde PostgreSQL suele devolverte en línea más rápido
- Cuando necesitas PITR preciso y fiable a una marca de tiempo o límite de transacción. La recuperación basada en WAL de PostgreSQL es directa, bien definida y el ecosistema (pgBackRest, WAL-G, etc.) es excelente.
- Cuando puedes restaurar y empezar a servir lecturas mientras continúa la reproducción (según arquitectura y tolerancias). El comportamiento de recuperación y la visibilidad en PostgreSQL pueden ser más predecibles con la configuración y monitorización adecuadas.
- Cuando dependes de checksums + disciplina de WAL y quieres salvaguardas más fuertes contra corrupción silenciosa. No acelera las restauraciones por arte de magia, pero puede evitar incidentes de “restauración exitosa, datos basura” que te hacen perder horas.
La verdad incómoda
En incidentes reales, el motor de base de datos rara vez es la principal demora. El reloj lo matan:
- copiar terabytes sobre un enlace saturado,
- descomprimir en el perfil de CPU equivocado,
- reproducir logs en I/O aleatorio lento,
- esperar a que DNS/pools de conexión/despliegues de la app noten el nuevo primario,
- o descubrir que tus “respaldos” faltaban una pieza crucial (WAL/binlogs, claves, configs, usuarios, permisos).
Elige la base de datos que prefieras. Pero para la velocidad de recuperación, elige la arquitectura de restauración: réplicas, respaldos físicos, retención de WAL/binlog y ensayos.
Datos interesantes y contexto histórico (breve y útil)
- InnoDB se convirtió en el predeterminado de MySQL en MySQL 5.5, y con ello llegó una larga era donde las herramientas de respaldo físico importaban más que los dumps lógicos para sistemas grandes.
- PostgreSQL introdujo la replicación por streaming en la 9.0, lo que llevó a muchas organizaciones a pasar de “restaurar desde respaldo” a “promover una réplica” como mentalidad principal.
- WAL (PostgreSQL) y binlogs (MySQL) resuelven problemas similares, pero las expectativas operativas difieren: el archivado de WAL suele ser tratado como requisito de primera clase para PITR; el manejo de binlogs a veces se considera “opcional hasta que no lo es”.
- Percona XtraBackup popularizó los respaldos físicos en caliente para MySQL a escala, especialmente donde no era aceptable detener la base para hacer respaldos.
- El modelo base backup + replay de WAL de PostgreSQL ha sido estable durante años, por eso las herramientas compiten mayormente en usabilidad, eficiencia de almacenamiento y verificación—menos en corrección.
- El soporte de checksums en PostgreSQL (checksums a nivel de clúster) empujó a más equipos a detectar corrupción antes; restaurar rápido está bien, restaurar datos correctos es mejor.
- GTIDs de MySQL cambiaron cómo los equipos razonan sobre la promoción y la recuperación basada en binlogs—menos “qué archivo/posición”, más “qué conjunto de transacciones”. Esto puede acelerar la toma de decisiones de failover cuando se usa correctamente.
- La compresión se volvió un impuesto al tiempo de restauración a medida que los respaldos se movieron a almacenamiento de objetos. La capa de almacenamiento más barata suele ser lo más lento entre tú y tu RTO.
Tipos de respaldo que importan para la velocidad de recuperación
Respaldos físicos (nivel de archivos): tu principal arma contra el tiempo de inactividad
Los respaldos físicos copian los archivos reales de la base de datos (o deltas a nivel de página) y se restauran poniéndolos de nuevo. La velocidad de restauración suele escalar con:
- ancho de banda de lectura/escritura secuencial (almacenamiento y red),
- CPU para descompresión y checksums,
- velocidad de reproducción de logs (WAL/binlog),
- y cuántos archivos creas (la sobrecarga de metadatos del sistema de archivos es un villano real).
En MySQL esto suele significar copias del datadir producidas por XtraBackup más manejo de redo/undo. En PostgreSQL implica un base backup más segmentos WAL para alcanzar un punto objetivo.
Respaldos lógicos (dumps SQL): la opción lenta pero portable
Las restauraciones lógicas son más lentas porque estás reconstruyendo datos mediante SQL. Eso significa:
- parseo de SQL,
- reconstrucción de índices y restricciones,
- escritura de muchas transacciones pequeñas salvo que se optimice cuidadosamente,
- y potencialmente generar mucho WAL/binlog durante la restauración.
Siguem siendo valiosos para:
- restauraciones parciales (un esquema, una tabla),
- migraciones entre versiones/engines,
- instantáneas legibles por humanos y auditables,
- y como protección contra “bug en la herramienta de respaldo físico”.
Recuperación basada en replicación: la restauración más rápida es no restaurar
Si puedes promover una réplica/standby, a menudo superarás cualquier restauración desde respaldo. Pero lo pagas con:
- cuidado operacional continuo (lag de replicación, cambios de esquema, mantenimiento),
- modos de fallo donde las réplicas comparten la misma corrupción o escrituras incorrectas,
- y la necesidad de recuperación ante errores lógicos (PITR) cuando los datos están mal pero son consistentes.
Broma #1: Los respaldos son como un seguro—caros, aburridos, y el día que los necesitas siempre es un terrible momento para aprender los detalles de la póliza.
Ruta de recuperación en MySQL: qué toma tiempo realmente
Qué significa “restaurar” en un incidente MySQL
Para MySQL (dominados por InnoDB, que son la mayoría de sistemas reales), la recuperación suele desglosarse así:
- Obtener una copia física consistente del datadir (desde almacenamiento de respaldo) en un servidor objetivo.
- Preparar/aplicar redo si tu herramienta de respaldo lo requiere (XtraBackup “prepare”).
- Arrancar mysqld y dejar que la recuperación por crash termine.
- Aplicar binlogs si haces PITR más allá del tiempo de la instantánea del respaldo.
- Reconstruir la topología de replicación (GTID o file/pos), verificar y cortar el tráfico.
Los mayores sumideros de tiempo (MySQL)
- Descompresión + escritura de archivos: si tu respaldo está comprimido y el host de restauración tiene menos núcleos (o peor CPU), obtendrás lentitud sorpresa.
- Muchos archivos pequeños: tablespaces por tabla (archivos .ibd) pueden crear tormentas de metadatos en la restauración. El ajuste de XFS/EXT4 ayuda, pero esto es física y syscalls.
- Aplicación de redo: cargas de escritura intensas generan más redo; el “prepare” puede tardar y la recuperación por crash al iniciar también.
- Puesta al día de binlog: si aplicas horas de binlogs, básicamente estás rehaciendo tu tráfico a mano. Tu cuello de botella puede ser la aplicación monohilada, la latencia de disco o simplemente el volumen.
Qué hace bien MySQL para la velocidad de recuperación
MySQL puede ser brutalmente rápido para volver a estar en marcha si restauras un respaldo preparado a almacenamiento rápido y aceptas que la recuperación por crash más el calentamiento del buffer pool ocurrirán en vivo. Con la topología correcta, también puedes evitar restauraciones por completo promoviendo una réplica.
Bordes afilados de MySQL que sientes durante las restauraciones
- Suposiciones de retención de binlogs: “Mantenemos binlogs por 24 horas” es un plan hasta que necesitas 30 horas.
- Configuraciones GTID incorrectas: un failover basado en GTID es rápido cuando está correcto y confuso cuando está a medias.
- Deriva de ajustes InnoDB: restaurar en un servidor con innodb_log_file_size, cifrado o tamaño de página diferentes puede convertir una restauración limpia en una larga sesión de depuración.
Ruta de recuperación en PostgreSQL: qué toma tiempo realmente
Qué significa “restaurar” en un incidente PostgreSQL
La recuperación física de PostgreSQL es conceptualmente limpia:
- Restaurar un base backup (archivos del clúster).
- Proveer segmentos WAL para adelantarlo (desde archivo o fuente streaming).
- Reproducir WAL hasta el objetivo (último, timestamp, LSN o punto de restauración nombrado).
- Promover y volver a enlazar clientes/réplicas.
Los mayores sumideros de tiempo (PostgreSQL)
- Restaurar el base backup: misma física—red, rendimiento de disco, descompresión.
- Reproducción de WAL: sistemas con muchas escrituras generan mucho WAL; la reproducción puede limitarse por I/O aleatorio y comportamiento de fsync.
- Comportamiento de checkpoints: la configuración y la carga influyen en cuán dolorosa es la recuperación por crash y la reproducción.
- Objetos grandes e índices inflados: la restauración puede estar bien, pero “volver en línea” puede requerir vacuum o reindexado posteriormente.
Qué hace bien PostgreSQL para la velocidad de recuperación
PITR es una ciudadana de primera clase. Puedes elegir “restaurar al momento antes del despliegue” con menos contorsiones, y el ecosistema de herramientas tiende a validar la continuidad de WAL de forma agresiva. Eso reduce tiempo perdido en “restauramos… ¿por qué faltan transacciones?”
Bordes afilados de PostgreSQL que sientes durante las restauraciones
- Huecos en el archivo WAL: un segmento faltante y no haces PITR; estás haciendo arqueología.
- Corrección de restore_command: un error sutil de quoting/ruta puede detener la recuperación indefinidamente.
- Confusión de timelines: fallos repetidos crean timelines; restaurar la línea temporal equivocada es un clásico “arranca, pero está mal”.
Una idea parafraseada de Werner Vogels (CTO de Amazon): Todo falla, todo el tiempo; los sistemas resilientes lo asumen y se recuperan rápido
(idea parafraseada).
Tareas prácticas (comandos, salidas, decisiones)
Estas son las tareas que realmente ejecuto durante restauraciones y simulacros. Cada una incluye: comando, qué significa la salida y qué decisión tomas.
Tarea 1: Medir el throughput de disco en el destino de restauración (Linux)
cr0x@server:~$ sudo fio --name=restore-write --filename=/var/lib/db-restore.test --size=8G --bs=1M --rw=write --iodepth=16 --direct=1
restore-write: (g=0): rw=write, bs=(R) 1024KiB-1024KiB, (W) 1024KiB-1024KiB, (T) 1024KiB-1024KiB, ioengine=psync, iodepth=16
...
write: IOPS=720, BW=720MiB/s (755MB/s)(8192MiB/11374msec)
Significado: Tu ancho de banda de escritura secuencial es ~720MiB/s. Ese es el techo para “restaurar base backup” si estás limitado por I/O.
Decisión: Si BW es <200MiB/s para restauraciones de varios TB, deja de pretender que tu RTO son minutos. Mueve la restauración a discos más rápidos, restaura desde snapshots locales o promueve una réplica.
Tarea 2: Comprobar espacio libre en el sistema de archivos y presión de inodos
cr0x@server:~$ df -h /var/lib/mysql
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 3.5T 1.2T 2.3T 35% /var/lib/mysql
cr0x@server:~$ df -i /var/lib/mysql
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 244M 18M 226M 8% /var/lib/mysql
Significado: Tienes espacio y muchos inodos. En MySQL con archivos por tabla, la agotación de inodos es un asesino real de restauración.
Decisión: Si el uso de inodos es alto (>80%), restaura en un sistema de archivos con más inodos o consolida tablespaces (a largo plazo). No “esperes que quepa”.
Tarea 3: Verificar metadatos de integridad del respaldo (pgBackRest)
cr0x@server:~$ pgbackrest --stanza=prod check
stanza: prod
status: ok
Significado: El repo y la stanza son accesibles y lo suficientemente consistentes para que pgBackRest confíe.
Decisión: Si esto falla durante un incidente, cambia inmediatamente a tu ubicación secundaria de respaldos o estrategia de snapshot. No depures la configuración del repo mientras el CEO actualiza dashboards.
Tarea 4: Listar respaldos disponibles de PostgreSQL y escoger el correcto
cr0x@server:~$ pgbackrest --stanza=prod info
stanza: prod
status: ok
cipher: none
db (current)
wal archive min/max (15): 0000001200000A1B000000C0/0000001200000A1C0000002F
full backup: 2025-12-28-010001F
timestamp start/stop: 2025-12-28 01:00:01 / 2025-12-28 01:12:33
database size: 2.1TB, backup size: 820GB
repo1: backup set size: 820GB, backup size: 820GB
Significado: Tienes un full backup reciente y cobertura de WAL entre min/max.
Decisión: Escoge el respaldo más nuevo que aún tenga WAL completo hasta tu tiempo objetivo. Si WAL max está detrás del timestamp del incidente, tu RPO empeoró—dilo pronto.
Tarea 5: Restaurar PostgreSQL a una marca de tiempo objetivo (PITR)
cr0x@server:~$ sudo pgbackrest --stanza=prod --delta --type=time --target="2025-12-28 14:32:00" restore
INFO: restore command begin 2.53: --delta --stanza=prod --target=2025-12-28 14:32:00 --type=time
INFO: restore size = 2.1TB, file total = 2143
INFO: restore command end: completed successfully
Significado: Los archivos están restaurados; la base de datos reproducirá WAL al arrancar hasta la hora objetivo.
Decisión: Si la restauración es “exitosa” pero el arranque se queda colgado, tu cuello de botella probablemente sea la recuperación de WAL o la I/O de reproducción. Pasa al playbook de diagnóstico en lugar de volver a ejecutar restauraciones.
Tarea 6: Supervisar el progreso de recuperación de PostgreSQL
cr0x@server:~$ sudo -u postgres psql -c "select pg_is_in_recovery(), pg_last_wal_replay_lsn(), pg_last_xact_replay_timestamp();"
pg_is_in_recovery | pg_last_wal_replay_lsn | pg_last_xact_replay_timestamp
-------------------+------------------------+------------------------------
t | 0/A1C3F9B0 | 2025-12-28 14:29:17+00
(1 row)
Significado: Aún en recuperación; puedes cuantificar cuánto te falta por timestamp/LSN en movimiento.
Decisión: Si el timestamp no avanza, sospecha de restore_command atascado, WAL faltante o saturación de I/O. Si avanza despacio, afina el entorno de reproducción WAL (discos más rápidos, menos cuellos) en vez de tocar SQL.
Tarea 7: Buscar segmentos WAL faltantes (logs de PostgreSQL)
cr0x@server:~$ sudo tail -n 20 /var/log/postgresql/postgresql-16-main.log
2025-12-28 14:35:02 UTC [21741] LOG: entering standby mode
2025-12-28 14:35:02 UTC [21741] LOG: restored log file "0000001200000A1B000000F1" from archive
2025-12-28 14:35:05 UTC [21741] LOG: could not restore file "0000001200000A1B000000F2" from archive: No such file or directory
2025-12-28 14:35:05 UTC [21741] LOG: waiting for WAL to become available at 0/A1BF0000
Significado: Hueco en el archivo. La recuperación está bloqueada.
Decisión: O localiza el WAL faltante (archivo secundario, política de ciclo de vida en object storage), o acepta un RPO peor y restaura a un punto anterior. No sigas esperando “por si acaso”.
Tarea 8: Validar disponibilidad de binlogs en MySQL para PITR
cr0x@server:~$ mysql -uroot -p -e "SHOW BINARY LOGS;"
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| binlog.000812 | 1073741824|
| binlog.000813 | 1073741824|
| binlog.000814 | 512345678|
+------------------+-----------+
Significado: Los binlogs existen en este servidor (o donde estés comprobando). La lista sugiere retención.
Decisión: Confirma que tienes los binlogs que cubren la ventana del incidente. Si binlog.000811 es necesario pero falta, no puedes hacer PITR a través de ese límite.
Tarea 9: Determinar el estado GTID de MySQL antes de reconfigurar replicación
cr0x@server:~$ mysql -uroot -p -e "SHOW VARIABLES LIKE 'gtid_mode'; SHOW MASTER STATUS\G"
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| gtid_mode | ON |
+---------------+-------+
*************************** 1. row ***************************
File: binlog.000814
Position: 83422119
Executed_Gtid_Set: 4a9b1c9c-8b60-11ee-9f4e-0242ac120002:1-987654321
Significado: GTID está activado; tienes un conjunto ejecutado específico que puedes comparar entre servidores.
Decisión: Usa el auto-posicionamiento basado en GTID si es posible. Si GTID está OFF en partes de tu flota, el failover se vuelve más lento y arriesgado—planea arreglarlo cuando no estés en llamas.
Tarea 10: Preparar una restauración XtraBackup de MySQL (aplicar redo)
cr0x@server:~$ sudo xtrabackup --prepare --target-dir=/backups/mysql/full-2025-12-28
xtrabackup: starting shutdown with innodb_fast_shutdown = 1
xtrabackup: completed OK!
Significado: El respaldo es consistente y está listo para copiar de vuelta. Si este paso es lento, tu aplicación de redo es pesada.
Decisión: Si prepare tarda una eternidad, o aceptas un RTO más largo o cambias la cadencia/estrategia de respaldos (incrementales, fulls más frecuentes o recuperación basada en réplicas).
Tarea 11: Copiar el datadir de MySQL y ajustar permisos correctamente
cr0x@server:~$ sudo systemctl stop mysql
cr0x@server:~$ sudo rsync -aHAX --info=progress2 /backups/mysql/full-2025-12-28/ /var/lib/mysql/
1.23T 42% 610.23MB/s 0:35:12 (xfr#214, to-chk=1290/2143)
cr0x@server:~$ sudo chown -R mysql:mysql /var/lib/mysql
Significado: La restauración está limitada por I/O; la velocidad de rsync te da una ETA. Se corrige la propiedad para que mysqld pueda leer archivos.
Decisión: Si la velocidad de rsync está muy por debajo del rendimiento esperado del disco, sospecha del camino de red, throttling o sobrecarga de archivos pequeños. Considera restaurar desde snapshot local o volumen adjunto en vez de sobre la red.
Tarea 12: Iniciar MySQL y confirmar comportamiento de recuperación por crash
cr0x@server:~$ sudo systemctl start mysql
cr0x@server:~$ sudo tail -n 20 /var/log/mysql/error.log
2025-12-28T14:40:01.120345Z 0 [Note] InnoDB: Starting crash recovery.
2025-12-28T14:40:12.551231Z 0 [Note] InnoDB: Crash recovery finished.
2025-12-28T14:40:13.883412Z 0 [Note] mysqld: ready for connections.
Significado: La recuperación por crash terminó rápido; el servicio está aceptando conexiones.
Decisión: Si la recuperación por crash tarda mucho o entra en bucle, tus logs/redo pueden estar desajustados (restauración incorrecta, innodb settings equivocados, copia parcial). Detente y valida los pasos de respaldo/restauración en vez de reiniciar repetidamente.
Tarea 13: Aplicar binlogs de MySQL para alcanzar un punto objetivo
cr0x@server:~$ mysqlbinlog --start-datetime="2025-12-28 14:00:00" --stop-datetime="2025-12-28 14:32:00" /var/lib/mysql/binlog.000812 /var/lib/mysql/binlog.000813 | mysql -uroot -p
cr0x@server:~$ echo $?
0
Significado: La aplicación de binlog fue exitosa (código de salida 0). Este es tu paso PITR.
Decisión: Si da error, decide rápido: arreglar y volver a ejecutar (si puedes), o aceptar un punto de restauración diferente. No apliques binlogs parciales y digas que “está bien” a menos que disfrutes las sesiones de terapia de integridad de datos.
Tarea 14: Verificar que PostgreSQL puede aceptar escrituras y está en la línea temporal esperada
cr0x@server:~$ sudo -u postgres psql -c "select pg_is_in_recovery(); select timeline_id from pg_control_checkpoint();"
pg_is_in_recovery
-------------------
f
(1 row)
timeline_id
------------
19
(1 row)
Significado: La recuperación terminó y puedes seguir cuál es la línea temporal en la que estás.
Decisión: Si los clientes siguen fallando, la base de datos probablemente esté bien y tu cuello de botella esté en otro lado: DNS, balanceador, pool de conexiones, secretos o reglas de firewall.
Tarea 15: Encontrar lag de replicación / demora de aplicación (standby PostgreSQL)
cr0x@server:~$ sudo -u postgres psql -c "select client_addr, state, sync_state, write_lag, flush_lag, replay_lag from pg_stat_replication;"
client_addr | state | sync_state | write_lag | flush_lag | replay_lag
-------------+---------+------------+-----------+-----------+-----------
10.0.2.41 | streaming | async | 00:00:00 | 00:00:01 | 00:00:03
(1 row)
Significado: El standby está casi al día; el replay lag es pequeño.
Decisión: Si el replay lag es de minutos/horas, promover este standby puede romper tu RPO. O elige otro standby, o restaura desde respaldo con WAL a un punto más seguro.
Tarea 16: Confirmar estado de replicación MySQL tras el failover
cr0x@server:~$ mysql -uroot -p -e "SHOW SLAVE STATUS\G" | egrep "Slave_IO_Running|Slave_SQL_Running|Seconds_Behind_Master|Last_Error"
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 0
Last_Error:
Significado: La réplica está sana y al día.
Decisión: Si el hilo SQL está parado con un error, no omitas transacciones a ciegas. Decide si importa más la corrección de datos o la recuperación rápida, documenta el compromiso y planifica la limpieza.
Playbook de diagnóstico rápido: encuentra el cuello de botella en minutos
Las restauraciones fallan de maneras aburridas. El objetivo es identificar cuál de esas aburridas maneras te está afectando hoy.
Primero: ¿estás bloqueado por logs faltantes (WAL/binlogs)?
- PostgreSQL: revisa logs por “could not restore file … waiting for WAL.” Si aparece, no estás lento—estás atascado.
- MySQL: confirma la cobertura de binlogs para el intervalo objetivo. Si no encuentras los binlogs necesarios, PITR es imposible a través de ese hueco.
Segundo: ¿el cuello de botella es throughput (red/disco) o reproducción (CPU/I/O aleatorio)?
- Si la copia del base backup es lenta, mide red y disco. Observa iostat, resultados de fio, progreso de rsync.
- Si la copia es rápida pero el arranque/recuperación es lento, es reproducción/checkpoint/recuperación por crash. Mide el progreso de reproducción WAL (PostgreSQL) o logs de recuperación y latencia I/O (MySQL).
Tercero: ¿la base de datos está “up” pero el servicio no?
- Pools de conexión apuntando a hosts antiguos.
- TTL de DNS y cachés obsoletos.
- Secretos no desplegados en el entorno de restauración.
- Firewall/security groups que bloquean replicación/acceso de la app.
Cuarto: ¿el rendimiento es la nueva caída?
- Cache fría: buffer pools y shared buffers vacíos.
- Autovacuum, checkpoints o tareas de purge poniéndose al día.
- Índices faltantes porque alguien restauró desde un dump lógico parcial.
Broma #2: La restauración más rápida es la que probaste la semana pasada—extrañamente, a la base de datos le gusta que le prestes atención.
Tres mini-historias corporativas desde la trinchera
Mini-historia 1: El incidente causado por una suposición equivocada
La empresa tenía un primario MySQL y dos réplicas. También tenían “respaldos de binlogs”, que significaba un cron que copiaba archivos de binlog a object storage. Todos dormían tranquilos.
Entonces un deploy salió mal. Un job por lotes corrió contra producción con un WHERE equivocado. Los datos eran consistentes, la replicación propagó el daño fielmente, y cuando alguien lo notó, el problema ya estaba en todos los nodos.
El equipo decidió PITR: restaurar el respaldo físico de la noche anterior y luego aplicar binlogs hasta “justo antes del deploy”. Fácil. Excepto que los binlogs se rotaban y purgaban agresivamente en el primario para ahorrar espacio, y el cron había estado copiando solo los binlogs “actuales”. Los antiguos se fueron. No “difíciles de encontrar”. Desaparecieron.
Restauraron el respaldo de la noche anterior y aceptaron un RPO que nunca habían escrito. Lo doloroso no fue la pérdida de datos; fue que la pérdida de datos era evitable. La suposición fue: “si respaldamos binlogs, podemos hacer PITR.” La realidad fue: “si retenemos una cadena continua de binlogs, podemos hacer PITR.”
La solución fue aburrida: retención explícita de binlogs basada en RPO, además de un trabajo de verificación que chequea continuidad y alerta sobre huecos. El siguiente incidente fue menos dramático porque fue menos sorpresivo.
Mini-historia 2: La optimización que salió mal
Un equipo PostgreSQL quería respaldos más rápidos. Aumentaron la compresión y movieron los respaldos a almacenamiento más barato. Las ventanas de respaldo se redujeron, la factura mensual bajó y alguien recibió aplausos en la reunión de planificación.
Meses después, una falla de almacenamiento eliminó el primario y el standby en la misma rack. Restauraron en un host nuevo y descubrieron la verdad: el tiempo de restauración no es el tiempo de respaldo. Restaurar significó tirar terabytes desde un almacenamiento frío, descomprimir archivos agresivamente comprimidos en una instancia con CPU menor y luego reproducir una montaña de WAL.
Recuperaron la base de datos. Solo que tardó lo suficiente como para hacer que el negocio reevalúe qué significa “alta disponibilidad”.
La decisión del postmortem fue simple: mantener una “vía de restauración rápida” para el respaldo full más reciente y el WAL más nuevo, aunque la retención a largo plazo quede barata y comprimida. También asignaron hosts de restauración con un perfil de cómputo que cubriera las necesidades de descompresión. La compresión es una herramienta, no una virtud por sí sola.
Mini-historia 3: La práctica aburrida pero correcta que salvó el día
Una plataforma de pagos ejecutaba PostgreSQL con pgBackRest. Cada semana, un ingeniero hacía un drill de restauración en un entorno aislado. No un simulacro completo con ejecutivos—solo un ejercicio tranquilo: restaurar el respaldo de la noche anterior, reproducir WAL hasta un timestamp elegido, ejecutar un conjunto de consultas de validación y luego borrar el entorno.
Se sentía casi ridículo. La restauración siempre funcionaba. Las consultas siempre coincidían con lo esperado. Los tickets eran repetitivos.
Luego una actualización introdujo un problema intermitente de permisos en el archivo WAL archive. El WAL se generaba, pero archive_command fallaba ocasionalmente. El drill lo detectó en días porque la instancia restaurada no podía alcanzar el tiempo objetivo. La solución llegó antes de que el primer incidente real dependiera de ese WAL.
Más tarde, ocurrió una caída real: un humano borró un attachment de volumen en la consola equivocada. El playbook de restauración ya estaba escrito, el perfil del host de restauración ya era conocido, y el equipo no tuvo que redescubrir su propio sistema a las 2 a.m. La recuperación fue rápida precisamente porque era aburrida.
Errores comunes: síntomas → causa raíz → solución
1) “La restauración tuvo éxito” pero PostgreSQL no alcanza el tiempo objetivo
Síntomas: pgBackRest completa la restauración; Postgres arranca; los logs muestran waiting for WAL; pg_last_xact_replay_timestamp deja de avanzar.
Causa raíz: Segmento(s) WAL faltante(s) por hueco en el archivo, eliminación por ciclo de vida o archive_command/restore_command mal configurados.
Solución: Encontrar el WAL en un archivo secundario; reparar restore_command; ajustar retención para cubrir el RPO; añadir comprobaciones continuas de huecos de WAL en la monitorización.
2) MySQL está arriba, pero la recuperación por crash tarda una eternidad
Síntomas: mysqld arranca pero queda indisponible; el error log muestra recuperación por crash extendida; la latencia de disco se dispara.
Causa raíz: Restaurado en almacenamiento lento; redo logs pesados; el respaldo no fue preparado correctamente; o desajuste en innodb settings que causa trabajo extra.
Solución: Validar el paso XtraBackup prepare; restaurar en discos más rápidos; asegurar que innodb_log_file_size y ajustes relevantes coincidan con el entorno del respaldo; evitar reinicios repetidos que re-disparen trabajo de recuperación.
3) La restauración lógica está “corriendo” pero la ETA son días
Síntomas: pg_restore/mysqldump import se arrastra; la CPU está ocupada parseando; WAL/binlog crece rápidamente; la replicación se queda atrás.
Causa raíz: Herramienta equivocada para el RTO; restauración monohilada; índices/restricciones reconstruidos en el peor orden; insuficiente ajuste.
Solución: Usa respaldos físicos para DR; si te ves forzado a usar lógico, paraleliza pg_restore donde sea posible, carga datos antes de índices/restricciones, desactiva logging innecesario donde sea seguro y restaura en una instancia aislada.
4) Existen réplicas, pero el failover aún tarda una eternidad
Síntomas: Hay un standby “ahí”, pero la promoción se demora; la app sigue reportando errores; la configuración de replicación es un desastre.
Causa raíz: No hay corte automatizado; TTL de DNS demasiado alto; los pools de conexión no se refrescan; certificados/secretos ligados a nombres de host; o la replicación estaba atrasada más allá del RPO.
Solución: Practica failover; reduce TTL con sentido; usa VIPs/proxies estables; asegura que las apps reconecten; monitoriza lag de replicación con alertas ligadas al RPO.
5) La restauración es rápida, pero el rendimiento es tan malo que equivale a estar caído
Síntomas: La base de datos responde, pero la latencia p95 es terrible; CPU e I/O se disparan; caches frías; autovacuum/purge en tormenta.
Causa raíz: Cache fría junto a mantenimiento en segundo plano y carga pesada de aplicación inmediatamente después de la restauración.
Solución: Etapa el tráfico: primero solo lecturas, luego incrementa escrituras; precalienta tablas/índices críticos; ajusta mantenimiento en segundo plano; considera retrasar jobs no críticos hasta estar estable.
Listas de verificación / plan paso a paso
Decide tu estrategia de recuperación por tipo de incidente
- Pérdida de hardware/nodo: prefiere failover a réplica/standby (más rápido), luego reconstruye desde respaldos.
- Corrupción lógica (mal deploy, borrados accidentales): prefiere PITR desde respaldo físico + logs.
- Sospecha de corrupción silenciosa: restaura en entorno aislado, valida y luego corta; no promociones un posible réplica corrupta.
Checklist previo al incidente (hazlo con calma)
- Define RTO/RPO por servicio (escríbelo, ponlo en el runbook).
- Implementa respaldos físicos (XtraBackup / pgBackRest) con verificación.
- Asegura retención continua de logs: binlogs/WAL cubren al menos RPO + margen.
- Mantén una “vía de restauración rápida”: respaldos recientes + logs en almacenamiento que pueda entregar throughput real.
- Provisiona hosts de restauración con CPU para descompresión y IOPS para reproducción.
- Automatiza o al menos script: restauración, consultas de validación y pasos de corte.
- Ejecuta simulacros de restauración con regularidad; registra tiempos reales y cuellos de botella.
Plan paso a paso durante el incidente (con límites de tiempo)
- Tiempo-boxed triage (5–10 minutos): decide si vas a hacer failover o restauración. No hagas ambas a la vez a menos que tengas suficiente gente y disciplina.
- Bloquea tu objetivo: “lo más reciente posible” vs “timestamp antes del incidente”. Comunica la implicación de RPO de inmediato.
- Valida continuidad de logs: cobertura WAL/binlog. Si falta, ajusta el objetivo y comunica.
- Inicia la restauración en paralelo con la preparación de la app: prepara DNS/LB/config mientras se mueven los bytes.
- Observa métricas de progreso: throughput de copia, timestamp/LSN de reproducción, latencia de disco.
- Pon en línea por niveles: solo lectura o tráfico limitado primero; luego carga completa cuando esté estable.
- Reconstruye redundancia: añade réplicas/standbys antes de declarar victoria; un único primario recuperado es una apuesta.
Preguntas frecuentes
1) ¿Cuál se restaura más rápido: MySQL o PostgreSQL?
Con respaldos físicos y buen almacenamiento, ambos son “lo suficientemente rápidos” para muchos sistemas. El ganador suele ser el que tenga logs continuos, un runbook probado y una ruta de restauración que evite almacenamiento lento. Si te fuerzas a usar restauraciones lógicas, ambos se sentirán lentos; MySQL suele sufrir con la reconstrucción de esquema/índices, PostgreSQL sufre por generación de WAL y tiempo de construcción de índices. No elijas engine basado en la velocidad de dumps.
2) ¿Promover una réplica siempre es más rápido que restaurar desde respaldo?
Casi siempre, sí. Pero puede violar el RPO si existe lag de replicación, y no ayuda ante corrupción lógica que se replicó. Además: si las réplicas comparten la misma corrupción subyacente (fallos de almacenamiento/firmware ocurren), puedes promover basura rápidamente.
3) ¿Cuál es el fallo más común en PITR?
Logs faltantes. Huecos en archivo WAL en PostgreSQL; brechas en retención/colección de binlog en MySQL. Las herramientas no pueden recuperar lo que no guardaste.
4) ¿Los respaldos lógicos son inútiles?
No. Son excelentes para portabilidad, restauraciones parciales, auditorías y migraciones. Simplemente rara vez son la herramienta correcta para “tenemos que volver en 15 minutos”. Manténlos como línea secundaria de defensa, no como el plan RTO primario.
5) ¿La compresión ayuda o perjudica la velocidad de restauración?
Ambas cosas. Reduce bytes transferidos y almacenados, pero aumenta CPU durante la restauración. El escenario que sale mal es común: almacenamiento barato + alta compresión + host de restauración pequeño = la restauración más lenta posible. Mide el tiempo de restauración, no el tiempo de respaldo.
6) ¿Cómo sé si estoy limitado por I/O o por CPU durante la restauración?
Observa el throughput de restauración junto con la utilización de CPU y la latencia del disco. Si la CPU está saturada y los discos están tranquilos, la descompresión/checksums dominan. Si la CPU está moderada pero la latencia del disco es alta y el throughput bajo, estás limitado por I/O. Entonces arregla lo correcto en vez de gritarle a la base de datos.
7) En PostgreSQL, ¿puedo servir tráfico mientras está en recuperación?
Puedes servir lecturas desde un standby en recuperación (hot standby) según la configuración y el caso de uso. Para una restauración PITR que busca convertirla en un nuevo primario, generalmente esperas a que la recuperación alcance el objetivo y luego promueves. Si necesitas lecturas rápidas, mantén una arquitectura con standbys; no la improvises durante el incidente.
8) En MySQL, ¿cuál es el patrón de restauración más rápido y seguro?
Promover una réplica sana para disponibilidad y luego reconstruir el primario fallido desde un respaldo físico en segundo plano. Si el incidente es corrupción lógica, restaura un respaldo físico en un host aislado y aplica binlogs con cuidado hasta un punto seguro, luego corta.
9) ¿Con qué frecuencia deberíamos hacer drills de restauración?
Suficiente para que tu RTO medido sea real. Semanal para sistemas críticos es común; mensual puede ser aceptable si tu entorno es estable. Tras cambios de versión, migraciones de almacenamiento, actualizaciones de herramientas de respaldo o políticas de retención, ejecuta un drill de inmediato.
10) ¿Qué debería validar después de una restauración además de “arranca”?
Ejecuta consultas críticas de la aplicación, valida conteos de filas o checksums para tablas clave, confirma corrección de usuarios/permisos, verifica que la replicación pueda reconstruirse y confirma que el sistema alcanza tu timestamp PITR previsto. “Servicio iniciado” es Nivel 0; no es la meta final.
Conclusión: siguientes pasos que puedes ejecutar
Si tu objetivo es volver en línea rápido, el debate MySQL vs PostgreSQL es en gran parte una distracción. Las diferencias duraderas están en la postura operativa: logs continuos, respaldos físicos, almacenamiento rápido para restauraciones y procedimientos ensayados.
Pasos prácticos:
- Elige tu nivel de recuperación (Nivel 1/2/3) para cada servicio y escríbelo junto con RTO/RPO.
- Estandariza en respaldos físicos (XtraBackup para MySQL InnoDB; pgBackRest para PostgreSQL) y automatiza la verificación.
- Demuestra continuidad de logs: implementa un trabajo que verifique cadenas WAL/binlog end-to-end, no solo “los archivos existen”.
- Cronometra tus restauraciones en hardware realista. Mide velocidad de copia y velocidad de reproducción por separado.
- Mantén una vía de restauración rápida: respaldos recientes y logs en almacenamiento que pueda cumplir tu RTO, además de hosts de restauración dimensionados para descompresión e I/O.
- Ensaya hasta que sea aburrido. Aburrido es rápido. Aburrido es fiable. Aburrido es por lo que pagan tus clientes.