Saltar al contenido

Copias de seguridad de SQLite sin parar la web

SQLite es un solo archivo, así que copiarlo con cp parece suficiente. Lo probamos: la copia salió sin datos. Te contamos por qué pasa y cómo hacerlo bien.

AEWhite Devs 3 min de lectura
Portada del artículo: Copias de SQLite sin parar la web, junto a una terminal donde la copia con cp falla y la copia con .backup conserva las 1000 filas
En este artículo
  1. La prueba
  2. Por qué falla cp
  3. Las dos formas correctas
  4. Un script listo para usar
  5. Restaurar: el paso que casi nadie ensaya
  6. Tres consejos más

Varias de nuestras webs guardan sus datos en SQLite: un solo archivo, sin servidor aparte. Por eso la copia de seguridad parece trivial: cp app.db copia.db y listo. Lo probamos con la web en marcha y la copia salió sin datos. Esto es lo que pasa y cómo hacerlo bien.

La prueba

Creamos una base en modo WAL (el habitual en una web, porque deja leer mientras se escribe), guardamos 1000 filas y, con la conexión todavía abierta, como en una web funcionando, la copiamos de tres formas:

MétodoResultado de la copiacp app.db copia.dbSin datos: no such table.backup1000 filas, íntegraVACUUM INTO1000 filas, íntegra

Después repetimos .backup cinco veces con otro programa escribiendo sin parar. Durante la prueba hubo casi 37 000 escrituras y las cinco copias pasaron la comprobación de integridad.

Por qué falla cp

En modo WAL, los cambios recientes no van al archivo principal, sino a otro al lado, app.db-wal. SQLite los pasa al principal de vez en cuando. En nuestra prueba, el archivo app.db medía 4 KB y todos los datos estaban todavía en el -wal.

Lo peligroso es que cp no siempre falla así de claro. Si SQLite acaba de hacer ese traspaso, la copia parece correcta, pero te pueden faltar los últimos cambios o puede quedar dañada si se estaba escribiendo. Copiar también el -wal no lo arregla: los dos archivos se copian en momentos distintos.

Las dos formas correctas

  • .backup usa la función de copia de SQLite, pensada para hacerse con la base en uso:

    sqlite3 app.db ".backup 'copia.db'"
  • VACUUM INTO escribe una copia compactada, sin el espacio que dejan los datos borrados. El archivo de destino no puede existir antes:

    sqlite3 app.db "VACUUM INTO 'copia.db'"

Un script listo para usar

Hace la copia, comprueba que se pueda leer, la comprime y borra las de más de 14 días:

#!/bin/sh
set -eu
umask 077                     # las copias tienen los mismos datos que la base: solo para su dueño

BASE="$1"
DESTINO="$2"
COPIA="$DESTINO/app-$(date +%Y%m%d-%H%M%S).db"

sqlite3 -cmd ".timeout 5000" "$BASE" ".backup '$COPIA'"

# Una copia solo vale si se puede leer
[ "$(sqlite3 "$COPIA" 'PRAGMA integrity_check;')" = "ok" ] || { echo "Copia dañada: $COPIA" >&2; exit 1; }

gzip "$COPIA"
find "$DESTINO" -name 'app-*.db.gz' -mtime +14 -delete

Para que se ejecute cada noche, añade una línea al cron:

0 3 * * * /usr/local/bin/backup-sqlite.sh /var/www/app/shared/data/app.db /var/backups/app

Restaurar: el paso que casi nadie ensaya

  1. Para la aplicación.

  2. Borra los archivos app.db-wal y app.db-shm que queden. Son de la base anterior y no deben mezclarse con la copia.

  3. Descomprime la copia y ponla en su sitio, con el dueño y los permisos que usa la aplicación.

  4. Comprueba la integridad y arranca la aplicación:

gunzip -c app-20261103-030000.db.gz > app.db
sqlite3 app.db "PRAGMA integrity_check;"

Tres consejos más

  • Saca las copias de ese servidor. Una copia en el mismo disco no sirve si falla el disco. Si quieres automatizarlo, te contamos cómo usamos Duplicati.

  • Ensaya la restauración de vez en cuando. No sabes si tienes una copia hasta que la has restaurado.

  • Elige la frecuencia según lo que puedas perder. Con una copia por noche, puedes perder hasta un día de datos. Si eso es demasiado, mira Litestream, que replica los cambios a medida que ocurren.

¿Tienes dudas con las copias de tu proyecto? Escríbenos.

Compartir X LinkedIn Facebook WhatsApp

Sigue leyendo