Multi-Tenencia
Reglas
- Aislamiento a nivel de fila (por defecto): agrega la columna
tenantId a cada tabla de ámbito del inquilino y aplícalo mediante middleware o RLS (Seguridad a Nivel de Fila)
- Esquema por inquilino: úsalo cuando los inquilinos necesiten esquemas personalizados o aislamiento estricto de datos — operaciones más complejas, mejor aislamiento
- Enrutamiento por subdominio: extrae el inquilino de
{tenant}.app.com — resuélvelo en middleware, establece tenantId en el contexto de la solicitud
- Dominios personalizados: CNAME a tu aplicación, busca el inquilino por el encabezado
Host — usa una tabla domains que mapee dominios personalizados a IDs de inquilino
- Configuración por inquilino: almacena tema (logo, colores), flags de características y límites en una tabla
tenant_settings — almacena en caché en memoria con TTL
- Patrón RLS (Postgres):
ALTER TABLE posts ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON posts USING (tenant_id = current_setting('app.tenant_id'))
- Middleware: extrae el inquilino del subdominio/encabezado, establécela en el contexto, aplica a todas las consultas de base de datos — nunca dejes que una consulta se ejecute sin ámbito de inquilino
- Particionamiento de datos: para tablas grandes, particiona por
tenantId — mejora el rendimiento de las consultas y permite copia de seguridad/restauración por inquilino
Patrón de Aislamiento a Nivel de Fila
function getTenantFromHost(host: string): string {
const subdomain = host.split(".")[0];
return subdomain;
}
prisma.$use(async (params, next) => {
if (TENANT_MODELS.includes(params.model)) {
params.args.where = { ...params.args.where, tenantId: context.tenantId };
}
return next(params);
});
Enrutamiento por Subdominio (Middleware de Next.js)
export function middleware(request: NextRequest) {
const host = request.headers.get("host") ?? "";
const tenant = host.split(".")[0];
const response = NextResponse.next();
response.headers.set("x-tenant-id", tenant);
return response;
}
Evitar
- Consultas sin ámbito de inquilino — un solo
WHERE tenantId = ? faltante filtra datos entre inquilinos
- Codificar el inquilino en el cliente — resuélvelo desde el subdominio/dominio en el servidor
- Sin índice en
tenantId — cada tabla de ámbito del inquilino necesita un índice compuesto que comience con tenantId
- Mezclar datos de inquilinos en cachés compartidas — asigna las claves de caché con
{tenantId}:{key}