Saltar al contenido
Volver al blog
DevOpsBuenas prácticas

PocketBase 0.40 cambia los backups y el manejo de archivos

La nueva versión reduce bloqueos durante los backups, pero obliga a revisar extensiones Go antes de actualizar.

Ismael Catala6 min de lectura

Los backups no deberían parar la aplicación

Cuando una aplicación pequeña empieza a tener uso real, los backups dejan de ser una tarea secundaria. No basta con programarlos: también importa qué ocurre mientras se generan y si interfieren con las operaciones normales. PocketBase 0.40 elimina el bloqueo transaccional de la base de datos durante la generación de backups. Es un cambio que miro con interés si tengo una instancia que recibe escrituras mientras automatizo copias.

No lo leería como una promesa de backup instantáneo ni como una garantía genérica de consistencia para cualquier escenario. La nota de la versión dice exactamente que deja de bloquear la base de datos mediante una transacción durante esa generación. No explica, por sí sola, cuánto tardará una copia ni sustituye una política de retención, restauración y verificación. Mi conclusión práctica es sencilla: el backup puede interferir menos, pero sigue siendo obligatorio probar que se puede restaurar.

Si gestionas PocketBase desde cron, systemd, Docker o una herramienta externa, esta mejora no exige cambiar el comando de backup. Lo que sí conviene es repetir una prueba real con una base de datos parecida a la de producción. Yo comprobaría la restauración, los ficheros asociados y el comportamiento de la aplicación durante una copia. Tener un archivo de backup no equivale a tener un plan de recuperación.

Escribir archivos desde un io.Reader

La otra parte importante de PocketBase 0.40 está en su capa de filesystem. El método NewWriter(fileKey, opts) devuelve un escritor para crear o reemplazar un archivo almacenado. Después se puede copiar el contenido desde cualquier io.Reader usando ReadFrom, algo útil cuando recibo datos de una petición HTTP, una descarga o un proceso de importación. Al terminar, hay que llamar a Close() y comprobar también ese posible error.

Este ejemplo usa el filesystem configurado en PocketBase, que puede apuntar al almacenamiento local o a S3 según la configuración de la aplicación. app.NewFilesystem() crea la instancia y también debe cerrarse cuando ya no se utiliza. El body del ejemplo implementa io.Reader, así que no necesito cargar todo el contenido en memoria antes de guardarlo. La clave del archivo es responsabilidad de mi código y debe encajar con la estructura de almacenamiento que haya decidido usar.

fsys, err := app.NewFilesystem()
if err != nil {
	return err
}
defer fsys.Close()
 
writer, err := fsys.NewWriter("imports/origen.json", nil)
if err != nil {
	return err
}
 
if _, err := writer.ReadFrom(body); err != nil {
	_ = writer.Close()
	return err
}
 
return writer.Close()

Esto no reemplaza las operaciones habituales sobre campos de tipo archivo en las colecciones. PocketBase sigue gestionando de forma transparente la persistencia, validación y borrado de archivos cuando guardo un registro con un campo file. Usaría NewWriter cuando necesito trabajar directamente con el storage y sé qué clave debo crear. Para adjuntar un archivo a un registro, normalmente prefiero seguir usando el flujo de modelos y app.Save(record).

Hooks para crear controles alrededor del storage

La instancia de filesystem incorpora ahora OnNewWriter() y OnDelete(). Son hooks de bajo nivel para reaccionar, respectivamente, a la inicialización de un escritor y a cada llamada a Delete(fileKey). Pueden servir para añadir auditoría, aplicar reglas internas o instrumentar operaciones de almacenamiento que pasan por esa instancia. No los trataría como eventos universales de toda la aplicación.

Hay dos detalles que cambian bastante cómo los usaría. OnNewWriter() se activa al crear un writer, también cuando se usan operaciones como Upload, pero actualmente no se dispara con Copy. OnDelete() no se ejecuta cuando un archivo se sobrescribe, porque esa sustitución no llama a Delete. Si mi auditoría necesita cubrir copias y reemplazos, estos hooks por sí solos no bastan.

También hay que tener presente que los hooks viven en la instancia de filesystem.System. La propia nota de lanzamiento aclara que no están expuestos en core.App para evitar una ruptura de compatibilidad. Eso significa que registrar un hook en una instancia temporal creada con app.NewFilesystem() no convierte el hook en un observador global de PocketBase. Antes de construir lógica crítica sobre ellos, probaría qué rutas de mi código pasan realmente por esa instancia.

La actualización relevante para extensiones Go

La parte menos cómoda de esta versión afecta a quienes compilan PocketBase como aplicación Go con extensiones propias. PocketBase 0.40 sube su versión mínima de Go a 1.27.0 y migra a encoding/json/v2. Aunque el cambio de JSON quede por debajo de la API que uso directamente, la compatibilidad no es completa según las notas de la versión. No actualizaría un binario personalizado directamente en producción.

El primer paso es alinear el entorno de compilación y las dependencias. En una aplicación integrada como módulo Go, la declaración puede quedar así:

module example.com/mi-pocketbase
 
go 1.27.0
 
require github.com/pocketbase/pocketbase v0.40.0

Después compilaría desde cero y ejecutaría pruebas sobre los puntos donde mi extensión serializa o deserializa datos. Me interesan especialmente los endpoints personalizados, payloads de webhooks, estructuras con etiquetas JSON y cualquier integración que dependa de formatos concretos. No hace falta anticipar una rotura concreta para ser prudente: basta con aceptar que el cambio de motor JSON merece pruebas. Si uso los ejecutables precompilados y no tengo extensiones Go, esta parte tiene menos impacto directo, aunque seguiría validando el despliegue.

La letra pequeña antes de actualizar

Esta versión no convierte automáticamente los backups en una estrategia completa de recuperación. No elimina la necesidad de conservar copias fuera del servidor, probar restauraciones y vigilar el espacio disponible. Tampoco hace que los hooks de filesystem cubran todas las formas posibles de modificar archivos. Son primitivas útiles, no una capa de auditoría completa.

Con NewWriter, el cierre importa tanto como la escritura. Un error en Close() puede indicar que el archivo no se terminó de guardar correctamente, así que ignorarlo es un mal patrón. Además, el método reemplaza el archivo si la clave ya existe, por lo que conviene generar o validar las claves antes de empezar a escribir. No usaría claves construidas directamente desde entradas de usuario sin normalizarlas y revisarlas.

Mi orden de actualización sería claro: probar primero una copia de la aplicación, reconstruir extensiones con Go 1.27.0, revisar flujos JSON y ejecutar un backup con restauración. Después validaría las rutas que escriben archivos, sobre todo si añado hooks para auditoría o controles internos. PocketBase 0.40 aporta mejoras concretas, pero es una versión para actualizar con pruebas, no por inercia. Ese trabajo previo cuesta menos que descubrir una incompatibilidad después del despliegue.


Fuente del lanzamiento Documentación oficial del filesystem