Objetivos de aprendizaje
Después de leer este artículo podrás:
- Entender qué cambia al guardar imágenes como BLOB, bytea o GridFS
- Comparar base de datos y almacenamiento de objetos en tamaño, caché, backup y acceso
- Implementar el patrón de guardar el archivo en el storage y solo la clave en la base
En esta página
- ¿Cómo se guarda una imagen en una base de datos?
- ¿Qué ventajas tiene guardar imágenes en la base?
- ¿Qué problemas tiene guardar imágenes en la base?
- ¿Qué es el almacenamiento de objetos y qué resuelve?
- Base de datos o almacenamiento de objetos: ¿cuál elegir?
- ¿Cómo implementar el patrón "archivo en el storage, clave en la base"?
- Cómo hacerlo en Square Cloud
- Preguntas frecuentes
Casi toda app guarda imágenes: avatares, fotos de producto, comprobantes. Ponerlas todas en la base de datos parece el camino más simple, porque ya existe, ya tiene backup y ya tiene transacciones. Funciona, pero suele salir caro.
A continuación están los límites de cada base, el efecto en el rendimiento y en el backup, y el patrón que la mayoría de los proyectos termina adoptando. No hay un ganador universal, pero sí un punto en el que la cuenta cambia.
¿Cómo se guarda una imagen en una base de datos?#
En las bases relacionales, la imagen pasa a ser una columna binaria y el contenido del archivo viaja dentro del INSERT. Los tipos cambian según la base:
- MySQL:
BLOB(hasta 64 KB),MEDIUMBLOB(hasta 16 MB) yLONGBLOB(hasta 4 GB). Lo que realmente se puede transferir también depende demax_allowed_packet, que hay que subir en el servidor y en el cliente. - PostgreSQL:
bytea, con un límite de 1 GB por valor. Los valores grandes van a una tabla TOAST, fuera de la fila principal. Para ir más allá existen los large objects, con su propia API. - MongoDB: un documento BSON admite como máximo 16 MiB. Para archivos mayores, la documentación indica GridFS, que divide el archivo en fragmentos de 255 KiB por defecto y los escribe en
fs.chunks, con los metadatos enfs.files. Si todos los archivos pesan menos de 16 MiB, la propia documentación sugiere guardar cada uno en un documento con el tipo BinData.
En todos los casos el archivo es un dato más de la base. De ahí vienen tanto las ventajas como los problemas.
¿Qué ventajas tiene guardar imágenes en la base?#
- Consistencia transaccional. Borrar la fila borra la imagen. No hay imágenes huérfanas ni filas que apuntan a un archivo que desapareció.
- Un solo sistema. Sin segundo servicio, sin segundo juego de credenciales, sin configurar CORS ni permisos de bucket.
- Un solo backup. Un dump contiene datos e imágenes juntos, en la misma versión.
- Control de acceso reutilizado. Quien lee la fila lee la imagen, con las reglas que ya tienes.
Para un prototipo, una herramienta interna o un puñado de íconos pequeños, esto juega a favor de la base.
¿Qué problemas tiene guardar imágenes en la base?#
- La base se infla. Una base con 2 GB de datos pasa a ocupar decenas de GB cuando entran las fotos. Restaurar, migrar y replicar pasan a tardar horas, y cada dump carga todos los archivos otra vez.
- Cada imagen pasa por la conexión de la base. Para mostrar una foto, tu API lee los bytes de la base y los reenvía al navegador. Las conexiones y la memoria de la base, en general el recurso más limitado, quedan ocupadas sirviendo archivos estáticos.
- Sin CDN directa. Una imagen servida por la API solo llega a una caché de borde con trabajo extra. En un almacenamiento de objetos, la URL ya puede ser cacheada por una CDN.
- Memoria en disputa. Los binarios grandes compiten con los datos calientes que la base debería mantener en caché.
- Límites duros.
bytease detiene en 1 GB y BSON en 16 MiB, y los PDF y videos chocan con eso rápido. Si no eliges los campos, un ORM que carga la fila completa trae también la columna binaria. - Costo de escalar. Agrandar la base solo para que quepan archivos es pagar por capacidad de consulta que los archivos no usan.
¿Qué es el almacenamiento de objetos y qué resuelve?#
El almacenamiento de objetos guarda archivos como objetos en buckets, cada uno con una clave. Subes el archivo por HTTP y recibe una URL. El almacenamiento de objetos fue diseñado para este uso: muchos archivos, tamaños variados, lectura desde cualquier punto de internet.
Cubre lo que la base no cubre bien: URL públicas cacheables por CDN, URL firmadas para archivos privados, reglas de ciclo de vida como la expiración automática, subida directa desde el navegador sin pasar por tu API y subida multipart para archivos grandes. A cambio, no hay transacción entre el storage y la base, y mantener ambos consistentes pasa a ser trabajo tuyo.
Base de datos o almacenamiento de objetos: ¿cuál elegir?#
| Criterio | Imagen en la base | Imagen en almacenamiento de objetos |
|---|---|---|
| Tamaño del archivo | Limitado (16 MiB en BSON, 1 GB en bytea) | Mucho mayor; archivos de GB son normales |
| Tamaño de la base | Crece con cada archivo | Queda solo con metadatos |
| Servir al navegador | Pasa por la API y la base | Directo del storage o de la CDN |
| Caché de borde | Trabajo extra | Natural |
| Consistencia con la fila | Transaccional | Manual |
| Backup | Uno solo, pero pesado | Separado de la base |
| Acceso privado | Reglas de tu API | URL firmada o enlace para compartir |
| Complejidad inicial | Menor | Un servicio y credenciales más |
Elige la base cuando los archivos son pocos, pequeños (unos KB), poco leídos y deben cambiar junto con su fila. Elige almacenamiento de objetos cuando las imágenes vienen de subidas de usuarios, pasan de unos cientos de KB, aparecen en páginas públicas o el volumen tiende a crecer. Ante la duda, empieza por el storage: sacar GB de imágenes de una base después da mucho más trabajo que empezar fuera de ella.
¿Cómo implementar el patrón "archivo en el storage, clave en la base"?#
Sube el archivo al storage, guarda solo la clave y los metadatos en la base, y arma la URL al momento de leer. El ejemplo usa Python, boto3 y psycopg 3 (pip install boto3 "psycopg[binary]"), con las credenciales en variables de entorno:
import os
import uuid
import boto3
import psycopg
s3 = boto3.client(
"s3",
endpoint_url=os.environ.get("S3_ENDPOINT"), # omítelo en AWS S3
aws_access_key_id=os.environ["S3_ACCESS_KEY"],
aws_secret_access_key=os.environ["S3_SECRET_KEY"],
)
BUCKET = os.environ["S3_BUCKET"]
def save_avatar(conn: psycopg.Connection, user_id: int, data: bytes) -> str:
key = f"avatars/{user_id}/{uuid.uuid4()}.webp"
s3.put_object(Bucket=BUCKET, Key=key, Body=data, ContentType="image/webp")
try:
conn.execute(
"UPDATE users SET avatar_key = %s, avatar_size = %s WHERE id = %s",
(key, len(data), user_id),
)
conn.commit()
except Exception:
s3.delete_object(Bucket=BUCKET, Key=key) # no deja objetos huérfanos
raise
return key
def avatar_url(key: str) -> str:
# archivo privado: URL firmada por 10 minutos
return s3.generate_presigned_url(
"get_object",
Params={"Bucket": BUCKET, "Key": key},
ExpiresIn=600,
)Importan tres detalles. El primero es el orden: escribe el archivo antes que la fila y borra el objeto si la base falla. Así, el peor caso es un objeto sobrante, nunca una fila apuntando al vacío. El segundo es usar una clave nueva en cada subida (aquí, un UUID), porque cambiar el contenido bajo la misma clave deja cachés viejas sirviendo la imagen anterior. El tercero es guardar la clave, no la URL, y decidir al leer si la imagen es pública o privada (mira archivos públicos o privados).
Para limpiar huérfanos que se escaparon, ejecuta de vez en cuando un job que compare las claves del bucket con las de la base, o envía las subidas temporales a una carpeta con regla de expiración.
Cómo hacerlo en Square Cloud#
Blob Storage de Square Cloud es un almacenamiento de objetos con API compatible con S3, así que el código anterior funciona con boto3, aws-cli, rclone o los SDK de AWS, apuntando S3_ENDPOINT a https://s3-blob.squarecloud.app (región auto) con las claves de la pestaña "Acceso S3" del panel. También puedes usar la API REST o el panel. Cada archivo puede pesar hasta 10 GB, y la subida multipart solo es obligatoria por encima de 100 MB.
Los archivos públicos, como avatares y fotos de producto, salen por una CDN global en el edge, que los guarda en caché. Para los privados, la URL prefirmada SigV4 que genera tu código puede durar hasta 7 días, y el panel ofrece el enlace temporal (hasta 24 horas) y el enlace para compartir (hasta 30 días, revocable). No hay tarifa de salida ni de entrada, y todos los planes incluyen una cuota.
Para los datos, hay hospedaje de MongoDB, PostgreSQL, MySQL y Redis desde el plan Standard, en el mismo datacenter que las aplicaciones. Con las imágenes en Blob, la base guarda solo la clave y los metadatos.
Blob Storage
Object storage compatible con S3, sin tarifas de salida
Preguntas frecuentes
¿Puedo guardar imágenes en MySQL o PostgreSQL?
Sí. MySQL tiene BLOB, MEDIUMBLOB (hasta 16 MB) y LONGBLOB (hasta 4 GB), y PostgreSQL guarda binarios en bytea, con un límite de 1 GB por valor. Funciona, pero la base crece, los backups engordan y cada imagen pasa por la conexión de la base.
¿Qué es GridFS de MongoDB?
Es la especificación de MongoDB para guardar archivos mayores que el límite de 16 MiB por documento. Divide el archivo en fragmentos de 255 KiB por defecto y los escribe en dos colecciones, fs.files y fs.chunks.
¿Debo guardar la URL o la clave del objeto en la base?
Prefiere la clave, por ejemplo avatars/42/foto.webp. La URL depende del dominio, de la CDN y, en archivos privados, de una firma que caduca. Con la clave armas la URL correcta en cada petición.
¿Los íconos pequeños, de pocos KB, pueden quedarse en la base?
Sí. Para pocos archivos diminutos que cambian junto con su fila, guardarlos en la base simplifica el código y da consistencia transaccional. El costo aparece cuando crecen el volumen y el tamaño.
Más en Storage
Ver los 15 artículos- Qué es una URL prefirmada (presigned URL)Una URL prefirmada (presigned URL) es un enlace temporal a un archivo privado que funciona sin login. Mira cómo funciona, sus límites y cuándo usarla.
- Qué es el almacenamiento de objetosEl almacenamiento de objetos guarda archivos como objetos con clave y metadatos, accesibles por una API HTTP. Mira cómo funciona, para qué sirve y sus límites.
- Qué es S3 y qué significa 'compatible con S3'S3 es el almacenamiento de objetos de Amazon y su API se volvió el estándar del sector. Aprende qué significa ser compatible con S3 y cómo conectarte.
- Subir archivos grandes: carga multiparte y reanudableLa carga multiparte divide un archivo grande en partes que se envían en paralelo y se reintentan por separado. Mira el paso a paso en Python y cómo reanudar.