Copias de seguridad MySQL vs SQLite: ¿Cuál es más fácil de recuperar bajo presión?

¿Te fue útil?

Las copias de seguridad no fallan durante tu ejercicio trimestral de mesa. Fallan a las 02:17, cuando el teléfono de guardia vibra sobre la mesita y tu cerebro aún negocia con la realidad.

En ese momento no te importan las arquitecturas elegantes. Te importa una cosa: ¿qué tan rápido puedo restaurar, demostrar que está correcto y desbloquear a los usuarios sin empeorar el daño? Tanto MySQL como SQLite pueden recuperarse. La pregunta es cuál es más fácil de recuperar cuando estás cansado, con prisa y operando con información incompleta.

Lo que realmente significa “más fácil de recuperar”

La mayoría de las comparaciones entre copias de seguridad de MySQL y SQLite las escriben personas que no han restaurado ninguno de los dos mientras Slack se desangra. Bajo presión, “más fácil” no significa “menos funciones”. Es una lista de verificación de cosas que puedes hacer rápido, de manera repetible y segura.

Las preguntas de recuperación que importan a las 02:17

  • ¿Tengo una copia consistente? No “existe un archivo”. Consistente significa que representa un punto en el tiempo real sin páginas partidas o transacciones a medio escribir.
  • ¿Puedo restaurar sin adivinar? Idealmente: un comando y un procedimiento conocido. En la realidad: todavía necesitarás juicio, pero menos bifurcaciones en el camino ayudan.
  • ¿Puedo validar rápidamente? Si la verificación toma horas, la gente la omitirá. Entonces descubrirás que la copia está rota en el peor momento posible.
  • ¿Puedo hacer recuperación punto en el tiempo (PITR)? Si no puedes, “restauramos” también puede significar “perdimos las últimas seis horas”.
  • ¿Qué pasa si la capa de almacenamiento es el problema? La corrupción rara vez es educada. Felizmente se hará pasar por un bug de aplicación hasta que estés profundamente en la negación.

Aquí está la verdad operativa: SQLite es simple porque es mayormente un solo archivo, y MySQL es recuperable porque es un sistema con herramientas probadas en batalla. Bajo presión, ambos pueden ser fáciles y ambos pueden ser una trampa. La diferencia es el tipo de trampa.

Una cita que vale la pena mantener en tu runbook: La esperanza no es una estrategia. — General Gordon R. Sullivan

Ese es el estado mental de recuperación. No confíes en que la copia de archivos fue consistente. No confíes en que los binlogs existan. No confíes en que el WAL fue incluido. Demuéstralo.

Hechos interesantes y contexto histórico (las partes que importan)

Pequeños detalles históricos explican por qué cada sistema se comporta como lo hace durante respaldo y restauración.

  1. SQLite empezó en 2000 como una base de datos embebida para herramientas que necesitaban un motor SQL sin un proceso servidor. Su historia de respaldo es “copiar datos de forma segura”, no “coordinar un clúster”.
  2. SQLite es famoso por ser “sin servidor”: sin daemon, sin protocolo de red. Esa simplicidad es una superpotencia para recuperación—hasta que la concurrencia y la semántica del sistema de archivos se meten.
  3. El modo WAL de SQLite se hizo mainstream en los 2010s porque mejora la concurrencia. También cambia lo que significa “una copia de seguridad”: el archivo .db por sí solo puede no contener los últimos datos comprometidos.
  4. InnoDB de MySQL se convirtió en el motor por defecto en MySQL 5.5. Antes, MyISAM era común y carecía de transacciones a prueba de fallos. Si tu organización tiene suposiciones legacy, pueden ser más antiguas que tus compañeros.
  5. Los binlogs binarios de MySQL fueron diseñados para replicación pero se convirtieron en la columna vertebral de la recuperación punto en el tiempo. No son opcionales si te importa deshacer errores humanos.
  6. Percona popularizó respaldos físicos en caliente para InnoDB en los 2000s con XtraBackup, porque los dumps lógicos no escalan y no preservan el layout físico.
  7. “Copiar el datadir” solía ser un remedio popular en MySQL. Funcionaba a veces, y luego mordió duro cuando file-per-table, redo logs y metadata se volvieron más complejos.
  8. SQLite depende de las semánticas de bloqueo del sistema de archivos. Ponlo en un filesystem raro o en un montaje de red compartido con bloqueos peculiares y puedes convertir “base de datos simple de archivo” en un festival de rendimiento y corrupción.

La historia no es trivia. Es la razón por la que tu plan de restauración funciona—o no—cuando el pager suena.

Primitivas de respaldo: lo que realmente estás respaldando

