¿Cómo diseñas una base de datos multi-tenant: esquema compartido, esquema por tenant o base de datos por tenant?
La decisión de multi-tenancy es en realidad una decisión sobre dónde quieres que viva el aislamiento — en una columna, en un esquema o en una base de datos completa — y cada respuesta cambia costo por radio de impacto.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Cómo diseñas una base de datos multi-tenant: esquema compartido, esquema por tenant o base de datos por tenant?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Tres puntos en un espectro
Los backends SaaS multi-tenant colocan los datos de cada tenant en uno de tres lugares, y la elección es en realidad sobre cuánto aislamiento compras y a qué costo operativo:
- Esquema compartido (pooled) — una sola base de datos, un solo conjunto de tablas, cada fila etiquetada con un
tenant_id. Todos los tenants comparten el mismo esquema, el mismo pool de conexiones, los mismos índices. - Esquema por tenant (bridge / siloed-lite) — una sola instancia de base de datos, pero cada tenant tiene su propio esquema/namespace con un conjunto idéntico de tablas (
tenant_123.orders,tenant_456.orders). - Base de datos por tenant (siloed) — cada tenant tiene una base de datos completamente separada (potencialmente en instancias separadas), sin ningún motor de almacenamiento compartido.
| Dimensión | Esquema compartido | Esquema por tenant | Base de datos por tenant |
|---|---|---|---|
| Aislamiento | Solo lógico (a nivel de fila) | A nivel de namespace | Completo (almacenamiento, credenciales y a menudo host separados) |
| Riesgo de "vecino ruidoso" | El más alto — una consulta pesada de un tenant golpea el pool de conexiones y el buffer cache de todos | Medio — recursos de instancia compartidos, pero más fácil de ver por esquema | El más bajo — puede incluso vivir en hardware separado |
| Costo a escala (miles de tenants pequeños) | El más bajo — un solo conjunto de infraestructura | Medio — proliferación de esquemas, pero misma instancia | El más alto — el overhead de instancia por tenant domina para tenants pequeños |
| Onboarding de un nuevo tenant | Insertar una fila / sembrar un tenant_id — instantáneo | Correr migraciones para crear un nuevo esquema — segundos | Aprovisionar una nueva base de datos/instancia — minutos, un paso operativo real |
| Migraciones de esquema | Se corren una vez, aplican a todos | Hay que correrlas una vez por esquema — N veces | Hay que correrlas una vez por base de datos — N veces, lo más difícil de mantener sincronizado |
| "Exportar/borrar todos los datos del tenant X" (cumplimiento) | Requiere filtrado cuidadoso en todas partes, fácil olvidar una tabla | DROP SCHEMA tenant_123 CASCADE — limpio | DROP DATABASE — lo más limpio |
| Residencia de datos (el tenant necesita almacenamiento solo en UE) | Difícil — requeriría dividir toda la tabla por región | Posible si los esquemas pueden vivir en instancias específicas de región | Encaje nativo — coloca toda la base de datos en la región requerida |
| Personalización por tenant (columnas extra, índices distintos) | Incómodo — normalmente necesita una tabla EAV o una columna JSON añadida a la fuerza | Directo — el esquema puede divergir | Totalmente libre — cualquier cosa es posible |
#Esquema compartido: el default para la mayoría de los SaaS
CREATE TABLE orders (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,tenant_id BIGINT NOT NULL REFERENCES tenants(id),customer_id BIGINT NOT NULL,total_cents INTEGER NOT NULL,status TEXT NOT NULL,created_at TIMESTAMPTZ NOT NULL DEFAULT now());-- Todo índice que soporte una consulta acotada por tenant empieza con tenant_id,-- para que el planner pueda podar de inmediato a las filas de un solo tenant.CREATE INDEX idx_orders_tenant_created ON orders (tenant_id, created_at DESC);-- Aislamiento de cinturón y tirantes: row-level security en Postgres significa que-- incluso un WHERE olvidado en el código de la aplicación no puede filtrar filas de otro tenant.ALTER TABLE orders ENABLE ROW LEVEL SECURITY;CREATE POLICY tenant_isolation ON ordersUSING (tenant_id = current_setting('app.current_tenant_id')::BIGINT);
La aplicación fija app.current_tenant_id una vez por conexión/transacción (típicamente justo después de tomar una conexión del pool, a partir del claim de tenant de la petición autenticada), y desde ese punto cada consulta —incluso un SELECT * FROM orders escrito con descuido, sin ningún WHERE— queda filtrada de forma transparente a ese tenant por el propio Postgres. Esta es la red de seguridad más importante en un diseño de esquema compartido, porque la alternativa es "cada ingeniero, en cada consulta, para siempre, debe recordar filtrar por tenant_id" — una promesa que un código base termina rompiendo tarde o temprano.
El costo del esquema compartido es enteramente riesgo operativo concentrado en un solo lugar: un full table scan causado por la consulta mal escrita de un tenant quema I/O y buffer cache de los que dependen todos los demás tenants simultáneamente, y un problema de bloat de índice o de vacuum es problema de todos a la vez.
#Esquema por tenant: aislamiento sin proliferación de infraestructura
-- Onboarding del tenant 123: clonar la estructura del esquema plantilla.CREATE SCHEMA tenant_123;CREATE TABLE tenant_123.orders (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,customer_id BIGINT NOT NULL,total_cents INTEGER NOT NULL,status TEXT NOT NULL,created_at TIMESTAMPTZ NOT NULL DEFAULT now());
Aquí tenant_id desaparece de todas las tablas — el límite del esquema es el aislamiento, así que ningún WHERE tenant_id = ? puede olvidarse jamás. La trampa aparece en operaciones: agregar una columna ahora significa iterar cada esquema de tenant y correr la migración N veces (normalmente mediante una herramienta de migración manejada en un loop, con reintento/rollback rastreado por esquema), y la mayoría de los motores tiene un techo práctico de esquemas por instancia (Postgres maneja miles de esquemas, pero la planificación de consultas, pg_dump y las búsquedas de catálogo al conectar se vuelven más lentas conforme el número de esquemas crece hacia las decenas de miles).
#Base de datos por tenant: aislamiento total, peso operativo total
Esta es la decisión correcta cuando los tenants son lo bastante grandes como para que el riesgo de vecino ruidoso sea inaceptable, cuando los contratos exigen residencia de datos o infraestructura dedicada, o cuando los requisitos de cumplimiento/auditoría de un tenant empresarial grande (su propio calendario de backups, su propia llave de cifrado, su propia política de retención) no pueden satisfacerse con nada menos que una instancia dedicada. El costo es que "aprovisionar un tenant" se vuelve trabajo de infraestructura real, y correr una migración a través de una flota de 500 bases de datos de tenants necesita su propia orquestación (un runner de migraciones que itera cadenas de conexión, rastrea la versión de migración por tenant y maneja fallas parciales sin corromper el estado de toda la flota).
#El patrón que casi nadie se salta: un híbrido
Los backends SaaS de producción rara vez eligen un solo punto del espectro para todos sus tenants. La forma común es un nivel pooled (esquema compartido) para la larga cola de tenants gratuitos o de plan pequeño, donde el overhead por tenant debe ser casi cero, y un nivel siloed (esquema o base de datos por tenant) para cuentas empresariales que pagan por aislamiento, cumplimiento o rendimiento garantizado — con una ruta de migración para mover a un tenant de pooled a siloed conforme crece o sube de plan. Diseñar esa ruta de migración desde el día uno (identificadores de tenant consistentes, una capa de acceso a datos que no asuma "todo es una sola tabla compartida") es mucho más barato que improvisarla una vez que un solo prospecto empresarial exige un aislamiento que tu arquitectura nunca contempló.
🧠 Check your understanding
0/3 · 0/3 answered1. En un diseño multi-tenant de esquema compartido, ¿cuál es el beneficio práctico principal de habilitar row-level security además del filtrado por tenant_id a nivel de aplicación?
2. ¿Qué modelo de multi-tenancy hace más limpio ejecutar 'borrar todos los datos de un tenant' para una solicitud de cumplimiento?
3. ¿Por qué la mayoría de las plataformas SaaS grandes terminan usando una estrategia híbrida de multi-tenancy en lugar de un solo modelo para todos los tenants?