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.
En este artículo
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
.backupusa la función de copia de SQLite, pensada para hacerse con la base en uso:sqlite3 app.db ".backup 'copia.db'"VACUUM INTOescribe 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 -deletePara 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/appRestaurar: el paso que casi nadie ensaya
Para la aplicación.
Borra los archivos
app.db-walyapp.db-shmque queden. Son de la base anterior y no deben mezclarse con la copia.Descomprime la copia y ponla en su sitio, con el dueño y los permisos que usa la aplicación.
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.