SQLite: “la base de datos es un archivo” (más a veces dos archivos adicionales)

SQLite almacena datos en un único archivo de base de datos principal (comúnmente app.db). Pero dependiendo del modo de jurnalización, también puedes tener:

  • Rollback journal: app.db-journal (común en el modo DELETE journal).
  • Archivo WAL: app.db-wal más el archivo de memoria compartida app.db-shm (en modo WAL).

Bajo presión, la trampa es olvidar que “la base de datos” puede ser tres archivos y que deben capturarse de forma consistente. La buena noticia: si puedes usar el propio mecanismo de respaldo de SQLite o tomar la instantánea a nivel de sistema de archivos correctamente, la recuperación suele ser directa.

MySQL: un sistema en funcionamiento con múltiples capas de respaldo

MySQL tiene un proceso servidor, redo logs, undo logs, archivos de datos y opcionalmente binlogs. Los respaldos vienen en dos grandes sabores:

  • Respaldos lógicos (p. ej., mysqldump, mysqlpump): SQL portable, más lento a escala, puede ser parcial, y usualmente más fácil de inspeccionar.
  • Respaldos físicos (p. ej., Percona XtraBackup, instantáneas de sistema de archivos): más rápidos, preservan la estructura física, requieren más disciplina y cuidado de compatibilidad.

Bajo presión, la ventaja de MySQL es que fue construido para recuperación operativa a escala—si adoptaste la estrategia de respaldo correcta con antelación. Si no lo hiciste, puede ser brutalmente implacable.

Chiste #1: Las copias de seguridad son como los extintores—todo el mundo ama la idea, y nadie revisa el manómetro hasta que la cocina está en llamas.

Recuperación de SQLite bajo presión: lo bueno, lo malo, el archivo

Por qué SQLite puede ser ridículamente fácil

Si tienes una copia consistente, restaurar SQLite puede ser tan simple como devolver un archivo a su lugar. No hay servidor que arrancar, no hay permisos de usuario que rehidratar, no hay topología de replicación que desenredar.

Por eso SQLite es querido para apps móviles, dispositivos edge, herramientas internas pequeñas y productos “necesitamos algo fiable pero no un servicio de base de datos completo”. Para simplicidad en tiempo de restauración, es difícil superar: archivo en su lugar, la app arranca, listo.

Por qué SQLite puede ser sorprendentemente difícil

La parte difícil no es restaurar. Es saber que lo que copiaste es consistente.

Si copiaste el archivo de la base de datos mientras la aplicación escribía, puedes obtener una copia que parece correcta hasta que ejecutes una consulta que toque la página equivocada. O hasta el primer vacuum. O hasta que el CEO intente exportar un informe y descubras que restauraste una base de datos lógicamente inconsistente que pasa pruebas superficiales.

Modo WAL: el mejor amigo y peor enemigo de la recuperación

El modo WAL es genial para la concurrencia. También es un clásico pie de plomo para respaldos. En modo WAL, transacciones comprometidas recientes pueden residir en -wal hasta ser checkpointed en el archivo principal .db. Si solo haces copia de app.db, puedes restaurar a un estado más antiguo de lo que crees.

Bajo presión, el éxito de la recuperación en SQLite a menudo se reduce a una pregunta: ¿incluyó tu copia de seguridad una instantánea consistente de .db, -wal y -shm, o usó el método de respaldo online de SQLite?

Realidad de la corrupción

SQLite es robusto, pero no mágico. La corrupción suele venir del entorno: almacenamiento defectuoso, bloqueos rotos en sistemas de archivos de red, o aplicaciones que tratan a SQLite como una base de datos multi-escritor servidor y hacen su propia concurrencia “creativa”.

Cuando SQLite se corrompe, tu camino de recuperación a menudo se ve así:

  • Intentar restaurar desde una copia conocida buena.
  • Si no hay copia buena, intentar un volcado y recarga, salvamento o extracción tabla por tabla.
  • Aceptar que puede perderse algún dato y planear cómo reconciliarlo en la capa de aplicación.

Recuperación de MySQL bajo presión: opciones, superficies cortantes y por qué sigue siendo sensato

Ventaja de recuperación de MySQL: múltiples rutas

MySQL te da opciones: restauración lógica, restauración física, recuperación tras fallo, promoción de réplica, recuperación punto en el tiempo vía binlogs. Bajo presión, múltiples opciones son buenas si ya sabes cuál vas a usar. Si no, es fatiga de decisiones con sintaxis SQL.

Respaldos lógicos: fiables, pero el tiempo puede matarte

