Fotos, videos, documentos y archivos JSON pueden tratarse como secuencias de bytes que una aplicación carga y recupera. Object storage organiza esos contenidos como objetos identificables, con metadatos y operaciones sobre su representación.
La decisión de arquitectura consiste en separar qué datos necesitan consultas y transacciones frecuentes de qué contenidos se leen o escriben como archivos. Esa separación puede descargar a la aplicación y a la base de datos, pero introduce permisos, estados intermedios y recuperación entre sistemas distintos.
Bytes, metadatos y tokens
Una aplicación puede conservar en PostgreSQL el propietario, estado, nombre original y referencia de un archivo, mientras guarda sus bytes en object storage. Los filtros, relaciones y permisos usan ese modelo de datos consultable. El objeto también puede tener metadatos propios, como tipo de contenido o checksum; separar responsabilidades es una decisión, no una prohibición de guardar metadatos junto a los bytes.
| Representación | Qué conserva | Qué debe evaluarse |
|---|---|---|
| BLOB en PostgreSQL | Bytes dentro de la base, por ejemplo en una columna bytea o mediante Large Objects. | Acceso, transacciones, tamaños, backups y carga sobre la base. |
| Objeto | Bytes y metadatos asociados a una clave en un bucket. | Operaciones del servicio, permisos, versiones, durabilidad y coste de transferencia. |
| NFT ERC-721 | Identidad y titularidad de un token en un contrato. | Qué contenido referencia, cómo se resuelve y qué garantías ofrece esa referencia. |
En PostgreSQL, bytea y su almacenamiento mediante TOAST siguen manteniendo los bytes dentro de PostgreSQL. La interfaz de Large Objects usa otra API y almacenamiento en tablas específicas; no es intercambiable sin considerar sus accesos y ciclo de vida. Guardar una URL externa en una fila conserva una referencia, no el BLOB al que apunta. La elección depende del tamaño, frecuencia y unidad transaccional; object storage no es automáticamente mejor para cualquier valor binario.
Un BLOB y un NFT no son equivalentes. En ERC-721, la extensión de metadatos y tokenURI es opcional. Esa URI puede resolver a un documento JSON que referencia una imagen u otro recurso; eso no implica que el archivo esté dentro del token. Ser propietario del token no garantiza la disponibilidad del archivo ni derechos sobre su contenido. Identidad del token, referencia y almacenamiento de bytes son capas distintas.
Buckets, claves y versiones
Un modelo común usa un bucket como contenedor y una key como identificador del objeto dentro de él. Una clave como tenant-42/uploads/upload-123/photo.jpg permite organizar nombres por prefijos. La apariencia de carpetas no implica directorios POSIX, permisos heredados o un rename transaccional entre rutas. El contrato del servicio determina esas operaciones.
La base de datos debe guardar un localizador estable: bucket, key y, cuando corresponda, versión o generación. Una URL firmada es temporal y no sirve como identificador permanente. El nombre original del archivo puede conservarse como dato de presentación; no necesita decidir por sí solo la clave ni el tenant al que pertenece.
En muchas APIs de objetos, escribir significa cargar un objeto completo o reemplazar el asociado a una clave. Como ejemplo concreto, PutObject de S3 guarda el objeto enviado; no convierte una key en una fila que pueda editarse byte por byte. Leer un rango de bytes, cuando se admite, tampoco implica disponer de escrituras parciales equivalentes.
Object storage no significa inmutabilidad automática. Sobrescritura, versionado, condiciones de escritura y retención son capacidades diferentes. El versionado puede conservar revisiones, pero no equivale a impedir sus borrados mediante una política de retención o bloqueo. Cuando el contenido publicado debe permanecer fijo, la aplicación necesita una estrategia explícita de claves, versiones y permisos.
Durabilidad y disponibilidad
La durabilidad describe la capacidad de conservar datos frente a los fallos contemplados por el servicio. La disponibilidad describe si pueden leerse o escribirse en un momento concreto. Un objeto puede seguir conservado y estar temporalmente inaccesible.
La replicación mantiene copias en dominios de fallo definidos. Erasure coding distribuye fragmentos de datos y redundancia que permiten reconstruir el contenido mientras queden suficientes fragmentos. La distribución, tolerancia a fallos y comportamiento de recuperación dependen del diseño y de la clase de almacenamiento; no todos los objetos se replican en todas las regiones.
Esos mecanismos no sustituyen los permisos, backups o políticas de recuperación frente a borrados lógicos. Un borrado autorizado puede propagarse a las copias. También hay que conservar y recuperar los metadatos que permiten encontrar el objeto: bytes disponibles con una referencia perdida siguen siendo un problema para la aplicación.
Carga directa y URLs firmadas
La aplicación puede autorizar una carga y permitir que el cliente envíe los bytes directamente al almacenamiento. Así evita que cada archivo atraviese los servidores de la API. El control del negocio permanece en esos servidores.
--- config: look: handDrawn --- flowchart TD client["Cliente"] -->|"Solicitar carga"| api["API<br/>Autenticar y autorizar"] api -->|"Registrar intento pendiente"| metadata[("BD de metadatos<br/>Propietario, key y estado")] api -->|"URL firmada de carga"| upload["Cliente sube<br/>y completa la carga"] upload --> storage[("Object storage<br/>Bytes y versión")] upload -.->|"Solicitar finalización"| verify["Servidor verifica<br/>objeto, versión y condiciones"] verify -->|"Consultar objeto real"| storage verify -->|"Marcar listo tras verificar"| metadata metadata -->|"Estado listo"| read["API autoriza lectura"] read -->|"URL firmada de lectura"| download["Cliente descarga bytes"] download --> storage
La API autentica al usuario, comprueba que pueda adjuntar ese archivo y crea un upload_id con propietario, clave asignada y estado pendiente. Después del commit de esos metadatos emite una autorización limitada para la carga. El cliente no elige libremente una key ajena ni convierte su ownerId declarado en identidad confiable.
Una URL firmada funciona como una capacidad bearer: quien la posee puede realizar la operación permitida mientras sea válida. En el ejemplo de S3, se vincula a bucket, key, método, vigencia y permisos del firmante. Puede reutilizarse y expirar antes del plazo indicado si vencen las credenciales correspondientes. No debe interpretarse como un enlace de un solo uso ni como una comprobación de permisos por cada acceso en la aplicación.
La firma puede restringir condiciones o cabeceras según el mecanismo disponible. Límites de tamaño, formato y cuota necesitan controles realmente soportados y verificaciones del servidor; declarar Content-Type no demuestra que el archivo tenga ese formato. Las URLs deben tratarse como capacidades sensibles, con vigencias apropiadas y sin exposición innecesaria en logs.
Multipart y recuperación de cargas
Para archivos grandes, multipart divide la carga en partes que pueden enviarse en paralelo y reintentarse por separado. Eso puede aprovechar la conexión y evitar repetir todo el archivo, pero la concurrencia debe ajustarse a memoria, ancho de banda, límites y costes reales.
En el flujo multipart de S3, se inicia una carga, se envían sus partes y se completa con las referencias necesarias. El objeto final se materializa tras completar el proceso; subir algunas partes no equivale a publicar el archivo. Las partes incompletas pueden seguir ocupando almacenamiento hasta completar, abortar o aplicar la limpieza configurada.
El identificador de carga y las partes confirmadas permiten recuperar progreso según el contrato de la implementación. Un timeout requiere comprobar qué se aceptó antes de repetir o empezar otro intento. La finalización también puede tener resultado incierto: la aplicación debe poder consultar y reconciliar el objeto asociado al mismo intento.
Checksum y ETag no deben confundirse universalmente. Su significado depende del servicio y del modo de carga; un ETag de multipart no es necesariamente el hash de todo el archivo. La verificación debe usar el mecanismo de integridad documentado para esa operación.
Finalización y lectura coherentes
El cliente puede solicitar finalización, pero su afirmación de que la carga terminó no es evidencia suficiente. El servidor autoriza el upload_id y verifica el objeto real en la clave esperada, su versión, tamaño y condiciones requeridas. Solo entonces marca los metadatos como listos. Si hay validación adicional del contenido, el estado puede permanecer pendiente hasta que termine.
La verificación debe quedar vinculada a los bytes que se servirán. Si la autorización de carga permite sobrescribir la misma clave, una escritura posterior podría cambiar el contenido ya validado. Claves aisladas por intento, condiciones de escritura y una versión concreta para las lecturas permiten definir ese contrato; habilitar versionado por sí solo no impide sobrescrituras o borrados.
La base de datos y el almacenamiento de objetos no forman automáticamente una transacción atómica común. Puede quedar un objeto sin metadatos listos o un intento pendiente cuyo archivo nunca llegó. La finalización idempotente y los estados durables permiten recuperar esas situaciones, en línea con los límites transaccionales y recuperación entre componentes.
Al leer, la API comprueba permisos y el estado listo, recupera el localizador estable y emite una URL de lectura limitada. El cliente descarga los bytes directamente. Un cambio de permisos en la aplicación no revoca necesariamente una URL ya emitida de forma inmediata; su vigencia y las capacidades del servicio deben encajar con el contrato de acceso.
Operación y criterios de elección
La limpieza necesita distinguir cargas activas, partes abandonadas y objetos huérfanos, con plazos y referencias verificadas para no borrar contenido válido. También conviene reconciliar metadatos listos con objetos ausentes y registrar fallos de finalización para su recuperación.
Las lecturas repetidas pueden beneficiarse de una CDN o caché, con decisiones de frescura y autorización explícitas. Una key mutable puede necesitar invalidación; una versión fija facilita identificar qué contenido se está sirviendo, sin resolver por sí sola todos los permisos.
La evaluación incluye tamaño y crecimiento, latencia de cargas y lecturas, fallos y reintentos, transferencias, solicitudes y almacenamiento de versiones o partes incompletas. No existe una cifra universal de ahorro o throughput. Para bytes pequeños que participan en una unidad transaccional local, PostgreSQL puede seguir siendo adecuado; para archivos y cargas directas, object storage puede encajar mejor con el acceso previsto.
El recorrido de System Design conecta esta elección con el modelo de datos, los límites del sistema y la recuperación. La pregunta útil es qué conserva cada componente y cómo se verifica que el archivo y su estado siguen correspondiendo.