Técnico8 min

Arquitectura Multi-tenant desde el Día 1

Nuestras decisiones de diseño iniciales para soportar múltiples clientes de forma segura y escalable, usando PostgreSQL y Supabase.

AM

Ana Martínez

CTO & Co-founder

Por Qué Multi-tenant desde el Inicio

Una de las decisiones más importantes que tomamos fue diseñar InfraUX como una aplicación multi-tenant desde el primer día. Esta decisión, aunque agregó complejidad inicial, ha sido fundamental para nuestro crecimiento y escalabilidad.

¿Qué es Multi-tenancy?

Multi-tenancy es un patrón de arquitectura donde una única instancia de la aplicación sirve a múltiples clientes (tenants), manteniendo sus datos completamente aislados y seguros. En nuestro caso, cada "company" en InfraUX es un tenant independiente.

El Gran Debate: MongoDB vs PostgreSQL

Inicialmente, consideramos MongoDB por su flexibilidad con esquemas dinámicos. Los diagramas de infraestructura pueden ser muy variados, y la naturaleza schema-less de MongoDB parecía ideal. Sin embargo, después de semanas de análisis, PostgreSQL emergió como el ganador.

Por qué elegimos PostgreSQL:

CaracterísticaPostgreSQLMongoDB
Row Level Security✅ Nativo❌ Manual
JSONB✅ Flexibilidad + Estructura✅ Solo flexibilidad
Transacciones ACID✅ Completas⚠️ Limitadas
Integración Supabase✅ Perfecta❌ No disponible
Madurez✅ 30+ años✅ 10+ años

Implementación de Row Level Security

RLS es la piedra angular de nuestra arquitectura multi-tenant. Cada tabla tiene políticas que garantizan que los usuarios solo pueden acceder a datos de su company:

1-- Política para la tabla diagrams 2CREATE POLICY "Users can only see their company diagrams" ON diagrams 3 FOR SELECT 4 USING (company_id = auth.jwt() ->> 'company_id'); 5 6-- Política para INSERT 7CREATE POLICY "Users can only create diagrams in their company" ON diagrams 8 FOR INSERT 9 WITH CHECK (company_id = auth.jwt() ->> 'company_id'); 10 11-- Política para UPDATE 12CREATE POLICY "Users can only update their company diagrams" ON diagrams 13 FOR UPDATE 14 USING (company_id = auth.jwt() ->> 'company_id') 15 WITH CHECK (company_id = auth.jwt() ->> 'company_id');

Estructura de Datos Multi-tenant

Nuestra estructura de base de datos sigue un patrón consistente:

1-- Tabla companies (root del tenant) 2CREATE TABLE companies ( 3 id UUID PRIMARY KEY DEFAULT gen_random_uuid(), 4 name TEXT NOT NULL, 5 slug TEXT UNIQUE NOT NULL, 6 created_at TIMESTAMPTZ DEFAULT NOW(), 7 settings JSONB DEFAULT '{}'::jsonb 8); 9 10-- Tabla users con relación a company 11CREATE TABLE users ( 12 id UUID PRIMARY KEY DEFAULT gen_random_uuid(), 13 company_id UUID REFERENCES companies(id) ON DELETE CASCADE, 14 email TEXT UNIQUE NOT NULL, 15 role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'member', 'viewer')), 16 created_at TIMESTAMPTZ DEFAULT NOW() 17); 18 19-- Tabla diagrams con company_id 20CREATE TABLE diagrams ( 21 id UUID PRIMARY KEY DEFAULT gen_random_uuid(), 22 company_id UUID REFERENCES companies(id) ON DELETE CASCADE, 23 name TEXT NOT NULL, 24 content JSONB NOT NULL, -- Aquí almacenamos el diagrama completo 25 environment TEXT NOT NULL, 26 created_by UUID REFERENCES users(id), 27 created_at TIMESTAMPTZ DEFAULT NOW(), 28 updated_at TIMESTAMPTZ DEFAULT NOW() 29); 30 31-- Índices para performance 32CREATE INDEX idx_diagrams_company_id ON diagrams(company_id); 33CREATE INDEX idx_diagrams_environment ON diagrams(company_id, environment); 34CREATE INDEX idx_diagrams_content_gin ON diagrams USING gin(content);