mysqldump es la cinta adhesiva de las operaciones MySQL. Puedes usarlo casi en cualquier lado, restaurar en versiones algo diferentes e inspeccionar lo que hay dentro.

La desventaja es la velocidad y el radio de impacto operacional:

  • Las bases de datos grandes pueden tardar mucho en volcar y aún más en restaurar.
  • Las restauraciones son ruidosas y pueden saturar I/O y replicación.
  • Bajo presión, te pueden tentar a omitir constraints, triggers o índices “temporalmente”. Lo temporal tiene la costumbre de volverse permanente.

Respaldos físicos: restauraciones rápidas, más prerrequisitos

Los respaldos físicos (como XtraBackup) pueden restaurar grandes datasets InnoDB mucho más rápido que la reproducción lógica de SQL. También preservan estructuras internas, lo que ayuda con rendimiento predecible tras la restauración.

El coste es que debes respetar compatibilidad de versión de MySQL, configuración y requisitos de la herramienta de respaldo. La restauración física puede ser una victoria limpia—hasta que descubres que nunca la practicaste y tu runbook tiene tres años de antigüedad.

PITR: la razón real por la que MySQL gana muchas discusiones de producción

Cuando alguien ejecuta una consulta destructiva, “restaurar la copia de anoche” no es un plan, es una confesión. MySQL con binlogs puede recuperar hasta un timestamp específico o límite de transacción. Eso es importante.

SQLite puede hacer cosas parecidas a PITR si envías segmentos WAL o haces snapshots frecuentes, pero no es un flujo operativo estandarizado de primera clase en las organizaciones del modo en que lo es el PITR basado en binlogs de MySQL.

La recuperación tras fallo está incorporada, pero no la confundas con respaldo

La recuperación de InnoDB puede reproducir redo logs y devolverte a un estado consistente tras un crash. Eso no es lo mismo que tener una copia de seguridad que puedas restaurar en otra máquina. Bajo presión, los equipos suelen confundir esto y luego descubren que “el servidor no arranca” no se soluciona con optimismo.

Chiste #2: “No necesitamos backups, tenemos RAID” es como decir que no necesitas cinturón de seguridad porque tu coche tiene cuatro ruedas.

Veredicto bajo presión: quién gana y cuándo

Si necesitas la restauración más simple posible

Gana SQLite cuando tu base de datos es pequeña o mediana, las escrituras están controladas y tienes un mecanismo de snapshot limpio y consistente (API de backup de SQLite o instantáneas de sistema de archivos bien hechas). La restauración puede ser tan simple como reemplazar un archivo y reiniciar una app.

Si necesitas recuperación punto en el tiempo y flexibilidad operativa

Gana MySQL cuando necesitas deshacer errores específicos, restaurar datasets grandes, manejar concurrencia y múltiples escritores, y recuperarte sin perder horas de datos. El ecosistema de respaldo de MySQL es más rico y está construido alrededor de la realidad operativa.

Dónde los equipos realmente se lastiman

Los equipos con SQLite se lastiman por suposiciones erróneas sobre copiar archivos, comportamiento de WAL y bloqueo del sistema de archivos.

Los equipos con MySQL se lastiman por no elegir una estrategia de respaldo (lógica vs física + binlogs) y luego improvisar durante un incidente.

Si quieres el sistema más recuperable bajo presión, la respuesta no es “elige MySQL” o “elige SQLite”. La respuesta es: elige la base de datos que coincida con tu madurez operativa, y practica la restauración como si realmente importara.

Tareas prácticas con comandos, salidas y decisiones (12+)

Estos son comandos reales y ejecutables. Cada uno incluye lo que significa la salida y qué decisión tomar a continuación. Bajo presión, quieres comandos que colapsen la incertidumbre.

Tareas de SQLite

Tarea 1: Identificar si la base de datos usa WAL

cr0x@server:~$ sqlite3 /var/lib/app/app.db "PRAGMA journal_mode;"
wal

Significado: La salida wal significa que los cambios activos pueden estar en app.db-wal.

Decisión: Los respaldos deben incluir app.db, app.db-wal y app.db-shm, o usar el método de respaldo online de SQLite.

Tarea 2: Comprobar si existen archivos WAL (y su tamaño)

cr0x@server:~$ ls -lh /var/lib/app/app.db*
-rw-r----- 1 app app 512M Dec 30 01:58 /var/lib/app/app.db
-rw-r----- 1 app app  96M Dec 30 02:15 /var/lib/app/app.db-wal
-rw-r----- 1 app app  32K Dec 30 02:15 /var/lib/app/app.db-shm

Significado: Un -wal grande sugiere muchas transacciones comprometidas no checkpointed en el archivo principal.

