Volver a desafíos
Desafío

Manejo de cargas grandes de archivos

La aplicación necesitaba trabajar con archivos potencialmente grandes sin generar un impacto innecesario sobre el servidor, permitiendo validaciones asíncronas y estrategias de carga escalonada.

JavaSpring BootAWS S3Redis

Enfoque 1: Carga directa con validación asíncrona

El cliente sube directamente al almacenamiento de objetos mediante una URL firmada generada por el backend. Una vez que el usuario confirma la carga, el backend encola trabajos de validación que se ejecutan de forma asíncrona. Las validaciones incluyen verificación de existencia, comprobaciones de integridad, escaneo antivirus, verificación de formato y otras reglas de negocio. El estado de validación se expone mediante una API que el frontend puede consultar (polling) o a la que puede suscribirse vía WebSocket, transitando por los estados: pendiente → validando → válido/inválido → completado. Este enfoque es adecuado cuando las validaciones pueden ejecutarse en segundo plano y el usuario puede continuar trabajando.

Flujo

  1. El backend emite una URL PUT firmada para el bucket destino
  2. El cliente sube el archivo directamente al almacenamiento
  3. El usuario confirma la carga mediante una llamada a la API
  4. El backend encola los trabajos de validación
  5. Los workers de validación procesan las verificaciones en paralelo
  6. El estado se actualiza en el store en tiempo real
  7. El frontend consulta/suscribe el completado
Enfoque 1: Carga directa + validación asíncrona Cliente URL PUT firmada Bucket permanente (destino final) 1. subir archivo 2. confirmar Backend 3. encolar validaciones Pipeline de validación (asíncrono) Existencia Integridad Antivirus Formato Reglas de negocio Límites y cuotas API de estado

Enfoque 2: Carga escalonada con validación síncrona

Para operaciones en las que el archivo es requerido para completar una transacción de negocio (por ejemplo, crear un ítem que requiere una imagen), se utiliza un enfoque escalonado con validación síncrona.

Fase 1 - Pre-carga: El cliente sube el archivo a un bucket temporal mediante una URL firmada. La ruta del objeto sigue el patrón /userid/operationid/, donde userid asegura que el usuario solo pueda acceder a sus propios archivos, y operationid distingue entre múltiples intentos de carga para la misma operación.

Fase 2 - Confirmar y validar: El cliente envía la petición de la operación de negocio (por ejemplo, POST /items con los datos del ítem) incluyendo el operationid en lugar del archivo. El backend recupera el archivo del bucket temporal usando la ruta, ejecuta todas las validaciones de forma síncrona (existencia, integridad, antivirus, formato, reglas de negocio) y solo si todas pasan, mueve el archivo al bucket permanente y completa la operación de negocio. Si alguna validación falla, la operación se rechaza y el archivo temporal queda disponible para reintentar o para su limpieza vía TTL.

Este enfoque garantiza que la operación de negocio nunca se complete con un archivo inválido, manteniendo las transferencias de archivos grandes fuera del servidor de aplicación.

Flujo

  1. El backend emite una URL PUT firmada para el bucket temporal en /userid/operationid/
  2. El cliente sube el archivo directamente a la ubicación temporal
  3. El cliente envía la petición de operación de negocio con operationid (sin archivo)
  4. El backend recupera el archivo del bucket temporal usando la ruta
  5. El pipeline de validación se ejecuta de forma síncrona
  6. Si todas las validaciones pasan: mueve el archivo al bucket permanente y completa la operación
  7. Si alguna validación falla: rechaza la operación, retorna el error, el archivo temporal expira vía TTL
  8. En caso de éxito: el archivo temporal se limpia y el archivo permanente se vincula a la entidad creada
Enfoque 2: Carga escalonada + validación síncrona Cliente URL PUT firmada Bucket temporal /userid/operationid/ (TTL corto · por usuario · por intento) 1. pre-cargar archivo 2. POST { operationid } Backend 3. GET /userid/opid/ Pipeline de validación (síncrono - bloquea) Existencia Integridad Antivirus Formato Reglas de negocio Límites y cuotas Mover a bucket permanente ✓ pasa → mueve ✗ falla → rechaza Operación completa

Contexto

La aplicación necesitaba manejar archivos potencialmente grandes sin crear un impacto innecesario sobre la infraestructura del servidor, soportando a la vez múltiples flujos de validación.

Problema

Las cargas directas de archivos a través del backend consumían recursos significativos del servidor y generaban cuellos de botella, especialmente con archivos grandes. Además, el sistema requería flujos de validación diferentes según el caso de uso: algunos archivos necesitaban validación asíncrona después de confirmada la subida, mientras otros requerían validación síncrona como parte de una operación de negocio (por ejemplo, crear una entidad que requiere una imagen).

Solución

Infraestructura común:

  • Generación de URLs firmadas para cargas directas al almacenamiento
  • Bucket temporal con limpieza basada en TTL
  • Control de propiedad basado en la ruta (/userid/operationid/)
  • Pipeline de validación (reutilizable en ambos enfoques)

Pipeline de validación (genérico y reutilizable):

  • Existencia: verificar que el objeto existe y es accesible
  • Integridad: verificación de checksum/hash
  • Seguridad: escaneo de malware/virus
  • Formato: validación de estructura y tipo MIME
  • Reglas de negocio: límites de tamaño, restricciones de tipo, cuotas

Resultado

El enfoque de estrategia dual permitió manejar archivos de cualquier tamaño de forma eficiente, descargando la transferencia al almacenamiento de objetos. El backend solo gestiona metadatos, coordinación y orquestación de validaciones. El enfoque síncrono escalonado garantiza la integridad de los datos en operaciones de negocio críticas, mientras que el enfoque asíncrono brinda mejor experiencia de usuario en flujos no bloqueantes. El modelo de propiedad basado en la ruta (/userid/operationid/) simplificó el control de acceso sin verificaciones de autorización adicionales.