Gestión de Sesiones y Context

Cada request incluye el company_id en el JWT token, lo que permite a PostgreSQL aplicar automáticamente las políticas RLS:

1// middleware/auth.ts 2export async function withAuth(req: Request) { 3 const token = req.headers.get('Authorization')?.replace('Bearer ', ''); 4 5 if (!token) { 6 throw new Error('No authentication token'); 7 } 8 9 const { data: { user }, error } = await supabase.auth.getUser(token); 10 11 if (error || !user) { 12 throw new Error('Invalid token'); 13 } 14 15 // El company_id está en los metadatos del usuario 16 const companyId = user.user_metadata.company_id; 17 18 // Supabase automáticamente incluye esto en el JWT 19 return { user, companyId }; 20}

Beneficios de Nuestra Arquitectura

<div class="success-box"> ✅ **Seguridad por Diseño:** Con RLS, es imposible acceder accidentalmente a datos de otro tenant. Incluso si hay un bug en el código de la aplicación, PostgreSQL bloqueará el acceso. </div> <div class="info-box"> 💡 **Escalabilidad Horizontal:** Podemos escalar agregando más servidores de aplicación sin preocuparnos por la segregación de datos. </div> <div class="warning-box"> ⚠️ **Backup y Restore Granular:** Podemos hacer backup o restaurar datos de un tenant específico sin afectar a otros. </div>

Desafíos y Soluciones

Desafío 1: Migraciones de Schema

Las migraciones deben ser cuidadosamente diseñadas para no romper el aislamiento de datos.

Solución: Todas las migraciones pasan por un proceso de revisión estricto y se prueban en un ambiente multi-tenant de staging.

Desafío 2: Performance con Muchos Tenants

A medida que crecemos, las queries pueden volverse lentas si no están bien optimizadas.

Solución: Índices estratégicos en company_id y particionamiento de tablas grandes por tenant.

Desafío 3: Límites y Quotas por Tenant

Diferentes planes necesitan diferentes límites de recursos.

Solución: Sistema de quotas flexible almacenado en la tabla companies:

1{ 2 "quotas": { 3 "max_diagrams": 10, 4 "max_users": 5, 5 "max_environments": 3, 6 "features": { 7 "collaboration": true, 8 "api_access": false, 9 "custom_domains": false 10 } 11 } 12}

Lecciones Aprendidas

  1. Invertir en arquitectura temprano paga dividendos: La complejidad inicial se ha traducido en facilidad de mantenimiento
  2. RLS es poderoso pero requiere disciplina: Cada tabla nueva necesita políticas bien pensadas
  3. Testing multi-tenant es crítico: Tenemos tests específicos que verifican el aislamiento de datos
  4. Monitoreo por tenant es esencial: Necesitas saber qué tenant está causando problemas

El Futuro: Sharding y Más Allá

Aunque nuestra arquitectura actual puede manejar miles de tenants, ya estamos planeando el siguiente nivel:

  • Sharding por tenant: Los tenants grandes podrán tener su propia base de datos
  • Multi-región: Tenants podrán elegir dónde almacenar sus datos
  • Bring Your Own Database: Empresas podrán usar su propia instancia de PostgreSQL

La decisión de ir multi-tenant desde el día 1 ha sido fundamental para nuestro éxito. Aunque agregó complejidad inicial, nos ha permitido crecer de manera segura y escalable, manteniendo los datos de nuestros clientes completamente aislados y protegidos.

Métricas de Performance

MétricaValorObjetivo
Queries/segundo12,00010,000
Latencia p5023ms< 50ms
Latencia p9987ms< 200ms
Aislamiento de datos100%100%
Uptime99.97%99.9%
#arquitectura#multitenant#postgresql#supabase