Decisión: Si restauras desde copias de archivos, restaura los tres archivos juntos desde el mismo conjunto de instantáneas.

Tarea 3: Tomar un respaldo online consistente con el comando de backup de SQLite

cr0x@server:~$ sqlite3 /var/lib/app/app.db ".backup '/backups/app.db.bak'"

Significado: SQLite crea un respaldo consistente incluso mientras la BD está en uso (coopera con sus propios bloqueos).

Decisión: Prefiere esto sobre cp cuando puedas; evita el problema de “copia partida”.

Tarea 4: Comprobación de integridad en un archivo SQLite restaurado

cr0x@server:~$ sqlite3 /restore/app.db "PRAGMA integrity_check;"
ok

Significado: ok es la mejor palabra de dos letras que leerás esta noche.

Decisión: Si no es ok, detente y restaura otro respaldo o pasa a modo salvamento.

Tarea 5: Listar rápidamente tablas para confirmar que el esquema está presente

cr0x@server:~$ sqlite3 /restore/app.db ".tables"
accounts audit_log invoices sessions users

Significado: Tienes las tablas esperadas; no estás frente a una base de datos vacía o incorrecta.

Decisión: Procede con pruebas de humo a nivel de aplicación. Si faltan tablas, restauraste el archivo equivocado o un respaldo parcial.

Tarea 6: Recuperar desde una base de datos SQLite dañada usando dump

cr0x@server:~$ sqlite3 /var/lib/app/app.db ".dump" > /tmp/app_dump.sql
Error: database disk image is malformed

Significado: El archivo está tan corrupto que un volcado completo falló.

Decisión: Intenta restaurar un respaldo anterior. Si no existe, intenta tácticas de salvamento (herramientas de recuperación por página o extracción de tablas intactas) y planifica pérdida parcial de datos.

Tareas de MySQL

Tarea 7: Confirmar que el logging binario está habilitado (comprobación de capacidad PITR)

cr0x@server:~$ mysql -uroot -p -e "SHOW VARIABLES LIKE 'log_bin';"
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | ON    |
+---------------+-------+

Significado: Los binlogs están habilitados, por lo que PITR es posible si conservaste los archivos.

Decisión: Si OFF, no puedes hacer PITR verdadero. Restaurarás al tiempo del respaldo y aceptarás más pérdida de datos.

Tarea 8: Encontrar dónde viven los binlogs y en qué formato están

cr0x@server:~$ mysql -uroot -p -e "SHOW VARIABLES WHERE Variable_name IN ('log_bin_basename','binlog_format');"
+------------------+----------------------+
| Variable_name    | Value                |
+------------------+----------------------+
| binlog_format    | ROW                  |
| log_bin_basename | /var/lib/mysql/binlog|
+------------------+----------------------+

Significado: Sabes dónde buscar y que el formato es ROW (bueno para corrección, menos legible).

Decisión: Asegura que los respaldos incluyan binlogs o se envíen a otro lugar; ROW implica que probablemente usarás mysqlbinlog para reproducir en lugar de editar a mano.

Tarea 9: Verificar que el último respaldo exitoso existe y no es trivial en tamaño

cr0x@server:~$ ls -lh /backups/mysql/full/
-rw-r----- 1 backup backup 8.2G Dec 30 01:00 full.sql.gz
-rw-r----- 1 backup backup 1.4M Dec 30 01:00 full.sql.gz.sha256

Significado: Un volcado de varios GB sugiere que no respaldaste un esquema vacío por error.

Decisión: Verifica la suma de comprobación antes de restaurar. Si el dump es sospechosamente pequeño, detente e investiga antes de reemplazar producción con una base de datos vacía y precisa.

Tarea 10: Validación de checksum antes de restaurar

cr0x@server:~$ sha256sum -c /backups/mysql/full/full.sql.gz.sha256
full.sql.gz: OK

Significado: Tu artefacto de respaldo sobrevivió al almacenamiento, transferencia y tiempo.

Decisión: Si esto falla, no lo restaures. Encuentra otro respaldo o crearás un segundo incidente.

Tarea 11: Restaurar un mysqldump en una instancia nueva (más seguro que en el sitio)

cr0x@server:~$ zcat /backups/mysql/full/full.sql.gz | mysql -uroot -p --host=127.0.0.1 --protocol=tcp

Significado: No ver salida suele significar éxito; los errores se imprimirán en stderr.

Decisión: Restaura en una instancia fresca o en un esquema separado cuando sea posible, luego realiza el corte. Las restauraciones in-place son cómo conviertes “mal día” en “oportunidad de desarrollo profesional”.

