Almacenamiento de objetos por plan: mover archivos entre R2 y B2 sin parar las pantallas
Las imágenes y los vídeos de ChronoFlow no viven en el servidor de la API sino en almacenamiento de objetos compatible con S3. Lo particular es que no todos viven en el mismo sitio: las cuentas del plan gratuito usan Cloudflare R2 y las de Pro, Backblaze B2. Este artículo cuenta cómo decide la API dónde va cada archivo, cómo se mueven los archivos cuando alguien cambia de plan y por qué las pantallas no se enteran. Por qué elegimos dos proveedores, y lo que cuesta cada uno, lo contaremos aparte.
Un disco por plan, y cada archivo recuerda el suyo
En Laravel, cada proveedor es un «disco» configurado. Una única función traduce el plan de la cuenta a su disco: Free va a R2; Pro y el plan piloto, a B2. No hay ningún otro sitio del código que lo decida.
La otra mitad de la idea es que cada fila de la tabla de archivos guarda el disco en el que está ese archivo. Las lecturas siguen a la fila, no al plan actual. Parece un detalle, pero es lo que permite cambiar de plan sin romper nada: durante una mudanza, un archivo que todavía está en R2 se sigue leyendo de R2 aunque la cuenta ya sea Pro.
Si falta la configuración de un disco, la API falla con un error claro. No hay un disco de reserva silencioso: preferimos un error visible en el despliegue a descubrir meses después que los archivos de una cuenta de pago están en el proveedor equivocado.
Subir directo al bucket
Los archivos no pasan por la API. El cliente pide una subida, la API comprueba que cabe en la cuota y devuelve una URL firmada de corta duración, y el cliente sube el archivo directamente al bucket del plan. Al terminar, avisa a la API, que comprueba que el objeto existe y que su tamaño coincide con lo anunciado antes de registrarlo. A partir de ahí, un trabajo en segundo plano genera la miniatura y lee la duración de los vídeos.
Para reproducir, la API entrega URLs firmadas de lectura que caducan en una hora. Las miniaturas no están en el bucket sino en la base de datos, y se sirven por su propia ruta firmada: son pequeñas, se piden constantemente en las listas y así no dependen de ningún proveedor.
Cuotas que se comprueban dos veces
El plan gratuito tiene 250 MB en total, con un máximo de 10 MB por imagen y 80 MB por vídeo. Pro tiene 15 GB, con 20 MB por imagen y 250 MB por vídeo. La cuota se comprueba al pedir la subida y otra vez al completarla, porque entre un momento y otro pueden haber terminado otras subidas.
El uso de cada cuenta se lleva en un contador que se incrementa de forma atómica en la base de datos, sin recalcular sumas en cada petición. Un proceso mensual lo reconcilia con los archivos reales por si algo se desvió. La cuenta recibe avisos al llegar al 80, al 90 y al 100 % de su espacio.
Subir de plan: la mudanza
Cuando una cuenta pasa a Pro, lo primero que cambia es el plan. Desde ese momento, las subidas nuevas van a B2 y los límites son los de Pro; nadie espera a que termine nada. Después se encola un trabajo que mueve los archivos existentes, uno a uno:
- Si el archivo ya existe en B2, por ejemplo porque un intento anterior se cortó a medias, se salta la copia.
- Si no, se copia como flujo, sin cargarlo entero en memoria.
- Se comprueba que el objeto existe en B2 y que su tamaño coincide.
- Se actualiza la fila para que apunte a B2.
- Solo entonces se borra la copia de R2.
Cada mudanza tiene un identificador y su trabajo es único, así que encolarla dos veces no la ejecuta dos veces en paralelo. Se reintenta hasta tres veces con esperas crecientes. Como cada paso se puede repetir sin efectos secundarios, un reintento retoma donde se quedó.
Por qué las pantallas no se enteran
Mientras la fila apunta a R2, las pantallas reciben URLs de R2; en cuanto apunta a B2, reciben URLs de B2. Como el origen se borra después de actualizar la fila, no hay ningún instante en el que la fila apunte a un sitio donde el archivo no esté. Una pantalla que está reproduciendo durante la mudanza no nota nada.
Bajar de plan: no se mueve nada
Al volver a Free, los archivos se quedan en B2 y se siguen reproduciendo. No hay mudanza de vuelta. Lo que cambia son los límites: mientras la cuenta ocupe más de 250 MB, no puede subir archivos nuevos. Las pantallas y horarios que exceden lo que permite el plan gratuito se pausan, no se borran, así que volver a Pro lo deja todo como estaba.
Tomamos esa decisión a sabiendas: guardar en B2 los archivos de una cuenta gratuita tiene un coste, pero borrar o mover contenido de alguien porque cambió de plan es mucho peor.
Lo que descartamos
- Leer siempre del disco del plan actual. Cualquier archivo que aún no se haya mudado dejaría de verse en cuanto cambia el plan.
- Mudar antes de activar el plan. Quien acaba de pagar tendría que esperar a que terminara una copia para subir nada.
- Borrar primero y copiar después. Un corte a mitad de camino perdería archivos. Copiar, verificar y borrar al final no pierde ninguno.
- Un disco de reserva. Oculta errores de configuración hasta que es tarde.
Lo que haríamos distinto
La mudanza hoy se ve en los registros del servidor, no en el producto. Si la hiciéramos de nuevo, mostraríamos su progreso en el dashboard desde el principio: cuántos archivos quedan y si hubo alguno que no se pudo mover. Es información que tranquiliza a quien acaba de pagar y que ahora solo tenemos nosotros.
Para seguir leyendo
Cómo se organiza el contenido que vive en esos buckets: playlists, horarios y grupos. Por qué los buckets también necesitan CORS: CORS en señalización. El producto: ChronoFlow. Si estás diseñando el almacenamiento de tu producto: Servicios.