Tarea 12: Confirmar conteos de filas para tablas críticas tras la restauración

cr0x@server:~$ mysql -uroot -p -e "SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema='app' AND table_name IN ('users','orders');"
+------------+------------+
| table_name | table_rows |
+------------+------------+
| users      |     184233 |
| orders     |     921044 |
+------------+------------+

Significado: Los conteos son plausibles (no cero, no absurdamente pequeños).

Decisión: Si los conteos difieren, puede que hayas restaurado el respaldo equivocado, o que el dump se tomó a mitad del incidente y está incompleto.

Tarea 13: Identificar la posición del binlog en el momento del respaldo (para PITR)

cr0x@server:~$ zcat /backups/mysql/full/full.sql.gz | grep -m1 "CHANGE MASTER TO"
-- CHANGE MASTER TO MASTER_LOG_FILE='binlog.003982', MASTER_LOG_POS=19483211;

Significado: Este dump registró el archivo/posición del binlog consistente con la instantánea.

Decisión: Usa esa posición como punto de inicio para reproducir binlogs hasta el tiempo de recuperación deseado.

Tarea 14: Inspeccionar binlogs alrededor del tiempo de una consulta sospechosa

cr0x@server:~$ mysqlbinlog --start-datetime="2025-12-30 01:00:00" --stop-datetime="2025-12-30 02:00:00" /var/lib/mysql/binlog.003982 | head
# at 19483211
#251230  1:00:00 server id 1  end_log_pos 19483315 CRC32 0xa1b2c3d4  Anonymous_GTID  last_committed=0 sequence_number=1 rbr_only=no
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;

Significado: Estás leyendo el flujo de binlog para esa ventana.

Decisión: Decide si harás reproducción basada en tiempo (detener antes del error) o filtrar una sentencia específica (más difícil, arriesgado, a veces necesario).

Tarea 15: Reproducir binlogs hasta alcanzar un tiempo objetivo (PITR)

cr0x@server:~$ mysqlbinlog --start-position=19483211 --stop-datetime="2025-12-30 01:47:00" /var/lib/mysql/binlog.003982 | mysql -uroot -p

Significado: Se aplican las transacciones entre la posición del respaldo y el tiempo de parada.

Decisión: Si sospechas deriva del reloj, prefiere stop-position o recuperación basada en GTID. La recuperación por tiempo asume que tus timestamps y zonas horarias no mienten.

Tarea 16: Revisar señales de recuperación de crash de InnoDB en los logs

cr0x@server:~$ journalctl -u mysql -n 50 --no-pager
Dec 30 02:11:04 db1 mysqld[2190]: InnoDB: Starting crash recovery from checkpoint LSN=7123489123
Dec 30 02:11:05 db1 mysqld[2190]: InnoDB: 1 transaction(s) which must be rolled back
Dec 30 02:11:06 db1 mysqld[2190]: InnoDB: Crash recovery finished.

Significado: MySQL realizó la recuperación tras crash y arrancó limpiamente.

Decisión: Si la recuperación entra en bucle o falla, deja de reiniciar a lo loco. Considera restaurar desde respaldo; reinicios repetidos pueden empeorar problemas de disco.

Guía de diagnóstico rápido: encuentra el cuello de botella pronto

Esta es la secuencia “deja de thrash y consigue señal”. Estás intentando responder: ¿qué impide la recuperación—artefactos faltantes, restauración lenta, corrupción o destino de recuperación incorrecto?

Primero: prueba que tienes los artefactos correctos

  • SQLite: Confirma el modo de jurnalización y confirma que tienes un conjunto consistente de archivos (.db + -wal + -shm cuando aplique).
  • MySQL: Confirma que el respaldo existe, pasa checksum, y (si PITR) confirma que los binlogs existen para la ventana de tiempo requerida.

Segundo: prueba que el objetivo de restauración es sensato

  • Restaura en una nueva ruta/instancia primero cuando sea posible.
  • Valida el espacio libre del sistema de archivos. Las restauraciones fallan tarde y de forma ruda cuando se llena el disco.
  • Revisa compatibilidad de versión (especialmente para restauraciones físicas de MySQL).

Tercero: averigua si estás limitado por I/O, CPU o bloqueos

  • Síntomas ligado a I/O: bajo rendimiento de restauración, tiempos de espera altos, picos de latencia del almacenamiento. Solución: mover a disco más rápido, usar instantáneas/restore físico, evitar descomprimir en el mismo volumen lento.
  • Síntomas ligados a CPU: descompresión saturando núcleos, checksums lentos. Solución: descompresión paralela, mover cómputo más cerca de los datos, o usar staging predescomprimido.
  • Síntomas de bloqueo (SQLite): “database is locked”, largos stalls. Solución: detener escritores, usar la API de backup, o restaurar desde snapshot mientras la app está abajo.

Cuarto: decide claramente el objetivo de recuperación

  • ¿Necesitas último conocido bueno (restauración rápida) o punto en el tiempo (preciso)?
  • ¿Te recuperas de falla de hardware, error del operario o corrupción de datos? El camino difiere.

Errores comunes: síntoma → causa raíz → solución

Errores con SQLite

Síntoma: Después de restaurar, faltan escrituras recientes.
Causa raíz: Modo WAL habilitado; se copió solo .db sin -wal.
Solución: Respalda vía sqlite3 .backup o haz snapshot de todos los archivos relacionados atómicamente; verifica comparando marcadores de la última escritura esperada.
Síntoma: “database disk image is malformed” durante consultas o volcado.
Causa raíz: Archivo de base de datos corrupto (a menudo por copia insegura durante escrituras, corrupción del almacenamiento o bloqueo roto en montajes compartidos).
Solución: Restaura un respaldo conocido bueno; si no está disponible, intenta salvamento/volcado de tablas intactas y reconstrucción; mueve la BD a disco local con bloqueo correcto.
Síntoma: “database is locked” durante respaldo o arranque de la app.
Causa raíz: Transacciones escritoras de larga duración, o múltiples escritores en un entorno que no se coordina correctamente.
Solución: Silencia escritores; asegura que el modo WAL y los timeouts de busy sean sensatos; ejecuta respaldos mediante el mecanismo de backup de SQLite.

Errores con MySQL

Síntoma: La restauración finaliza, pero los datos tienen horas de antigüedad.
Causa raíz: No se retuvieron binlogs (o no se respaldaron/enviaron), por lo que no hay PITR.
Solución: Habilita log_bin, establece una política de retención y respalda o envía binlogs fuera del host. Prueba PITR trimestralmente.
Síntoma: La restauración física falla al arrancar MySQL con errores de InnoDB.
Causa raíz: Mismatch de versión/config, archivos redo/undo faltantes, o restaurar datadir sin pasos de preparación adecuados.
Solución: Estandariza versiones, registra configuraciones con los respaldos y ensaya procedimientos de restauración física en un entorno aislado.
Síntoma: La restauración con mysqldump es dolorosamente lenta y bloquea producción.
Causa raíz: Restaurar en una instancia ocupada; reproducción monohilo; creación intensa de índices durante la carga.
Solución: Restaura en una instancia nueva y luego haz el corte. Considera respaldos físicos para datasets grandes. Usa estrategias de carga sensatas (pero no “optimices” a ciegas).
Síntoma: La reproducción PITR causa errores de clave duplicada o fallos de constraints extraños.
Causa raíz: Se empezó la reproducción desde la posición de binlog incorrecta, se reprodujo en el estado de esquema equivocado o se mezclaron supuestos GTID/no-GTID.
Solución: Siempre registra coordenadas de binlog al momento del respaldo. Usa una línea base de restauración limpia y luego reproduce hacia adelante. Prefiere setups consistentes con GTID si operas a escala.

Tres mini-historias corporativas desde las trincheras

Incidente #1: Una suposición errónea (copiar archivo SQLite ≠ respaldo)

Un equipo de producto pequeño desplegó una herramienta interna de flujo de aprobaciones. Usó SQLite porque era “solo metadata”, el tráfico era bajo y mantuvo el despliegue extremadamente simple. El plan de respaldo era un cron nocturno: cp app.db app.db.$(date) a un directorio de backups y luego a object storage.

Funcionó durante meses. Nadie lo cuestionó. Ahí es cuando siempre empiezan los problemas.

Una mañana, la herramienta empezó a fallar aprobaciones con errores SQL vagos. El on-call restauró el respaldo de anoche. La aplicación volvió, pero faltaban aprobaciones—específicamente, el último día de cambios. Todos culparon a la restauración. Luego culparon a la app. Luego, inevitablemente, culparon al on-call.

El verdadero problema: la base de datos estaba en modo WAL, y el cron solo copió app.db. El “respaldo” era consistente en el sentido de que era un archivo SQLite válido. También faltaban transacciones comprometidas que residían en el WAL al momento de la copia. La copia nocturna respaldó fielmente una vista más antigua de la realidad.

La solución fue aburrida: cambiar los respaldos al comando de backup online de SQLite (o snapshot del conjunto completo de archivos WAL de forma atómica), añadir una comprobación de integridad tras el respaldo y una prueba de restauración. De repente restaurar significó “restaurar”, no “restaurar-ish”.

Incidente #2: Una optimización que salió mal (atajos de velocidad en restauración MySQL)

Una SaaS mediana corría MySQL con dumps lógicos nocturnos. Las restauraciones eran lentas, así que alguien propuso el clásico truco: deshabilitar checks de foreign key y unique durante la restauración y apretar configuraciones bulk. Funcionó en staging. Funcionó en sandbox. Incluso funcionó una vez en producción, que es como las malas ideas adquieren peso.

Entonces tuvieron un incidente real: un nodo primario falló y necesitaban reconstruir rápido. Restauraron el dump con constraints deshabilitados y trajeron el servicio arriba. Las métricas parecían normales. El incidente se declaró resuelto.

Dos días después, los tickets de soporte se dispararon. El sistema tenía filas huérfanas y asociaciones duplicadas en casos límite. No en todas partes. Lo justo para ser costoso y humillante. La “optimización” permitió que estados intermedios inconsistentes se filtraran porque el proceso de restauración no era idéntico a la operación normal, y las escrituras de la aplicación comenzaron antes de que todas las constraints se habilitaran y verificaran.

El postmortem fue doloroso pero útil: las restauraciones deben tratarse como cambios en producción con puertas de validación. Pasaron a restaurar en una instancia nueva, ejecutar comprobaciones de integridad y trabajos de reconciliación a nivel de aplicación, y solo entonces hacer el corte. También revisaron los respaldos físicos para reducir el RTO sin sacrificar corrección.

Incidente #3: La práctica aburrida pero correcta que salvó el día (PITR MySQL con binlogs)

Un equipo empresarial ejecutaba MySQL con respaldos completos semanales, incrementales diarios y envío continuo de binlogs. No era glamoroso. Requería disciplina: ajustes de retención, monitorización de gaps de binlog, ensayos periódicos de restauración y la habilidad social de insistir en que “sí, estamos probando restauraciones otra vez”.

Una tarde, un ingeniero ejecutó un script de limpieza de datos pensado para staging. Se conectó a producción. El script no era malicioso; simplemente estaba confiado. Borró filas en una tabla de alto valor, y la aplicación propagó la ausencia de datos a caches y procesos downstream.

Declararon incidente y congelaron escrituras. Entonces ejecutaron un runbook practicado: restaurar el último backup completo en una nueva instancia, aplicar incrementales y reproducir binlogs hasta un timestamp justo antes de que el script empezara. El servicio volvió con pérdida mínima de datos, y el equipo pudo reconciliar los pocos minutos de escrituras que se pausaron intencionalmente durante la recuperación.

Lo que los salvó no fue genialidad. Fue un sistema predecible: coordenadas de respaldo conocidas, continuidad de binlogs y el hábito de ensayar. Cuando llegó la adrenalina, hubo menos pensar y más hacer.

Listas de verificación / plan paso a paso

SQLite: plan de recuperación paso a paso

  1. Detén escritores (o coloca la app en modo mantenimiento). SQLite puede manejar concurrencia, pero la recuperación no es momento para negociar bloqueos con una carga viva.
  2. Identifica el modo de jurnalización y confirma qué archivos deben restaurarse.
  3. Restaura como conjunto: .db más archivos relacionados con WAL si aplica, desde la misma instantánea.
  4. Ejecuta comprobaciones de integridad y cheques básicos de esquema.
  5. Prueba de humo con consultas de aplicación que toquen rutas de datos reales, no solo “SELECT 1”.
  6. Captura el artefacto roto para análisis posterior. No lo sobrescribas. Los incidentes son caros; extrae aprendizaje de ellos.

MySQL: plan de recuperación paso a paso (lógico + PITR)

  1. Define el punto de recuperación: última hora buena o “justo antes de la consulta mala”. Escríbelo.
  2. Congela escrituras si el objetivo es corrección (especialmente para PITR). No puedes perseguir un objetivo que se mueve indefinidamente.
  3. Valida el artefacto de respaldo (checksum, tamaño razonable).
  4. Restaura en una nueva instancia cuando sea posible. Las restauraciones in-place son para emergencias dentro de emergencias.
  5. Aplica binlogs desde la posición registrada hasta el timestamp/posición objetivo.
  6. Valida los datos: conteos de filas, invariantes críticas y cheques a nivel de aplicación.
  7. Haz el corte (DNS, balanceador, cadenas de conexión), y luego monitoriza tasas de error y replicación si aplica.
  8. Higiene post-restauración: asegura que la retención de binlogs se reanude, los respaldos continúen y la instancia vieja se preserve el tiempo suficiente para comparación forense.

MySQL: plan de recuperación paso a paso (mentalidad de restauración física)

  1. Confirma compatibilidad de versión y configuración con el respaldo.
  2. Provisiona almacenamiento limpio con suficiente espacio para datos + overhead.
  3. Prepara el respaldo (aplica logs) según el procedimiento de tu herramienta.
  4. Restaura el datadir y establece la propiedad/permisos correctamente.
  5. Arranca MySQL y observa los logs hasta que esté completamente listo.
  6. Valida con cheques que coincidan con tus invariantes de negocio.

Preguntas frecuentes

1) ¿SQLite es “más fácil de recuperar” porque es un solo archivo?

A veces. Si tienes una instantánea consistente, restaurar es trivial. Si lo copiaste de forma insegura o olvidaste archivos WAL, la recuperación se vuelve arqueología.

2) ¿Puede SQLite hacer recuperación punto en el tiempo como MySQL?

No como flujo operativo estandarizado. Puedes aproximar PITR con snapshots frecuentes o envío de WAL, pero es personalizado y fácil de equivocarse bajo presión.

3) ¿Es cp app.db alguna vez un respaldo válido de SQLite?

Sólo si puedes garantizar que la base de datos no se está escribiendo durante la copia, o estás tomando una instantánea de sistema de archivos que es consistente ante crashes e incluye archivos relacionados con WAL. De lo contrario, usa el mecanismo de backup de SQLite.

4) Para MySQL, ¿es mysqldump suficiente?

Para datasets pequeños o medianos y portabilidad, sí. Para datasets grandes o RTO estrictos, querrás respaldos físicos más binlogs. Y querrás practicar restauraciones.

5) ¿Cuál es el mayor “gotcha” de recuperación de MySQL en la vida real?

Pensar que tienes PITR cuando no lo tienes. Binlogs deshabilitados, binlogs no retenidos o binlogs no enviados fuera del host convierten “deshacer el error” en “restaurar anoche y disculparse”.

6) Si MySQL tiene recuperación tras crash, ¿por qué necesito respaldos?

La recuperación tras crash ayuda a una instancia a recuperarse de un cierre no limpio. No te protege contra pérdida de disco, error humano, ransomware o “necesitamos restaurar a ayer”.

7) ¿Cuál es más propenso a corromperse?

Ambos pueden corromperse si el almacenamiento falla. SQLite es más sensible a bloqueo del filesystem y patrones de copia inseguros. MySQL también puede corromperse, pero su ecosistema y patrones operativos tienden a detectar problemas antes—si los monitoreas y validas.

8) ¿Cuál es la vía de restauración segura y más rápida para cada uno?

SQLite: restaurar una instantánea conocida buena de la BD (y el conjunto WAL si aplica), ejecutar PRAGMA integrity_check, reiniciar la app. MySQL: restaurar desde respaldo físico a una instancia fresca y luego aplicar binlogs hasta el punto objetivo.

9) ¿Debería mantener SQLite en almacenamiento de red?

Evítalo a menos que estés seguro de las semánticas de bloqueo del filesystem y su comportamiento bajo contención. SQLite es más feliz en discos locales. Los montajes de red son donde lo “simple” se complica.

10) ¿Con qué frecuencia deberíamos probar restauraciones?

Al menos trimestralmente para sistemas importantes, y después de cualquier cambio mayor de versión/configuración. Si nunca has restaurado con éxito, no tienes respaldos—tienes almacenamiento.

Conclusión: pasos prácticos siguientes

Si quieres que la recuperación sea fácil bajo presión, diseña para ello:

  • Para SQLite: deja de hacer copias casuales de archivos. Usa el mecanismo de backup de SQLite o snapshots consistentes, y siempre ten en cuenta el WAL. Añade comprobaciones de integridad tras el respaldo y tras la restauración.
  • Para MySQL: elige una estrategia: lógica para portabilidad, física para velocidad y binlogs para PITR. Luego practica restauraciones en un entorno limpio hasta que el runbook deje de ser ficción aspiracional.
  • Para ambos: valida respaldos automáticamente, conserva múltiples generaciones y realiza al menos un simulacro de restauración real por trimestre. El tiempo para descubrir que tu respaldo está roto no es cuando tus clientes ya lo saben.

La recuperación no es una característica que añades después. Es un hábito que construyes deliberadamente—o lo pagas con sudor a las 02:17.

← Anterior
Ubuntu 24.04: las VLAN no funcionan — la única bandera del bridge que la mayoría olvida
Siguiente →
EPYC: cómo AMD convirtió los servidores en una sala de exposición

Deja un comentario